Game Framework

BlitzMax Forums/BlitzMax Programming/Game Framework

I have developed a new framework for my current game, and would like to invite the Blitz community to pull it apart and insult my coding as much as you can.

Essentially i'd like to know how you would improve upon it.

Features:
* Object based Game States
* Multiple screen layers in single game list
* Delta time
* Free

'FRAMEWORK
'Si Dunford, Nov 2007
'V1.01
'
SuperStrict

Const MAXFramerate:Float = 60.0

'# SCREEN LAYERS
Const LAYER_BACKGROUND%		 = 0
Const LAYER_SCENERY%		 = 1
Const LAYER_PLAYER%			 = 2
Const LAYER_ENEMIES%		 = 3
Const LAYER_INTERFACE%		 = 4

'# Initialise the drawing layers
Graphics 800, 600
Tlayers.Create( 5 ) 

'############################################################
'# GAME LOOP	
Global game:TGame = New TGame.Create( New TStateSplash )

	'# Run the game
	game.start()

'############################################################
'# GAME STATES
Type TState
	Method enter() ; End Method
	Method update() ; End Method
	Method leave() ; End Method
End Type
'============================================================
Type TStateSplash Extends TState
	Global timeout% = MilliSecs() + 2000
	'----------------------------------------
	Method enter()
		'# Create Static Text
		New TText.Create( "TESTING", Null, -1, 100 )
		New TText.Create( "(c) Si Dunford", Null, -1, 200 ) 
		'# Create background
		New TBackground.Create()
	End Method
	'----------------------------------------
	Method update()
		If MilliSecs() > timeout Then game.changeState( New TStateInit )
	End Method
	'----------------------------------------
	Method leave() 
		TText.zap() 
	End Method
End Type
'============================================================
Type TStateInit Extends TState
	'----------------------------------------
	Method enter() 
		TLayers.zap() 
		game.changeState( New TStateStart ) 
		New TFps.Create( 10,10 )		'# Frames per second counter
	End Method
	'----------------------------------------
	Method leave() 
		'# Create background
		'# Needed here again cos we destroyed the whole game list in enter)
		New TBackground.Create()
	End Method
End Type
'============================================================
Type TStateStart Extends TState
	'----------------------------------------
	Method enter() 
		New TText.Create( "GAME FRAMEWORK", 	Null, -1, 300 ) 
		New TMenu.Create( "Start Game",			Null, -1, 350, 		New TStatePlay, True)
		New TMenu.Create( "Exit", 				Null, -1, 400, 		New TStateQuit)
	End Method
	'----------------------------------------
	Method leave() 
		TText.zap()
		TMenu.zap()
		'# Create player and enemies
	End Method
End Type
'============================================================
Type TStatePause Extends TState
	'----------------------------------------
	Method enter()
		'# Create Static Text
		New TText.Create( "PAUSED", 		Null, -1, 300 )
		New TMenu.Create( "Continue", 		Null, -1, 350, 		New TStatePlay, True)
		New TMenu.Create( "Quit game" , 	Null , -1, 400 , 	New TStateInit) 
		game.pause()
	End Method
	'----------------------------------------
	Method leave()
		TText.zap()
		TMenu.zap() 
		game.pause( False )
	End Method
End Type
'============================================================
Type TStatePlay Extends TState
	'----------------------------------------
	Method enter()
		New TText.Create( "RUNNING", 	Null, -1, 300 )
		New TText.Create( "Press ESC to pause", Null, -1, 350 )
	End Method
	'----------------------------------------
	Method update() 
		If KeyDown( KEY_ESCAPE ) Then game.changeState( New TStatePause )
	End Method
	'----------------------------------------
	Method leave() 
		TText.zap()
	End Method
End Type
'============================================================
Type TStateQuit Extends TState
	Method enter()
		game.quit() 
	End Method
End Type
'############################################################
'# GAME SYSTEM
Type TGame	'v1.1
	Field _paused:Int = 0
	Field _currentState:TState
	Field _hasQuit% = False
	'# Delta Timing
	Global _deltaTime:Float
	Global _delay% 
	'----------------------------------------
	Method Free() 
		_currentState.leave() 
	End Method
	'----------------------------------------
	Method Create:TGame( initialState:TState ) Final
		_currentState = initialState
	Return Self
	End Method
	'----------------------------------------
	Method _Update() Final
		'# Update Delta Timing
		_deltaTime = (MilliSecs() - _Delay ) * 0.001 '# px / second
		_Delay = MilliSecs() 
		'# Update Gamestate
		_currentState.update() 
		update() 
		'# Update Game objects
		TGameobj.updateAll()
		'# Draw screen layers ( Clearing screen between layers )
		TLayers.draw()
	End Method
	'----------------------------------------
	Method update() ; End Method
	'----------------------------------------
	Method changeState( newState:Tstate ) 
		_currentState.leave() 
		_currentState = newState
		_currentState.enter()
	End Method
	'----------------------------------------
	Method quit:Int() Final
		_hasQuit = True
	End Method
	'----------------------------------------
	Method isRunning:Int() Final
	Return Not(_hasQuit)
	End Method
	'----------------------------------------
	Function deltaTime:Float() Final
	Return _deltaTime
	End Function
	'----------------------------------------
	Method pause( state% = True ) Final
		_paused = state
	End Method	
	'----------------------------------------
	Method paused:Int() Final
	Return _paused
	End Method	
	'----------------------------------------
	Method start() Final
		'# Initialise the current state
		_currentState.enter()
		'# Initialise delta Timing
		_delay = MilliSecs()
		'# Run the game loop
		While Not(_hasquit)	'isRunning() 
			_update() 
		Wend
	End Method
End Type		
'############################################################
'# LAYERED GAME LIST
Type TLayers	'v1,1
	Global	layers:TList[]
'	Field	items:TList		'# List of objects in this layer
	'----------------------------------------
	Function Create( count% ) 
		layers = layers[..count]
		For Local element% = 0 To count -1
			layers[element] = CreateList() 
		Next
	End Function
	'----------------------------------------
	Function add( obj:TGameObj, l%)
		layers[l].addlast( obj )
	End Function
	'----------------------------------------
	'# If you need a clear screen, create a background object in layer 0.
	'# In it's draw() method, do the cls.
	Function draw()
		For Local layer:TList = EachIn layers
			For Local obj:TGameObj = EachIn layer
				obj.draw()
			Next
		Next
		Flip
	End Function
	'----------------------------------------
	Function zap()
		For Local layer:TList = EachIn layers
			layer.clear() 
		Next
	End Function
End Type
'############################################################
'# GAME OBJECTS
Type TGameObj	'v1.2
	Global list:TList = CreateList()
	Field x:Float , y:Float
	Field rotation% =0
	Field speed:Float =1
	Field image:TImage
	Field gameLink:TLink
	Field layerLink:TLink
	'----------------------------------------
	Function updateAll() 
		For Local obj:TGameObj = EachIn list
			obj.update() 
		Next
	End Function
	'----------------------------------------
	Method update() ; End Method
	'----------------------------------------
	Method draw()
		SetScale 1 , 1
		SetRotation( rotation ) 
		SetColor 255,255,255
		DrawImage( image , x , y ) 
	End Method
	'----------------------------------------
	Method add( layer% ) Final
		gameLink = list.addlast( Self ) 
		layerLink = TLayers.layers[layer].addlast(Self)
	End Method
	'----------------------------------------
	Method remove() Final
		If gamelink Then gamelink.remove() 
		If layerlink Then layerlink.remove()
		gameLink = Null
		layerlink = Null
	End Method
End Type
'============================================================
Type TBackground Extends TGameObj
	'----------------------------------------
	Method Create:TBackground() 
		add( LAYER_BACKGROUND )
	End Method
	'----------------------------------------
	Method draw()
		Cls
	End Method
End Type
'============================================================
Type TPlayer Extends TGameObj
	Method update()
		If KeyDown( KEY_LEFT ) Then x=x - 10
		If KeyDown( KEY_RIGHT ) Then x = x + 10
		If KeyDown( KEY_P ) Then game.changeState( New TStatePause )
	End Method
End Type
'============================================================
Type TText Extends TGameObj
	Field text$
	Field Font:TImageFont
	Field col% = rgb( 0 , 255 , 255 )
	'----------------------------------------
	Method Create:TText(Text:String , Font:TImageFont , x%, y%) 
		Self.text = text
		Self.Font = Font
		SetImageFont( font )
		If x = -1 Then x= GraphicsWidth() /2 '# Centre of screen if x=-1
		Self.X = X
		Self.Y = Y
		add( LAYER_INTERFACE )
	End Method
	'----------------------------------------
	Method colour( red% , green% , blue% )
		col = rgb( red, green, blue )
	End Method
	'----------------------------------------
	' For all you americans
	Method color( red% , green% , blue% )
		colour( red, green, blue )
	End Method
	'----------------------------------------
	Method draw()
		SetImageFont(Font)
		SetRotation 0
		SetScale 1.0,1.0
		SetColor getRed( col ) , getGreen( col ) , getblue( col )
		DrawText Text,x - TextWidth(text) /2 ,Y
	End Method
	'----------------------------------------
	'# Remove ALL objects of this type.
	Function zap()
		For Local obj:TText = EachIn list
			obj.remove() 
		Next
	End Function
End Type
'============================================================
'# Frames per second counter
Type TFPS Extends TGameObj	'v1.0
	Field counter%, time%, fps%
	Field Font:TImageFont
	Field col% = rgb( 0 , 255 , 255 )
	'----------------------------------------
	Method Create:TFPS( x%, y%, Font:TImageFont =Null) 
		Self.Font = Font
		SetImageFont( font )
		Self.X = X
		Self.Y = Y
		add( LAYER_INTERFACE )
	End Method
	'----------------------------------------
	Method colour( red% , green% , blue% )
		col = rgb( red, green, blue )
	End Method
	'----------------------------------------
	' For all you americans
	Method color( red% , green% , blue% )
		colour( red, green, blue )
	End Method
	'----------------------------------------
	Method update()
		counter:+1
		If time < MilliSecs()
			fps = counter' <- Frames/Sec
			time = MilliSecs() + 1000 'Update
			counter = 0
		EndIf
	End Method
	'----------------------------------------
	Method draw()
		SetImageFont(Font)
		SetRotation 0
		SetScale 1.0,1.0
		SetColor getRed( col ) , getGreen( col ) , getblue( col )
		DrawText "FPS: "+fps,x,Y
	End Method
End Type
'============================================================
Type TMenu Extends TGameObj
	Field text$
	Field Font:TImageFont
	Field col% = rgb( 255 , 255 , 255 )
	Field option:TState
	Field selected% = False
	Field dir% = -1
	Field fade% = 255
	
	'------------------------------------------------------------
	Method Create:TMenu(Text:String , Font:TImageFont, x% , y%, option:TState, selected%=False)
		Self.text = text
		Self.Font = Font
		SetImageFont( font )
		If x = -1 Then x= GraphicsWidth() /2 '# Centre of screen if x=-1
		Self.X = X
		Self.Y = Y
		Self.option = option
		Self.selected = selected
		add( LAYER_INTERFACE )
	End Method
	'----------------------------------------
	Method colour( red% , green% , blue% )
		col = rgb( red, green, blue )
	End Method
	'----------------------------------------
	' For all you americans
	Method color( red% , green% , blue% )
		colour( red, green, blue )
	End Method
	'------------------------------------------------------------
	Method draw() 
	Local str$ = text
		SetImageFont(Font)
		SetRotation 0
		SetScale 1.0,1.0
		If selected Then
			SetColor fade , fade , fade
			str = "["+text+"]"
		Else
			SetColor getRed( col ) , getGreen( col ) , getblue( col )
		End If
		DrawText Text,x - TextWidth(text) /2 ,Y
	End Method
	'------------------------------------------------------------
	Method update()
		If KeyDown( KEY_ENTER ) Then game.changestate(TMenu.getOption() ) 
		If KeyHit(KEY_UP) Then prevItem()
		If KeyHit(KEY_DOWN) Then nextItem()
		fade:+ dir * Int(255.0*game.deltatime()) '# 1 colour phase/second
		If fade > 255 Then
			fade = 255
			dir = - dir
		ElseIf fade < 0 Then
			fade = 0
			dir = - dir
		EndIf
		'# Required to stop key presses running over into sub-menu's
		FlushKeys()
	End Method
	'------------------------------------------------------------
	Function getOption:TState()
		For Local o:TMenu=EachIn list
			If o.selected Then Return o.option
		Next
	End Function

	'------------------------------------------------------------
	Function prevItem()
	Local last:Tmenu = Null
	Local item:Tmenu
		For Local o:TMenu=EachIn list
			If o.selected Then 
				o.selected = False	'# Remove old selection
				item = last			'# Select the previous item
			End If
			last = o
		Next
		'# Wrap around
		If Not item Then item = last
		'# Select item
		item.selected = True
	End Function

	'------------------------------------------------------------
	Function nextItem()
	Local first:Tmenu = Null
	Local item:Tmenu
	Local found% = False
		For Local o:TMenu=EachIn list
			If Not first Then first = o
			If found Then 
				item = o
				found = False
			End If
			If o.selected Then 
				o.selected = False	'# Remove old selection
				found = True
			End If
		Next
		'# Wrap around
		If Not item Then item = first
		'# Select item
		item.selected = True
	End Function
	
	'----------------------------------------
	'# Remove ALL objects of this type.
	Function zap()
		For Local obj:TMenu = EachIn list
			obj.remove() 
		Next
	End Function
End Type

'############################################################
'# Colour functions
Function rgb%(r%, g%, b%) ;	Return (r Shl 16) + (g Shl 8) + b ; End Function
'Function getA%(argb%) ; Return argb Shr 24 & $ff ; End Function
Function getRed%(rgb%) ; Return rgb Shr 16 & $ff ; End Function
Function getGreen%(rgb%) ; Return (rgb Shr 8) & $ff ; End Function
Function getBlue%(rgb%) ; Return rgb & $ff ;End Function
'


CRAAAAP

THE WORST THING I'VE EVER SEEN



Naw, now for real:

Looks nice.
SDL scene inspired, right?

Surely an interesting way of handling the whole thing.


Would potentially the "State" really rename to Scene to make it more intuitive what it means as a state in a game is quite a lot, not needfully a specific menu or the like. A state would be as well the wait on a new enemy wave and the like.

I have no idea what an SDL scene is, but I guess I should now google it.

An SDL scene is simply said a container for objects that you can move within that container, switch out with that container etc etc.

to some point similar to how hierarchy in Blitz3D works where you can "disable" anything by disabling the parent and the like.

Just that SDL scenes work like your states, you can switch between them. Scene Graphs if this helps :)

I like the FSM approach, very similar to how I do things.


Looks like you've got somthing very well written there.

I will take a closer look later.