Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
Skeleton game code — Gideros Forum

Skeleton game code

This code has a background 'game' (just a square moving), a title front end, an options screen, a play game screen and a game over screen. The user can use mouse, keyboard or gamepad controllers. It hopefully will help people make games that use the scenemanager and are compatible with both touch and controllers. If you look at the code then you should see how to access the controller fire, up, down, left and right (fudlr) and it's debounced value. Use the attractmode flag in the game to see if you should be in self play mode, game mode or game over mode.

The project is also here: https://www.dropbox.com/s/97nyg02dk25n3q2/skeleton.zip?dl=0
-- set the background to black (the number is a hexadecimal RRGGBB colour value)
application:setBackgroundColor(0x000000)
 
-- setup so lots of common functions are local and easy to use
local sub,len,upper,lower,format,gsub,find,rep,ascii=string.sub,string.len,string.upper,string.lower,string.format,string.gsub,string.find,string.rep,string.byte
local random,floor,sin,cos,tan,abs,atan2,sqrt=math.random,math.floor,math.sin,math.cos,math.tan,math.abs,math.atan2,math.sqrt
local remove,insert=table.remove,table.insert
 
-- include support for easing functions, the scene manager and joypad controllers
require "easing"
require "scenemanager"
pcall(function() require "controller" end)
 
local frameRate=application:getFps()
local frameCounter,skip,oldFps,oldTextureUsage=0,0,0,0
local fudlr,oldFudlr,debouncedFudlr=0,0,0 -- fire, up, down, left, right
local paused=false
 
local backgroundCount,oldBackgroundCount=0,0
 
local attractMode=1
local music,soundfx=true,true
local level=1
 
local sprites={}
sprites[1]={x=0,y=0,sprite=Pixel.new(0xff4090,0.95,10,10)} -- create a pink 95% transparent 10x10 pixel sprite
 
-- add my sprite to the screen
stage:addChild(sprites[1].sprite)
 
-- this function will be called every game frame
function gameLoop(e)
	-- e is a table that holds nice event information
 
	-- *** first some housekeeping! ***
 
	-- make sure garbage is collected regularly, but not too much in one
	collectgarbage("step")
	--print(collectgarbage("count")*1024)
 
	-- if texture usage has changed then display new texture usage
	local temp=(application:getTextureMemoryUsage()/1024)
	if temp~=oldTextureUsage then
		oldTextureUsage=temp
		print("Texture usage: "..temp.."MB")
	end
 
	-- if old FPS is different from the current one by 10 frames then display new FPS
    local temp=(1//e.deltaTime)
	if abs(temp-oldFps)>10 then
		oldFps=temp
		print("FPS: "..temp)
	end
 
	-- skip is a variable you can use to even things out of your game isn't running exactly on sync
	skip=e.deltaTime*frameRate
 
	-- frameCounter is added to each frame
	frameCounter+=skip
 
	-- *** here you can put code that is used for your main game ***
 
	-- check 'attractMode', if -1 then game over, if 0 then in game, if 1 then self play demo mode
 
	if backgroundCount~=oldBackgroundCount then
		oldBackgroundCount=backgroundCount
		print("background count: "..backgroundCount)
	end
 
	local s=sprites[1]  -- s now is pointing to sprites[1], my first sprite
 
	-- move the x across the screen, move the y in a sine wave pattern
	s.x+=5*skip
	if s.x>320 then s.x=0 end
	s.y=240+(sin(frameCounter/30)*200)
 
	s.sprite:setPosition(s.x,s.y) -- update the actual sprite position
 
 
end
 
-- this function runs continuously unless you exit it
function task()
	while true do
		if not paused then
			backgroundCount+=1
-- you can optionally give up the task processing time, eg to not execute for 10 seconds use 'Core.yield(10)', etc
			Core.yield(10)
		end
	end
end
 
-- handy functions for controlling menus and joypads
function addMenu(s,text,offset,funct)
	if s.menu==nil then s.menu={} end
	local i={}
	i.sprite=TextField.new(nil,text)
	i.sprite:setTextColor(0xffffff)
	i.sprite:setScale(3)
	i.sprite:setAnchorPoint(0.5,0.5)
	i.sprite:setPosition(160,offset+(#s.menu*40))
	i.funct=funct
	s:addChild(i.sprite)
	s.menu[#s.menu+1]=i
end
 
function setMenu(s,item,text)
	local i=s.menu[item]
	i.sprite:setText(text)
	i.sprite:setAnchorPoint(0.5,0.5)
end
 
function onOff(f)
	if f then return "On" else return "Off" end
end
 
function selectMenu(s,item)
	if item~=s.item then
		s.item=item
		for loop=1,#s.menu do
			if loop==item then
				s.menu[loop].sprite:setColorTransform(1,0.7,0)
			else
				s.menu[loop].sprite:setColorTransform(0,0.2,1)
			end	
		end
	end
end
 
function processMenu(s)
	if debouncedFudlr~=0 then
		local item=s.item
		if debouncedFudlr&0b01000~=0 then item-=1 end
		if debouncedFudlr&0b00100~=0 then item+=1 end
		if item<1 then item+=#s.menu elseif item>#s.menu then item-=#s.menu end
		selectMenu(s,item)
		if debouncedFudlr&0b10000~=0 and s.menu[item].funct then s.menu[item].funct(s) end
	end
end
 
function processMouse(s,e)
	for loop=1,#s.menu do
		if s.menu[loop].sprite:hitTestPoint(e.x,e.y,true) then
			selectMenu(s,loop)
			if e.type=="mouseUp" and s.menu[loop].funct then s.menu[loop].funct(s) end
		end
	end
end
 
function processPads()
	debouncedFudlr=fudlr&(~oldFudlr)
	oldFudlr=fudlr
end
 
-- *** scenemanager game overlays ***
 
Frontend=gideros.class(Sprite)
function Frontend:init(t)
	attractMode=1 -- turn on self play demo mode
	back=false
 
	self.done=false
 
-- add scene title
	local t=TextField.new(nil,"My Game Title")
	t:setTextColor(0x8000ff)
	t:setScale(3)
	t:setAnchorPoint(0.5,0.5)
	t:setPosition(160,100)
	self:addChild(t)
 
-- add game menu
	self.item=0
	addMenu(self,"Start",240,self.play)
	addMenu(self,"Options",240,self.options)
	addMenu(self,"Quit",240,self.quit)
	selectMenu(self,1)
 
	self:addEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
	self:addEventListener(Event.MOUSE_MOVE,self.onMouse,self)
	self:addEventListener(Event.MOUSE_HOVER,self.onMouse,self)
	self:addEventListener(Event.MOUSE_UP,self.onMouse,self)
	self:addEventListener("exitBegin",self.onTransitionOutBegin,self)
	self:addEventListener("exitEnd",self.onTransitionOutEnd,self)
end
 
function Frontend:onMouse(e)
	if not self.done then processMouse(self,e) end
end
 
function Frontend:options()
	if not self.done and sceneManager:changeScene("options",0.5,transitions[random(#transitions)]) then self.done=true end
end
 
function Frontend:play()
	if not self.done and sceneManager:changeScene("play",0.5,transitions[random(#transitions)]) then self.done=true end
end
 
function Frontend:quit()
	application:exit()
end
 
function Frontend:onTransitionOutBegin()
	self:removeEventListener(Event.MOUSE_UP,self.onMouse,self)
	self:removeEventListener(Event.MOUSE_MOVE,self.onMouse,self)
	self:removeEventListener(Event.MOUSE_HOVER,self.onMouse,self)
end
function Frontend:onTransitionOutEnd()
	self:removeEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
end
 
function Frontend:onEnterFrame(Event)
	processPads()
	if back then self:quit() else processMenu(self) end
 
end
 
Options=gideros.class(Sprite)
function Options:init(t)
	attractMode=1 -- turn on self play demo mode
	back=false
 
	self.done=false
 
-- add scene title
	local t=TextField.new(nil,"Game Options")
	t:setTextColor(0x30ff80)
	t:setScale(3)
	t:setAnchorPoint(0.5,0.5)
	t:setPosition(160,100)
	self:addChild(t)
 
-- add options menu	
	self.item=0
	addMenu(self,"",190,self.level)
	addMenu(self,"",190,self.music)
	addMenu(self,"",190,self.fx)
	addMenu(self,"Back",190,self.quit)
	selectMenu(self,#self.menu)
 
	self:level(true)
	self:music(true)
	self:fx(true)
 
	self:addEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
	self:addEventListener(Event.MOUSE_MOVE,self.onMouse,self)
	self:addEventListener(Event.MOUSE_HOVER,self.onMouse,self)
	self:addEventListener(Event.MOUSE_UP,self.onMouse,self)
	self:addEventListener("exitBegin",self.onTransitionOutBegin,self)
	self:addEventListener("exitEnd",self.onTransitionOutEnd,self)
 
end
 
function Options:onMouse(e)
	if not self.done then processMouse(self,e) end
end
 
function Options:level(justShow)
	if not justShow then
		level+=1
		if level>9 then level=1 end
	end
	setMenu(self,1,"Level: "..level)
end
 
function Options:music(justShow)
	if not justShow then
		if music then music=false else music=true end
	end
	setMenu(self,2,"Music: "..onOff(music))
end
 
function Options:fx(justShow)
	if not justShow then
		if soundfx then soundfx=false else soundfx=true end
	end
	setMenu(self,3,"Sound FX: "..onOff(soundfx))
end
 
function Options:quit()
	if not self.done and sceneManager:changeScene("frontend",0.5,transitions[random(#transitions)]) then self.done=true end
end
 
function Options:onTransitionOutBegin()
	self:removeEventListener(Event.MOUSE_UP,self.onMouse,self)
	self:removeEventListener(Event.MOUSE_MOVE,self.onMouse,self)
	self:removeEventListener(Event.MOUSE_HOVER,self.onMouse,self)
end
function Options:onTransitionOutEnd()
	self:removeEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
end
 
function Options:onEnterFrame(Event)
	processPads()
	if back then self:quit() else processMenu(self) end
end
 
Play=gideros.class(Sprite)
function Play:init(t)
	attractMode=0 -- game playng mode
 
	self.done=false
	self.playerScore=0
	self.timer=60*15
 
-- add score text
	self.score=TextField.new(nil,"")
	self.score:setTextColor(0xffffff)
	self.score:setScale(2)
	self.score:setAnchorPoint(0,0)
	self.score:setPosition(10,20)
	self:addChild(self.score)
 
-- add countdown text
	self.countdown=TextField.new(nil,"")
	self.countdown:setTextColor(0xffffff)
	self.countdown:setScale(2)
	self.countdown:setAnchorPoint(1,0)
	self.countdown:setPosition(310,20)
	self:addChild(self.countdown)
 
	self:updateScore()
	self:updateCountdown()
 
	self:addEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
	self:addEventListener("exitEnd",self.onTransitionOutEnd,self)
end
 
function Play:updateScore()
	self.score:setText("Score: "..self.playerScore)
	self.score:setAnchorPoint(0,0)
end
 
function Play:updateCountdown()
	self.countdown:setText("Countdown: "..self.timer)
	self.countdown:setAnchorPoint(1,0)
end
 
function Play:quit()
	if not self.done and sceneManager:changeScene("gameover",0.5,transitions[random(#transitions)]) then self.done=true end
end
 
function Play:onTransitionOutEnd()
	self:removeEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
end
 
function Play:onEnterFrame(Event)
	processPads()
 
	if debouncedFudlr&0b10000~=0 then
		self.playerScore+=1
		self:updateScore()
	end
 
	self.timer-=1
	if self.timer<0 then
		self:quit()
	else
		self:updateCountdown()
	end
 
end
 
GameOver=gideros.class(Sprite)
function GameOver:init(t)
	attractMode=-1 -- game over mode
	back=false
 
	self.done=false
 
-- add scene title
	local t=TextField.new(nil,"Game Over")
	t:setTextColor(0x30ffff)
	t:setScale(3)
	t:setAnchorPoint(0.5,0.5)
	t:setPosition(160,100)
	self:addChild(t)
 
-- add game over menu	
	self.item=0
	addMenu(self,"Play Again",190,self.play)
	addMenu(self,"Quit",190,self.quit)
	selectMenu(self,#self.menu)
 
	self:addEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
	self:addEventListener(Event.MOUSE_MOVE,self.onMouse,self)
	self:addEventListener(Event.MOUSE_HOVER,self.onMouse,self)
	self:addEventListener(Event.MOUSE_UP,self.onMouse,self)
	self:addEventListener("exitBegin",self.onTransitionOutBegin,self)
	self:addEventListener("exitEnd",self.onTransitionOutEnd,self)
 
end
 
function GameOver:onMouse(e)
	if not self.done then processMouse(self,e) end
end
 
function GameOver:play()
	if not self.done and sceneManager:changeScene("play",0.5,transitions[random(#transitions)]) then self.done=true end
end
 
function GameOver:quit()
	if not self.done and sceneManager:changeScene("frontend",0.5,transitions[random(#transitions)]) then self.done=true end
end
 
function GameOver:onTransitionOutBegin()
	self:removeEventListener(Event.MOUSE_UP,self.onMouse,self)
	self:removeEventListener(Event.MOUSE_MOVE,self.onMouse,self)
	self:removeEventListener(Event.MOUSE_HOVER,self.onMouse,self)
end
function GameOver:onTransitionOutEnd()
	self:removeEventListener(Event.ENTER_FRAME,self.onEnterFrame,self)
end
 
function GameOver:onEnterFrame(Event)
	processPads()
	if back then self:quit() else processMenu(self) end
end
 
sceneManager = SceneManager.new({
	["frontend"]=Frontend,
	["play"]=Play,
	["options"]=Options,
	["gameover"]=GameOver,
})
 
transitions = {
	SceneManager.moveFromLeft,
	SceneManager.moveFromRight,
	SceneManager.moveFromBottom,
	SceneManager.moveFromTop,
	SceneManager.moveFromLeftWithFade,
	SceneManager.moveFromRightWithFade,
	SceneManager.moveFromBottomWithFade,
	SceneManager.moveFromTopWithFade,
	SceneManager.overFromLeft,
	SceneManager.overFromRight,
	SceneManager.overFromBottom,
	SceneManager.overFromTop,
	SceneManager.overFromLeftWithFade,
	SceneManager.overFromRightWithFade,
	SceneManager.overFromBottomWithFade,
	SceneManager.overFromTopWithFade,
	SceneManager.fade,
	SceneManager.crossFade,
	SceneManager.flip,
	SceneManager.flipWithFade,
	SceneManager.flipWithShade,
}
stage:addChild(sceneManager)
 
-- *** joypad controller support ***
if controller then
	controller:addEventListener(Event.KEY_DOWN, function(e)
		--print("Button Down ", e.keyCode)
		local k=e.keyCode
		if k==KeyCode.BUTTON_BACK then back=true 
		elseif k==KeyCode.BUTTON_B or k==KeyCode.BUTTON_A then fudlr=fudlr|0b10000
		elseif k==KeyCode.DPAD_UP then fudlr=fudlr|0b01000
		elseif k==KeyCode.DPAD_DOWN then fudlr=fudlr|0b00100
		elseif k==KeyCode.DPAD_LEFT then fudlr=fudlr|0b00010
		elseif k==KeyCode.DPAD_RIGHT then fudlr=fudlr|0b00001
		end
	end)
 
	controller:addEventListener(Event.KEY_UP, function(e)
		local k=e.keyCode
		if k==KeyCode.BUTTON_B or k==KeyCode.BUTTON_A then fudlr=fudlr&0b01111
		elseif k==KeyCode.DPAD_UP then fudlr=fudlr&0b10111
		elseif k==KeyCode.DPAD_DOWN then fudlr=fudlr&0b11011
		elseif k==KeyCode.DPAD_LEFT then fudlr=fudlr&0b11101
		elseif k==KeyCode.DPAD_RIGHT then fudlr=fudlr&0b11110
		end
	end)
 
	controller:addEventListener(Event.RIGHT_JOYSTICK, function(e)
		--print("Player: ",e.playerId)
		--print("RIGHT_JOYSTICK:", "x:"..e.x, "y:"..e.y, "angle:"..e.angle, "strength:"..e.strength)
	end)
 
	controller:addEventListener(Event.LEFT_JOYSTICK, function(e)
		--print("Player: ",e.playerId)
		--print("LEFT_JOYSTICK:", "x:"..e.x, "y:"..e.y, "angle:"..e.angle, "strength:"..e.strength)
	end)
 
	controller:addEventListener(Event.RIGHT_TRIGGER, function(e)
	--	print("Player: ",e.playerId)
	--	print("RIGHT_TRIGGER:", "strength:"..e.strength)
	end)
 
	controller:addEventListener(Event.LEFT_TRIGGER, function(e)
	--	print("Player: ",e.playerId)
	--	print("LEFT_TRIGGER:", "strength:"..e.strength)
	end)
 
	controller:addEventListener(Event.CONNECTED, function(e)
	end)
 
	controller:addEventListener(Event.DISCONNECTED, function(e)
	end)
end
 
-- *** keyboard support ***
stage:addEventListener(Event.KEY_DOWN,function(e)
	local k=e.keyCode
	if k==KeyCode.BACK or k==KeyCode.ESC then back=true
	elseif k==KeyCode.SPACE or k==KeyCode.ENTER then fudlr=fudlr|0b10000
	elseif k==KeyCode.UP then fudlr=fudlr|0b01000
	elseif k==KeyCode.DOWN then fudlr=fudlr|0b00100
	elseif k==KeyCode.LEFT then fudlr=fudlr|0b00010
	elseif k==KeyCode.RIGHT then fudlr=fudlr|0b00001
	end
end)
 
stage:addEventListener(Event.KEY_UP,function(e)
	local k=e.keyCode
	if k==KeyCode.SPACE or k==KeyCode.ENTER then fudlr=fudlr&0b01111
	elseif k==KeyCode.UP then fudlr=fudlr&0b10111
	elseif k==KeyCode.DOWN then fudlr=fudlr&0b11011
	elseif k==KeyCode.LEFT then fudlr=fudlr&0b11101
	elseif k==KeyCode.RIGHT then fudlr=fudlr&0b11110
	end
end)
 
stage:addEventListener(Event.ENTER_FRAME,gameLoop)
 
-- take over automatic garbage collection, do it in small steps.  Increase/decrease the amount depending on your game.
collectgarbage("setstepmul",1000)
collectgarbage()
 
-- start the first initial 'scene'
sceneManager:changeScene("frontend")
 
-- a task can be used to run a non-event driven type of game
Core.asyncCall(task) -- comment this out if you don't want to write your game in task()</pre>
Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
https://deluxepixel.com
+1 -1 (+5 / -0 )Share on Facebook

Comments

  • SinisterSoftSinisterSoft Maintainer
    edited January 2020
    My style may be different than what a few people use. I tend to have the game running all the time, even behind the game's title screen - with a 'state machine' to pause/hide/reset it on parts of the 'frontend attract mode'. It makes the game have a retro arcade feel as that's what they do in arcade games.

    The unified controller code (so the keyboard and gamepad controls are merged) makes it easy to make it work on things like AndroidTV and also test in the player using the board or a gamepad. The debounce code is a trick I used to use back in the 8-bit days. Having it in a fudlr bitmap means I can also look up the directions in a lookup table to get x and y movement. When I code enemies I also make their 'brains' use fudlr so that to test them I can patch in a real joypad controller and play as if I'm the enemy.

    Likes: plicatibu

    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
    +1 -1 (+1 / -0 )Share on Facebook
  • thank you for sharing. I like the way you make the menu. I have to check your state machine too. It's great to see how gideros pros code!

    Likes: SinisterSoft

    my growING GIDEROS github repositories: https://github.com/mokalux?tab=repositories
    +1 -1 (+1 / -0 )Share on Facebook
  • Thanks. My style may suit everyone but it works for me. :)

    Not exactly pro though, I used to make games for a living back in the late 80s/early 90s - but now only serious software makes me any real cash. I make the games to show my kids how we used to do things - if they make money then that's a bonus.

    I do know people though (using Gideros) making a good living making games. :)
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
    +1 -1 (+3 / -0 )Share on Facebook
  • Always good stuff.

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • Regardless of style, examples of how to use the code is a great way to ease new developers into using the framework. And it provides something tangible to look at prior to doing their own work. Thanks for sharing!
    +1 -1 (+2 / -0 )Share on Facebook
Sign In or Register to comment.