Here’s a follow up to the previous post on game loops. The game loop works with actor objects. Actors are stored in an array. The game loop sends it’s actors an update message each frame, and passes the delta time.
Here I have created a Lua module to handle the game and appropriately named it: game_loop. Using a module gives us three advantages.
First, the code is easily portable. You can copy this Lua file/Module into another project and use the game there.
Second, the code is isolated. This makes it easier to edit the game loop if necessary.
Last, Lua modules are only loaded once. If we load this file with require() in file A, and then load it again with require() in file B, file B sees the same game_loop with the same variable values. It doesn’t load a new unique instance. In effect Lua modules act like singleton objects. For the game loop this is perfect. We will only need one game loop, and many other modules will want to access it.
Here’s the game_loop module. Move all of the code here into a file named game_loop.lua.
---------------------------------------------------
--
-- game_loop.lua
--
---------------------------------------------------
local M = {}
---------------------------------------------------
local MSPF = 1000 / display.fps
local actors_array = {}
local last_ms = 0
local isRunning = false
function add( obj )
if table.indexOf( actors_array, obj ) == nil then
actors_array[#actors_array+1] = obj
end
end
M.add = add
function remove( obj )
local index = table.indexOf( actors_array, obj )
if index ~= nil then
table.remove( actors_array, index )
end
end
M.remove = remove
function enterFrame( event )
local ms = event.time
local dt = MSPF / ( ms - last_ms )
last_ms = ms
for i = #actors_array, 1, -1 do
local actor = actors_array[i]
actor:update( dt )
end
end
function run()
if isRunning == false then
Runtime:addEventListener( "enterFrame", enterFrame )
isRunning = true
end
end
M.run = run
function pause()
if isRunning then
Runtime:removeEventListener( "enterFrame", enterFrame )
isRunning = false
end
end
M.pause = pause
---------------------------------------------------
return M
Load game_loop.lua with require(“game_loop”). When using require() do not include the .lua file extension.
The game_loop object/table has four methods.
game_loop.add( obj ) — Adds obj to the game loop, obj must implement
the update(dt) method.
game_loop.remove( obj ) — Removes obj from the game loop.
game_loop.run() — Starts the game loop by adding an enterFrame listener.
game_loop.pause() — pauses the game loop by removing the enterFrame listener.
A game_loop keeps track of objects, we’ll refer to these as actors, in an array/table. Each frame game_loop sends each of the actors an update() message and passes the delta time (dt).
To use this in a game all actor objects must implement the update() method.
Actors are added to the game_loop using game_loop.add( actor ). When an actor
is to be removed from the display it should also be removed from the game_loop using
game_loop.remove( actor )
The game_loop can be stopped and started with:
game_loop.run()
game_loop:pause()
To test this you can add the following code to main.lua in a new project. The code below first creates a new game loop. Touching the screen creates a new box. Boxes are added to the game loop that sends them update messages each frame. Touching a box removes it from the game loop, and then from the screen.
At the bottom I created two buttons to to run and pause the game loop.
-- Import the game loop module
local game_loop = require("game_loop")
-- Start the game loop
game_loop.run()
-- make green boxes
local function make_green_box()
local box = display.newRect( 0, 0, 40, 40 )
box:setFillColor( 0, 1, 0 )
box.x = 0
box.y = math.random( 0, 480)
box.speed = math.random() * 3
function box:update( dt ) -- receive dt here as a parameter.
self.x = self.x + self.speed * dt -- Use dt, multiply by pixels per frame (speed)
if self.x > 320 then
self.x = 0
end
end
return box
end
-- Tap a box to remove a box
local function remove_box( event )
if event.phase == "began" then
-- Remove the box from the loop
local box = event.target
game_loop.remove( box ) -- Remove box from loop
display.remove( box ) -- Remove box from display
end
return true
end
-- Tap the screen to add a box
local function add_box( event )
if event.phase == "began" then
local box = make_green_box() -- Make a new box
box.x = event.x -- Position box
box.y = event.y
box:addEventListener( "touch", remove_box ) -- Add a touch event
game_loop.add( box ) -- Add box to the game loop
end
return true
end
Runtime:addEventListener( "touch", add_box )
local run_button = display.newRect( 31, 21, 60, 40 )
run_button:setFillColor( 0, 200/255, 0 )
local pause_button = display.newRect( 92, 21, 60, 40 )
pause_button:setFillColor( 200/255, 0, 0 )
local function touch_run( event )
if event.phase == "began" then
game_loop.run()
end
return true
end
local function touch_pause( event )
if event.phase == "began" then
game_loop.pause()
end
return true
end
run_button:addEventListener( "touch", touch_run )
pause_button:addEventListener( "touch", touch_pause )
To expand on what’s here, you could imagine every actor in the actor_array as a table. These
actor tables could contain properties describing how the game_loop should treat each
actor. A simple idea might be to give each a type, and active property. As is the game
loop can be paused and run. Using this idea you could pause or run actors in groups.