My prototype game template with game state manager

Discuss any general programming issues here
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

My prototype game template with game state manager

Post by jalih »

Hi all,

Can you guys (and girls, if any? ;) ) test my current work in a progress game framework template?

Some remarks:

- All interacting game objects must have at least: :update(dt) and :draw() methods.
- All game states must have at least: .init(), .update(dt) and .draw() functions.

Required methods and functions can be dummy of course, if you don't need them.

Put example scripts in same directory and run the main.hws script. Be sure to save example scripts with correct names.

Code: Select all

 ;
 ; Game template module: game.hws
 ;

 game = {}
 game.updateRate = 20 ; Default to 20 millisec = 50 fps.
 game.dt = 0
 game.fps = 0 


 ; Game setup function for the user. 
 Function game.load()
	 ; Our game must override this dummy function.
 EndFunction


 ; Game update function for the user 
 Function game.update(dt)
	 ; Our game must override this dummy function.
 EndFunction


 ; Game draw frame function for the user 
 Function game.draw()
	 ; Our game must override this dummy function.	
 EndFunction


 ; Our game framework init function. Calls just user game setup function for now
 ; and starts double buffering. 
 Function game.init()
	game.load()
	BeginDoubleBuffer()	
 EndFunction


 ; Basic game loop function for our framework.
 Function game.loop(dt)
	game.state.states[game.state.state].update(dt)
	game.state.states[game.state.state].draw()
	Flip()
 EndFunction


 ; Set update rate for the game.  
 Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
 EndFunction


 ; Get the current fps-rate.
 Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
 EndFunction


 ; Runs the game, handles calling the game loop and timing.
 Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat 
		 ; Try to lock the frame update rate to game.updateRate
		 WaitTimer(1, game.updateRate)
		 ; Get the actual time it took to update the frame.
		 ftime = GetTimer(2)
		 ResetTimer(2)
		 ; Delta time in seconds between the two last frames.
		 game.dt = game.updateRate / 1000 * ftime / game.updateRate
		 game.loop(game.dt)	
	Forever
 EndFunction


 ; Simple game state manager
 game.state = {}
 game.state.states = {}
 game.state.states[0] = {} ; Default state 
 game.state.states[0].init = Function () EndFunction ; Initialized once on startup.
 game.state.states[0].update = Function (dt) game.update(dt) EndFunction 
 game.state.states[0].draw = Function () game.draw() EndFunction
 game.state.state = 0
 game.state.ids = {}


 ; Registers new game state by name.
 Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)	
 EndFunction


 ; Remove game state from manager by state name.
 Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend

 EndFunction


 ; Clears all states from manager except default state.
 Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
	
	self.ids = {}
	
 EndFunction


 ; Set active game state by name
 Function game.state:set(statename)
	self.state = statename
	self.states[self.state].init()
 EndFunction


 ; Get name of the current active game state
 Function game.state:get()
	Return(self.state)
 EndFunction

Code: Select all

 ;
 ; Intro state: intro.hws
 ;

 intro = {}
 

 Function intro.init()
	intro.counter = 0
 EndFunction


 Function intro.update(dt)
	intro.counter = intro.counter + dt
	If intro.counter >= 7
		intro.counter = 0
		game.state:set(0) ; Switch to default game state.
	EndIf
 EndFunction


 Function intro.draw()
	Cls()
	If intro.counter < 5
		drawscene()
		TextOut(#CENTER, #CENTER, "GET READY: " ..Round(intro.counter))
		player:draw()
	Else
		drawscene()
		TextOut(#CENTER, #CENTER, "GO!")
		player:draw()
	EndIf
 EndFunction

Code: Select all

 ;
 ; Player module: player.hws
 ;

 player = {}
 player.shootdelay = 1 ; Shoot delay in seconds.


 Function player:init(x, y, width, height, speed)
	self.x = x
	self.y = y
	self.width = width
	self.height = height
	self.speed = speed
 EndFunction


 Function player:draw()
	SetFillStyle(#FILLCOLOR)
	Box(self.x, self.y, self.width, self.height, #GREEN)
 EndFunction
	

 Function player:update(dt)
	If IsKeyDown("LEFT")
		self.x = self.x - self.speed * dt
		If self.x < 0 Then self.x = 0
	ElseIf IsKeyDown("RIGHT")
		self.x = self.x + self.speed * dt
		If self.x > 800 - self.width Then self.x = 800 - self.width
	EndIf
	
	self.shootdelay = self.shootdelay + dt
	If IsKeyDown("SPACE") Then self:shoot()
		
 EndFunction


 Function player:shoot()
	If self.shootdelay >= 1
		Local missile = {}
		missile.x = self.x + (self.width / 2)
		missile.y = self.y
		
		pmissiles:add(missile) ; Add player missile.
		
		self.shootdelay = 0 ; Reset shoot delay.
	EndIf
 EndFunction

Code: Select all

 ;
 ; Player missiles module: pmissiles.hws
 ;
 pmissiles = {}
 pmissiles.missiles = {} ; Table for missiles: missile = { x, y }
 pmissiles.width = 2
 pmissiles.height = 5
 pmissiles.speed = 100


 Function pmissiles:add(missile)
	InsertItem(self.missiles, missile)
 EndFunction


 Function pmissiles:update(dt)
	Local i, v = NextItem(self.missiles)
	While GetType(i) <> #NIL
		; Move missile upwards
		v.y = v.y - self.speed * dt
		If v.y < 0 ; Remove missile outside the screen.
			RemoveItem(self.missiles, i)
		Else
			; Check collision against aliens.		
		EndIf

		 i, v = NextItem(self.missiles, i)
	Wend	
 EndFunction


 Function pmissiles:draw()
	Local i, v = NextItem(self.missiles)
	While GetType(i) <> #NIL
		SetFillStyle(#FILLCOLOR)
		Box(v.x, v.y, self.width, self.height, #WHITE)
		 i, v = NextItem(self.missiles, i)
	Wend
 EndFunction

Code: Select all

 ;
 ; Main module: main.hws
 ;

 @DISPLAY {Width = 800, Height = 600, Title = "Game State Example"}

 ; include game framework
 @INCLUDE "game.hws"        ; include our game framework template 

 ; Include game objects
 @INCLUDE "player.hws"      ; include player object
 @INCLUDE "pmissiles.hws"   ; include player missiles object 

 ; Include game states
 @INCLUDE "intro.hws"       ; include intro state 


 Function game.load()
	game.state:register("intro", intro) ; Register new game state	
	game:setUpdateRate(50) ; Set the game update rate
	player:init(380, 500, 40, 20, 120) ; Setup game object
	game.state:set("intro") ; Start in intro state
 EndFunction


 Function game.update(dt)
	player:update(dt)
	pmissiles:update(dt)
 EndFunction


 Function game.draw()
	drawscene()
	player:draw()
	pmissiles:draw()
 EndFunction




 Function drawscene()
	Cls()
	SetFillStyle(#FILLCOLOR)
	Box(0, 520, 800, 600, #GRAY)
 EndFunction


 ; Game time!
 game.init()
 game.run()
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

Another more complete example:

With a little work and love, it could look like Space Invaders...

Code: Select all

 ;
 ; Game template module: game.hws
 ;

 game = {}
 game.updateRate = 20 ; Default to 20 millisec = 50 fps.
 game.dt = 0
 game.fps = 0


 ; Game setup function for the user.
 Function game.load()
	; Our game must override this dummy function.
 EndFunction


 ; Game update function for the user
 Function game.update(dt)
	; Our game must override this dummy function.
 EndFunction


 ; Game draw frame function for the user
 Function game.draw()
	; Our game must override this dummy function.   
 EndFunction


 ; Our game framework init function. Calls just user game setup function for now
 ; and starts double buffering.
 Function game.init()
	game.load()
	BeginDoubleBuffer()   
 EndFunction


; Basic game loop function for our framework.
Function game.loop(dt)
	game.state.states[game.state.state].update(dt)
	game.state.states[game.state.state].draw()
	Flip()
EndFunction


; Set update rate for the game. 
Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
EndFunction


; Get the current fps-rate.
Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
EndFunction


; Runs the game, handles calling the game loop and timing.
Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame update rate to game.updateRate
		WaitTimer(1, game.updateRate)
		; Get the actual time it took to update the frame.
		ftime = GetTimer(2)
		ResetTimer(2)
		; Delta time in seconds between the two last frames.
		game.dt = game.updateRate / 1000 * ftime / game.updateRate
		game.loop(game.dt)   
	Forever
EndFunction


Function game:go()
	self.init()
	self.run()
EndFunction



; Simple game state manager
game.state = {}
game.state.states = {}
game.state.states[0] = {} ; Default state
game.state.states[0].init = Function () EndFunction ; Initialized once on startup.
game.state.states[0].update = Function (dt) game.update(dt) EndFunction
game.state.states[0].draw = Function () game.draw() EndFunction
game.state.state = 0
game.state.ids = {}


; Registers new game state by name.
Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)   
EndFunction


; Remove game state from manager by state name.
Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend

EndFunction


; Clears all states from manager except default state.
Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
       
	self.ids = {}
       
EndFunction


; Set active game state by name
Function game.state:set(statename)
	self.state = statename
	self.states[self.state].init()
EndFunction


; Get name of the current active game state
Function game.state:get()
	Return(self.state)
EndFunction

Code: Select all

 ;
 ; New game state: ngame.hws
 ;

 ngame = {}

 ; Init game variables and objects
 Function ngame.init()
	level = 1
	lives = 3
	score = 0
	player:init(380, 500, 40, 20, 120)
	enemies:init()
	game.state:set("delay")  ; Change game state to level start delay state.
 EndFunction


 Function ngame.update(dt)

 EndFunction


 Function ngame.draw()
	
 EndFunction

Code: Select all

 ;
 ; Delay state: delay.hws
 ;

 delay = {}


 Function delay.init()
	delay.counter = 3
 EndFunction


 Function delay.update(dt)
	delay.counter = delay.counter - dt
	If delay.counter <= -2
		game.state:set(0) ; Switch to default game state.
	EndIf
 EndFunction


 Function delay.draw()
	Cls()
	If delay.counter > 0
		drawscene()
		player:draw()
		enemies:draw()
		TextOut(10, #TOP + 12, "SCORE: " ..score)
		TextOut(10, #TOP + 24, "LEVEL: " ..level)
		TextOut(10, #TOP + 36, "LIVES: " ..lives)
		TextOut(#CENTER, #CENTER - 20, "LEVEL " ..level)
		TextOut(#CENTER, #CENTER, "GET READY: " ..Round(delay.counter))
	Else
		drawscene()
		player:draw()
		enemies:draw()
		TextOut(10, #TOP + 12, "SCORE: " ..score)
		TextOut(10, #TOP + 24, "LEVEL: " ..level)
		TextOut(10, #TOP + 36, "LIVES: " ..lives)
		TextOut(#CENTER, #CENTER - 20, "LEVEL " ..level)
		TextOut(#CENTER, #CENTER, "GO!")
	EndIf
 EndFunction

Code: Select all

 ;
 ; player dies state: die.hws
 ;

 die = {}


 Function die.init()
	die.counter = 0
	lives = lives - 1
	pmissiles.missiles = {}
	emissiles.missiles = {}
 EndFunction


 Function die.update(dt)
	die.counter = die.counter + dt
	If die.counter >= 1
		game.state:set(0) ; Switch to default game state.
	EndIf
 EndFunction


 Function die.draw()

 EndFunction

Code: Select all

 ;
 ; Level up state: lup.hws
 ;

 lup = {}


 Function lup.init()
	level = level + 1
	player:init(380, 500, 40, 20, 120)
	enemies:init()
	pmissiles.missiles = {}
	emissiles.missiles = {}
	enemies:init()
	game.state:set("delay")
 EndFunction


 Function lup.update(dt)

 EndFunction


 Function lup.draw()
	
 EndFunction

Code: Select all

 ;
 ; Game over state: gover.hws
 ;

 gover = {}


 Function gover.init()
	gover.counter = 0
 EndFunction


 Function gover.update(dt)
	gover.counter = gover.counter + dt
	If IsKeyDown("SPACE") And gover.counter >= 2
		game.state:set("ngame") ; Switch to new game state.
	EndIf
 EndFunction


 Function gover.draw()
	TextOut(#CENTER, #CENTER, "GAME OVER")
	TextOut(10, #TOP + 12, "SCORE: " ..score)
	TextOut(10, #TOP + 24, "LEVEL: " ..level)
	TextOut(10, #TOP + 36, "LIVES: " ..lives)
 EndFunction

Code: Select all

;
; Player module: player.hws
;

player = {}
player.shootdelay = 1 ; Shoot delay in seconds.


Function player:init(x, y, width, height, speed)
	self.x = x
	self.y = y
	self.width = width
	self.height = height
	self.speed = speed
EndFunction


Function player:draw()
	SetFillStyle(#FILLCOLOR)
	Box(self.x, self.y, self.width, self.height, #GREEN)
EndFunction
	

Function player:update(dt)
	If IsKeyDown("LEFT")
		self.x = self.x - self.speed * dt
		If self.x < 0 Then self.x = 0
	ElseIf IsKeyDown("RIGHT")
		self.x = self.x + self.speed * dt
		If self.x > 800 - self.width Then self.x = 800 - self.width
	EndIf
	
	self.shootdelay = self.shootdelay + dt
	If IsKeyDown("SPACE") Then self:shoot()
	
		
EndFunction


Function player:shoot()
	If self.shootdelay >= 1
		Local missile = {}
		missile.x = self.x + (self.width / 2)
		missile.y = self.y
		
		pmissiles:add(missile)
		
		self.shootdelay = 0
	EndIf
EndFunction

Code: Select all

;
; Player missiles module: pmissiles.hws
;
pmissiles = {}
pmissiles.missiles = {} ; Table for missiles: missile = { x, y }
pmissiles.width = 2
pmissiles.height = 5
pmissiles.speed = 100


Function pmissiles:add(missile)
	InsertItem(self.missiles, missile)
EndFunction


Function pmissiles:update(dt)
	Local i, v = NextItem(self.missiles)
	While GetType(i) <> #NIL
		v.y = v.y - self.speed * dt
		If v.y < 0
			RemoveItem(self.missiles, i)
		Else
			; Check collision against aliens.
			Local ii, vv = NextItem(enemies.enemies)
			While GetType(ii) <> #NIL
				If CheckCollision(v.x, v.y, self.width, self.height, vv.x, vv.y, vv.width, vv.height)
					score = score + vv.score
					RemoveItem(enemies.enemies, ii)
				EndIf
		 		ii, vv = NextItem(enemies.enemies, ii)
			Wend
		EndIf

		 i, v = NextItem(self.missiles, i)
	Wend
	
EndFunction


Function pmissiles:draw()
	Local i, v = NextItem(self.missiles)
	While GetType(i) <> #NIL
		SetFillStyle(#FILLCOLOR)
		Box(v.x, v.y, self.width, self.height, #WHITE)
		 i, v = NextItem(self.missiles, i)
	Wend
EndFunction

Code: Select all

 ;
 ; enemies module: enemies.hws
 ;


 enemies = {}

 ; state table for enemies
 enemies.states = {}
 ; do nothing
 enemies.states[0] = Function () EndFunction
 ; shoot state
 enemies.states[1] = Function (v) If Rnd(240) = 1 Then v:shoot() EndFunction

 enemies.speed = 20
 enemies.frame = 0
 enemies.timepassed = 0


 Function enemies:init()
	self.enemies = {}
	
	Local starty
	If level < 19
		starty = level
	Else
		starty = 19
	EndIf
	
	For Local i = 1 To 8 Step 1
		Local enemy = {}
		enemy.width = 40
		enemy.height = 20
		enemy.x = i * (enemy.width + 40) + 40
		enemy.y = starty * enemy.height + 100
		enemy.statetime = Rnd(4) + 1
		enemy.timepassed = 0
		enemy.state = Rnd(2)
		enemy.score = 10

		Function enemy:shoot()
			Local missile = {}
			missile.x = self.x + self.width/2
			missile.y = self.y + self.height
			emissiles:add(missile)
		EndFunction

		InsertItem(self.enemies, enemy)
	Next
 EndFunction


 Function enemies:update(dt)
	Local fall = False
	Local temp
	Local moveFactor = 0
	
	If ListItems(self.enemies) = 0 Then Return
	
	Local i, v = NextItem(self.enemies)
	While GetType(i) <> #NIL	
		If v.x < 0
			temp = -v.x
			Local ii, vv = NextItem(self.enemies)
			While GetType(ii) <> #NIL	
				vv.x = vv.x + temp
				ii, vv = NextItem(self.enemies, ii)
			Wend
			self.speed = -self.speed
			fall = True
			
			Break
		ElseIf v.x + v.width > 800
			temp = v.x + v.width - 800
			Local ii, vv = NextItem(self.enemies)
			While GetType(ii) <> #NIL	
				vv.x = vv.x - temp
				ii, vv = NextItem(self.enemies, ii)
			Wend
			self.speed = -self.speed
			fall = True
			
			Break
		EndIf
		
		i, v = NextItem(self.enemies, i)
	Wend
	
	self.timepassed = self.timepassed + dt
	If self.timepassed >= ListItems(self.enemies) / 8 + 0.1
		moveFactor = 1
		self.frame = self.frame + 1
		If self.frame > 1 Then self.frame = 0
		
		self.timepassed = 0
	Else
		moveFactor = 0
	EndIf
	
	Local i, v = NextItem(self.enemies)
	While GetType(i) <> #NIL	
		v.x = v.x + self.speed * moveFactor
		v.timepassed = v.timepassed + dt
			
		If v.timepassed > v.statetime
			v.timepassed = 0
			v.statetime = Rnd(4) + 1
			v.state = v.state + 1
			If v.state > ListItems(self.states) - 1 Then v.state = 0
		EndIf
		
		self.states[v.state](v)
				
		i, v = NextItem(self.enemies, i)
	Wend
	
	If fall
		Local i, v = NextItem(self.enemies)
		While GetType(i) <> #NIL	
			v.y = v.y + v.height
			If v.y >= 520 - v.height
				lives = 0
			EndIf
			
			i, v = NextItem(self.enemies, i)
		Wend
		
		fall = False
	EndIf
	
 EndFunction

				
 Function enemies:draw()
	Local colors = {}
	colors[0] = #RED
	colors[1] = #YELLOW
	
	SetFillStyle(#FILLCOLOR)
	Local i, v = NextItem(self.enemies)
	While GetType(i) <> #NIL	
		Box(v.x, v.y, v.width, v.height, colors[self.frame])
		 i, v = NextItem(self.enemies, i)
	Wend
 EndFunction

Code: Select all

 ;
 ; Enemy missiles module: emissiles.hws
 ;


 emissiles = {}
 emissiles.missiles = {}
 emissiles.width = 2
 emissiles.height = 5
 emissiles.speed = 100


Function emissiles:add(missile)
	InsertItem(self.missiles, missile)
EndFunction


 Function emissiles:update(dt)
	Local i, v = NextItem(self.missiles)
	While GetType(i) <> #NIL
		v.y = v.y + self.speed * dt
		If v.y > 520 - self.height
			RemoveItem(self.missiles, i)
		Else
			If CheckCollision(v.x, v.y, self.width, self.height, player.x, player.y, player.width, player.height)
				RemoveItem(self.missiles, i)
				game.state:set("die") ; Shit happens... player just got killed.
				break
			EndIf
		EndIf
		 
		i, v = NextItem(self.missiles, i)
	Wend
 EndFunction


 Function emissiles:draw()
	Local i, v = NextItem(self.missiles)
	While GetType(i) <> #NIL
		SetFillStyle(#FILLCOLOR)
		Box(v.x, v.y, self.width, self.height, #WHITE)
		 i, v = NextItem(self.missiles, i)
	Wend
 EndFunction

Code: Select all

 ;
 ; Main module: main.hws
 ;

 @DISPLAY {Width = 800, Height = 600, Title = "Simple Game Example"}

 ; Include game framework
 @INCLUDE "game.hws"        ; include our game framework template

 ; Include game states
 @INCLUDE "ngame.hws"      ; new game state
 @INCLUDE "delay.hws"      ; level start delay state
 @INCLUDE "die.hws"        ; player dies state
 @INCLUDE "gover.hws"      ; game over state
 @INCLUDE "lup.hws"        ; level up state

 ; Include game objects
 @INCLUDE "player.hws"      ; include player object
 @INCLUDE "pmissiles.hws"   ; include player missiles object 
 @INCLUDE "enemies.hws"     ; include enemies object
 @INCLUDE "emissiles.hws"   ; include enemy missiles object 
 

 ; Game setup
 Function game.load()
    ; First let's register game states.
	game.state:register("ngame", ngame)
	game.state:register("delay", delay)
	game.state:register("die", die)	
	game.state:register("gover", gover)
	game.state:register("lup", lup)	
	game:setUpdateRate(60)   ; Set game update rate.
	game.state:set("ngame")  ; Start in new game state
 EndFunction


 ; game state 0 update function
 Function game.update(dt)
	If lives > 0
		player:update(dt)
		enemies:update(dt)
		pmissiles:update(dt)
		emissiles:update(dt)
		
		Local enemycount = ListItems(enemies.enemies)
		If enemycount = 0
			game.state:set("lup")
		EndIf
	Else
		game.state:set("gover")
	EndIf
 EndFunction


 ; Game state 0 draw function
 Function game.draw()
	drawscene()
	player:draw()
	enemies:draw()
	pmissiles:draw()
	emissiles:draw()
	TextOut(10, #TOP + 12, "SCORE: " ..score)
	TextOut(10, #TOP + 24, "LEVEL: " ..level)
	TextOut(10, #TOP + 36, "LIVES: " ..lives)
 EndFunction




 Function drawscene()
	Cls()
	SetFillStyle(#FILLCOLOR)
	Box(0, 520, 800, 600, #GRAY)
 EndFunction

 
 Function CheckCollision(box1x, box1y, box1w, box1h, box2x, box2y, box2w, box2h)
    If box1x > box2x + box2w - 1 Or ; Is box1 on the right side of box2?
       box1y > box2y + box2h - 1 Or ; Is box1 under box2?
       box2x > box1x + box1w - 1 Or ; Is box2 on the right side of box1?
       box2y > box1y + box1h - 1    ; Is b2 under b1?
       Return(False) ; No Collision
    Else
	Return(True)  ; Collision
    EndIf
 EndFunction


 ; Here we go!
 game:go()
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

A little update...

Replace game.hws by this:

Code: Select all

;
; Game template module: game.hws
;

game = {}
game.updateRate = 20 ; Default to 20 millisec = 50 fps.
game.dt = 0
game.fps = 0


; Game setup function for the user.
Function game.load()
	; Our game must override this dummy function.
EndFunction


; Game update function for the user
Function game.update(dt)
	; Our game must override this dummy function.
EndFunction


; Game draw frame function for the user
Function game.draw()
	; Our game must override this dummy function.   
EndFunction


; Our game framework init function. Calls just user game setup function for now
; and starts double buffering.
Function game.init()
	game.load()
	BeginDoubleBuffer()   
EndFunction


; Basic game loop function for our framework.
Function game.loop(dt)
	If GetType(game.state.states[game.state.state].update) = #FUNCTION Then game.state.states[game.state.state].update(dt)
	If GetType(game.state.states[game.state.state].draw) = #FUNCTION Then game.state.states[game.state.state].draw()
	Flip()
EndFunction


; Set update rate for the game.
Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
EndFunction


; Get the current fps-rate.
Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
EndFunction


; Runs the game, handles calling the game loop and timing.
Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame update rate to game.updateRate
		WaitTimer(1, game.updateRate)
		; Get the actual time it took to update the frame.
		Local ftime = GetTimer(2)
          	ResetTimer(2)
          	; Delta time in seconds between the two last frames.
          	game.dt = game.updateRate / 1000 * ftime / game.updateRate
          	game.loop(game.dt)   
	Forever
EndFunction


Function game:go()
	self.init()
	self.run()
EndFunction



; Simple game state manager
game.state = {}
game.state.states = {}
game.state.states[0] = {} ; Default state
game.state.states[0].init = Function () EndFunction ; Initialized just once on game.load()
game.state.states[0].update = Function (dt) game.update(dt) EndFunction
game.state.states[0].draw = Function () game.draw() EndFunction
game.state.state = 0
game.state.ids = {}


; Registers new game state by name.
Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)   
EndFunction


; Remove game state from manager by state name.
Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend
EndFunction


; Clears all states from manager except default state.
Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
           
	self.ids = {}

EndFunction


; Set active game state by name
Function game.state:set(statename)
	self.state = statename
	If GetType(self.states[self.state].init) = #FUNCTION Then self.states[self.state].init()
EndFunction


; Get name of the current active game state
Function game.state:get()
	Return(self.state)
EndFunction
Now you don't anymore need to supply dummy functions to your game states, if your game state doesn't require that function.

Some feedback would be nice... ;)
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

jalih wrote:A little update...

Now you don't anymore need to supply dummy functions to your game states, if your game state doesn't require that function.
Ignore my last post... this approach don't seem to work...
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

jalih wrote:Ignore my last post... this approach don't seem to work...
Andreas,

In Lua, I can test if variable exist by just:

Code: Select all

test = {}
test.member = function () end

if test.member2 then
	DebugPrint("defined")
else
	DebugPrint("undefined")
end
In Hollywood this gives an error: Table field "member2" was not initialized!

Actually you can check if variable exist in Hollywood by testing it's not nil, but variable can't be a table field.

Is this a bug?

I was hoping to eliminate the need for dummy functions in my game template.
User avatar
Allanon
Posts: 744
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: My prototype game template with game state manager

Post by Allanon »

Hi Jalih,
I've written a little function for this:

Code: Select all

Function ItemExists(table, index)

   If GetType(table) = #NIL Then Return(False)
   If GetType(RawGet(table, index)) = #NIL Then Return(False) Else Return(True)

EndFunction
Example:

Code: Select all

Local test = { }
DebugPrint(ItemExists(test, "check"))

Local test.check = "Hello!"
DebugPrint(ItemExists(test, "check"))
----------------------------
[Allanon] Fabio Falcucci | GitHub (leaving) | Gitea (my new house) | My Patreon page | All my links
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

Allanon wrote:Hi Jalih,
I've written a little function for this:
Thanks! RawGet() function will do the trick... :D
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

Ok, Here is fixed game.hws

Code: Select all

;
; Game template module: game.hws
;

game = {}
game.updateRate = 20 ; Default to 20 millisec = 50 fps.
game.dt = 0
game.fps = 0


; Game setup function for the user.
Function game.load()
	; Our game must override this dummy function.
EndFunction


; Game update function for the user
Function game.update(dt)
	; Our game must override this dummy function.
EndFunction


; Game draw frame function for the user
Function game.draw()
	; Our game must override this dummy function.   
EndFunction


; Our game framework init function. Calls just user game setup function for now
; and starts double buffering.
Function game.init()
	game.load()
	BeginDoubleBuffer()   
EndFunction


; Basic game loop function for our framework.
Function game.loop(dt)
	If RawGet(game.state.states[game.state.state], "update") Then game.state.states[game.state.state].update(dt)
	If RawGet(game.state.states[game.state.state], "draw") Then game.state.states[game.state.state].draw()
	Flip()
EndFunction


; Set update rate for the game.
Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
EndFunction


; Get the current fps-rate.
Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
EndFunction


; Runs the game, handles calling the game loop and timing.
Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame update rate to game.updateRate
		WaitTimer(1, game.updateRate)
		; Get the actual time it took to update the frame.
		Local ftime = GetTimer(2)
		ResetTimer(2)
		; Delta time in seconds between the two last frames.
		game.dt = game.updateRate / 1000 * ftime / game.updateRate
		game.loop(game.dt)   
	Forever
EndFunction


Function game:go()
	self.init()
	self.run()
EndFunction



; Simple game state manager
game.state = {}
game.state.states = {}
game.state.states[0] = {} ; Default state
game.state.states[0].update = Function (dt) game.update(dt) EndFunction
game.state.states[0].draw = Function () game.draw() EndFunction
game.state.state = 0
game.state.ids = {}


; Registers new game state by name.
Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)   
EndFunction


; Remove game state from manager by state name.
Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend
EndFunction


; Clears all states from manager except default state.
Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
               
	self.ids = {}

EndFunction


; Set active game state by name
Function game.state:set(statename)
	self.state = statename
	If RawGet(self.states[self.state], "init") Then self.states[self.state].init()
EndFunction


; Get name of the current active game state
Function game.state:get()
	Return(self.state)
EndFunction

jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

I made an attempt to ensure that all the game state events will always be executed in correct order. It should not matter what is the current running event when setting the game state to a new state.

Correct event start execution order now is: init() -> draw() -> update()
After that game state loops: draw() and update() events

Code: Select all

;
; Game template module: game.hws
;

game = {}
game.updateRate = 20 ; Default to 20 millisec = 50 fps.
game.dt = 0
game.fps = 0


; Game setup function for the user.
Function game.load()
	; Our game must override this dummy function.
EndFunction


; Game update function for the user
Function game.update(dt)
	; Our game must override this dummy function.
EndFunction


; Game draw frame function for the user
Function game.draw()
	; Our game must override this dummy function.   
EndFunction


; Our game framework init function. Calls just user game setup function for now
; and starts double buffering.
Function game.init()
	game.load()
	BeginDoubleBuffer()   
EndFunction


; Basic game loop function for our framework.
Function game.loop(dt)
	If game.state.event = "init" Or game.state.event = "update"
		game.state.event = "draw"
		If RawGet(game.state.states[game.state.state], "draw") Then game.state.states[game.state.state].draw()
		Flip()	
	EndIf

	If game.state.event = "draw"
		game.state.event = "update"
		If RawGet(game.state.states[game.state.state], "update") Then game.state.states[game.state.state].update(dt)	
	EndIf
EndFunction


; Set update rate for the game.
Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
EndFunction


; Get the current fps-rate.
Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
EndFunction


; Runs the game, handles calling the game loop and timing.
Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame update rate to game.updateRate
		WaitTimer(1, game.updateRate)
		; Get the actual time it took to update the frame.
		Local ftime = GetTimer(2)
		ResetTimer(2)
		; Delta time in seconds between the two last frames.
		game.dt = game.updateRate / 1000 * ftime / game.updateRate
		game.loop(game.dt)   
	Forever
EndFunction


Function game:go()
	self.init()
	self.run()
EndFunction



; Simple game state manager
game.state = {}
game.state.states = {}
game.state.states[0] = {} ; Default state
game.state.states[0].update = Function (dt) game.update(dt) EndFunction
game.state.states[0].draw = Function () game.draw() EndFunction
game.state.state = 0
game.state.event = "init" ; Start in init event.
game.state.ids = {}


; Registers new game state by name.
Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)   
EndFunction


; Remove game state from manager by state name.
Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend
EndFunction


; Clears all states from manager except default state.
Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
	       
	self.ids = {}

EndFunction


; Set active game state by name
Function game.state:set(statename)
	self.state = statename
	self.event = "init"
	If RawGet(self.states[self.state], "init") Then self.states[self.state].init()
EndFunction


; Get the name of a currently active game state
Function game.state:get()
	Return(self.state)
EndFunction


; Get the currently active event of a game state
Function game.state:getEvent()
	Return(self.event)
EndFunction
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: My prototype game template with game state manager

Post by jalih »

Ok, here is another simple example. It just displays a clock and elapset time count since program was started. Use keys "1" and "2" to change between states.

Code: Select all

;
; Simple clock in Hollywood
;
; Keys "1" and "2" change states between clock and elapset time state  
;

@DISPLAY {Width = 240, Height = 100, Borderless = True}



; Include game framework
@INCLUDE "game.hws"

; Include game states
@INCLUDE "elapset.hws" ; Include elapset time state.



; Setup
Function game.load()
	game.state:register("elapset", elapset) ; register elapset time state
	delay = 0
	time = 0
	game:setUpdateRate(60) ; set update rate
	SetFont(#SANS, 32)
	clock$ = GetTime(True)	
EndFunction


; Draw
Function game.draw()
	Cls()
	TextOut(#CENTER, #CENTER, clock$)
EndFunction


; Update
Function game.update(dt)
	; Update time in seconds.
	time = time + dt
	
	; Update clock$ string holding time at once per second.
	delay = delay + dt
	If delay >= 1
		clock$ = GetTime(True)
		delay = 0
	EndIf
	
	; Check keys: "ESC" quits and "2" changes state to elapset time mode.
	If IsKeyDown("ESC") Then End
	If IsKeyDown("2") Then game.state:set("elapset") 	
EndFunction



; Go!
game:go()

Code: Select all

;
; Elapset time state: elapset.hws
;

elapset = {}


Function elapset.init()
	; Calculate time in hours, minutes and seconds.
	; Required, so we won't miss the states first draw event.
	h = Int(time / 3600)
	m = Int((time % 3600) / 60)
	s = Int((time % 3600) % 60)
EndFunction


; Draw
Function elapset.draw()
	Cls()
	TextOut(#CENTER, #CENTER, PadNum(h, 2)  ..":" ..PadNum(m, 2)  ..":" ..PadNum(s, 2))	
EndFunction
	

; Update
Function elapset.update(dt)
	; Update time in seconds.
	time = time + dt
	
	; Calculate time in hours, minutes and seconds.
	h = Int(time / 3600)
	m = Int((time % 3600) / 60)
	s = Int((time % 3600) % 60)
	
	; Check keys: "ESC" quits and "1" changes state back to default state 0 (display clock).
	If IsKeyDown("ESC") Then End 
	If IsKeyDown("1")
		clock$ = GetTime(True)
		game.state:set(0)
	EndIf
EndFunction
Post Reply