Topics covered:
- graphic display
- video game basics
- engine basics
- object orientated programming.
Compiled in VS 2010, 3.5. Recommended Visual Studios 2005 or greater and framework 3.5 or greater.
Preface 1: I am going to assume you know how to create a Windows Form project and add class files to it.
Preface 2: This will be a quick walk through morphing the a VB.NET form into a game engine, show casing interesting bits of form styles to make it work, and a good example of object orientated programming fundamentals. It's not a serious engine or game programming how to, but a fun exercise.
Video games - gotta love them, but sometimes you just need to prototype something out fast. It turns out with some forward thinking you can make a pretty quick proof of concept game in a .NET windows form!
It's at this point I would highly advocate hitting up the XNA subforum and the XNA tutorials for games to be done in a proper setting through .NET. The flexibility and reliability is greatly held there so when you are done playing with this example upgrade and move over to the game programming tutorials!
By and by, to get started let's break down what is a video game. You have a user input, a game engine to process the current state in a consistently periodic time slice, and a video display. With in a windows form this means your form's keydown or mouse movement is the user input, a simple timer control will provide the periodic time, and the form's paint even for the display!
Honestly, it's that simple.
When the timer ticks (aka engine) any of our objects must process it's state and then draw itself. The state is any processing for that object. The user class processes where to move. The 'enemy' processes where on the path it needs to move by itself. The 'user' class could be adapted for attacking, using items, or feeling the effects of gravity in a jump!
Conceptually this is how the classes interact:

The trick is to compartmentalize your code in to object for reuse. In this case I have an abstract class that holds the basic information I think future classes will need to function. The bare necessities. An image to draw, a location, a required paint method, a required state to process, and a few other things.
Public MustInherit Class Entity Public Enum MovementDirection NONE = 0 UP = 1 RIGHT = 2 DOWN = 3 LEFT = 4 End Enum Protected _bitmapImage As Bitmap = Nothing '-- the image to be drawn. Protected _pointLocation As Point = Nothing '-- where on the map is the user. Protected _sID As String = String.Empty '-- ids are usually important Protected _lMovementRate As Int32 = 0 '-- how fast the location changes when a move command is issued. Protected _lScreenWidth As Int32 = 0 '-- used to determine the boundaries so the location is still visible. Property _lScreenHeight As Int32 = 0 '-- used to determine the boundaries so the location is still visible. Public WriteOnly Property ScreenHeight As Int32 Set(ByVal value As Int32) _lScreenHeight = value End Set End Property Public WriteOnly Property ScreenWidth As Int32 Set(ByVal value As Int32) _lScreenWidth = value End Set End Property ''' <summary> ''' Print pertinent information ''' </summary> ''' <returns></returns> ''' <remarks></remarks> Public Function Print() As String Return String.Format("{0}: x={1}, y={2}", _sID, _pointLocation.X, _pointLocation.Y) End Function '-- Everything must be drawn! Public MustOverride Sub Paint(ByVal e As Graphics) '-- Everything must be provided a consistant entry point to process that state for that given time slice. Public MustOverride Function ProcessState() As Boolean End Class
I then have a 'user' and 'enemy' class that inherit from the 'entity' abstract class. This means user and enemy have all the functionality of 'entity' and can add to it! Future classes like items could also extend 'entity' and be mostly complete!
My 'user' class processes what action is next when the engine ticks. This means if a user pushed left then it moves left a set number of pixels. It's bound by the dimensions of the form so hopefully the location will not wander off the grid.
Note - you will need to define the path to the blue image here. If you don't it won't show up.
Public Class User Inherits Entity Private _enumNextAction As MovementDirection = MovementDirection.NONE Private _lHealth As Int32 = 1 Public Property NextAction As MovementDirection Get Return _enumNextAction End Get Set(ByVal value As MovementDirection) _enumNextAction = value End Set End Property Public Sub New() _pointLocation = New Point(50, 50) _lMovementRate = 10 _sID = "User" Try _bitmapImage = New Bitmap("your file path\blue.jpg") Catch ex As Exception MsgBox(ex.Message, MsgBoxStyle.OkOnly, String.Format("Class: {0}", Me.GetType().Name)) _bitmapImage = New Bitmap(10, 10) End Try End Sub ''' <summary> ''' A constructor that takes in all the important bits of information to show the image on the screen. ''' </summary> ''' <param name="startingLocation"></param> ''' <param name="movementRate"></param> ''' <param name="imageLocation"></param> ''' <remarks></remarks> Public Sub New(ByVal startingLocation As Point, ByVal movementRate As Int32, ByVal imageLocation As String) _pointLocation = startingLocation _lMovementRate = movementRate _bitmapImage = New Bitmap(imageLocation) _sID = "User" End Sub '-- Sets up drawing the image. Public Overrides Sub Paint(ByVal e As System.Drawing.Graphics) e.DrawImage(_bitmapImage, _pointLocation) End Sub Public Overrides Function ProcessState() As Boolean '-- The only state the user needs to worry about is which direction the user wants to move. Return DoMovement(_enumNextAction) End Function ''' <summary> ''' Takes the information from the user's key press and update the location. ''' </summary> ''' <param name="direction"></param> ''' <returns></returns> ''' <remarks></remarks> Private Function DoMovement(ByVal direction As MovementDirection) As Boolean Dim bMoveOccured As Boolean = False If _enumNextAction <> MovementDirection.NONE Then Select Case direction Case MovementDirection.UP If _pointLocation.Y - _lMovementRate >= 0 Then '-- make sure user is not moving off the screen _pointLocation.Y -= _lMovementRate bMoveOccured = True Else _enumNextAction = MovementDirection.NONE End If Case MovementDirection.DOWN If _pointLocation.Y + _bitmapImage.Height + _lMovementRate < _lScreenHeight Then '-- make sure user is not moving off the screen _pointLocation.Y += _lMovementRate bMoveOccured = True Else _enumNextAction = MovementDirection.NONE End If Case MovementDirection.RIGHT If _pointLocation.X + _bitmapImage.Width + _lMovementRate < _lScreenWidth Then '-- make sure user is not moving off the screen _pointLocation.X += _lMovementRate bMoveOccured = True Else _enumNextAction = MovementDirection.NONE End If Case MovementDirection.LEFT If _pointLocation.X - _lMovementRate >= 0 Then '-- make sure user is not moving off the screen _pointLocation.X -= _lMovementRate bMoveOccured = True Else _enumNextAction = MovementDirection.NONE End If Case Else bMoveOccured = False End Select If bMoveOccured Then _enumNextAction = MovementDirection.NONE End If Return bMoveOccured End Function ''' <summary> ''' This shows we can tack more information as we add variables to the print method already in the abstract class. ''' </summary> ''' <returns></returns> ''' <remarks></remarks> Public Overloads Function Print() As String Return MyBase.Print + String.Format(" Health: {0}", _lHealth) End Function End Class
The 'enemy' class displays a different graphic and just walks a two hundred pixel path. It's all hard coded and you can alter the class later on to make the path be different. Notice how this is very similar in setup to the 'user' class, but different in the state processing and a few other areas. Oh the joys of object orientated programming!
Note - you will need to define the path to the red triangle image here. If you don't it won't show up.
Public Class Enemy Inherits Entity Private _bDirection As Boolean = True Private _pointStart As Point = Nothing Private _pointEnd As Point = Nothing Public Sub New() _pointLocation = New Point(400, 50) _lMovementRate = 10 _sID = "Enemy1" SetupPath() Try _bitmapImage = New Bitmap("your path to\red_triangle.jpg") Catch ex As Exception MsgBox(ex.Message, MsgBoxStyle.OkOnly, String.Format("Class: {0}", Me.GetType().Name)) _bitmapImage = New Bitmap(10, 10) End Try End Sub '-- determine the start and stop points for the path to follow. '-- This is hard coded to be just two hundred pixels below the current starting position. Private Sub SetupPath() _pointStart = _pointLocation _pointEnd = New Point(_pointStart.X, _pointStart.Y + 200) End Sub Public Overrides Sub Paint(ByVal e As System.Drawing.Graphics) '-- Paints the image at the current location. e.DrawImage(_bitmapImage, _pointLocation) End Sub Public Overrides Function ProcessState() As Boolean '-- The only state the enemy is required to care about is if it is following the path hard coded. WalkPath() Return True End Function ''' <summary> ''' A simple back and forth path. The location moves in one direction until it hits that direction, flips a boolean, and goes the opposite. ''' </summary> ''' <remarks></remarks> Private Sub WalkPath() If _bDirection Then If _pointLocation.Y + _lMovementRate + _bitmapImage.Height <= _pointEnd.Y Then _pointLocation.Y += _lMovementRate Else _bDirection = False End If Else If _pointLocation.Y - _lMovementRate >= _pointStart.Y Then _pointLocation.Y -= _lMovementRate Else _bDirection = True End If End If End Sub End Class
My form creates the user and enemy objects, reacts to keyboard input by setting the user's next action, takes care of the periodic engine processing for the user and enemy,and draws them both.
There are some critical styles set in the form's new that keep the movement smooth and not flickering. Also notice how when the key down is pushed and held only one 'next action' is set - this prevents a backup of key presses where the 'user' object tries to catch up and process a very long series of events.
Also notice the complexity of processing the states or graphics are all pushed into the objects. This makes the window form very simple to read and understand!
Public Class Form1 Private _IsDebug As Boolean = False '-- turn on if you want to see information in the debug window about the object's state. Private WithEvents _timer As Timer = Nothing '-- the 'game engine'. Private _oUser As User '-- our user class Private _oEnemy As Enemy = Nothing '-- An enemy class that follows a way point that is hard coded. Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. '-- Setup the display to not flicker and procss the graphics smoothly. Me.SetStyle(ControlStyles.UserPaint, True) Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True) Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True) Me.Height = 500 Me.Width = 500 '-- create our user to be drawn. _oUser = New User _oUser.ScreenHeight = Me.Height '-- to prevent moving off the screen! _oUser.ScreenWidth = Me.Width '-- to prevent moving off the screen! If _IsDebug Then Debug.WriteLine(_oUser.Print) _oEnemy = New Enemy _oEnemy.ScreenHeight = Me.Height '-- to prevent moving off the screen! _oEnemy.ScreenWidth = Me.Width '-- to prevent moving off the screen! If _IsDebug Then Debug.WriteLine(_oEnemy.Print) '-- setup our timer as the engine to process. _timer = New Timer _timer.Interval = 100 _timer.Start() End Sub '-- the "game engine". Private Sub _timer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles _timer.Tick '-- When the engine ticks proces the input into something our user class can do. _oUser.ProcessState() If _IsDebug Then Debug.WriteLine(_oUser.Print) _oEnemy.ProcessState() If _IsDebug Then Debug.WriteLine(_oEnemy.Print) '-- Redraw the scene Me.Refresh() End Sub '-- The take the form's input and set that action to our user object's next action on deck. Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown '-- To avoid the issue of having a stack of key presses if the user holds the key down only accept one '-- key stroke until it is processed and the graphics updated. If _oUser.NextAction = Entity.MovementDirection.NONE Then Select Case e.KeyCode Case Keys.Up, Keys.W _oUser.NextAction = Entity.MovementDirection.UP Case Keys.Down, Keys.S _oUser.NextAction = Entity.MovementDirection.DOWN Case Keys.Right, Keys.D _oUser.NextAction = Entity.MovementDirection.RIGHT Case Keys.Left, Keys.A _oUser.NextAction = Entity.MovementDirection.LEFT End Select End If End Sub Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint _oEnemy.Paint(e.Graphics) '-- Pain the changes to the screen. _oUser.Paint(e.Graphics) End Sub End Class
In theory this is what your screen should look like when you run the code.

Zip

Number of downloads: 1194
Helpful links:
XNA subforum
Game Programming
MSDN: Object-Oriented Programming in Visual Basic .NET
Advance topics
- change the images to something you created.
- animation in a windows form
- collisions between objects
- inventory: tracking, collecting, and using.
- interaction between objects (attacking and damage!)
- menus - drawing them
- more complex way-points for the enemy to follow.
- flesh out the enemy's constructor
This post has been edited by modi123_1: 25 May 2021 - 10:10 PM
Reason for edit:: Added github