In my latest projects I've been implementing an event-based system into most aspects of the engine through lua. A good example of its use was when I added my physics system; my main loop raises an event called 'onTick', which is called every loop and it passes the elapsed time for the previous loop and so I can bind custom functionality to occur every loop. At first I did the most basic:
g_application:addEventListener( "onTick", function( eventListener, eventOwner, elapsed )
local timeStep = elapsed
local velocityIterations = 2
local positionIterations = 1
g_physicsWorld:Step( timeStep, velocityIterations, positionIterations )
g_physicsWorld:ClearForces()
end )
which simply steps the physics every loop, but this isn't appropriate for accurate physics, so I later needed fixed-time-steps.
Which was added in no time without any recompilation:
g_application:addEventListener( "onTick", function( eventListener, eventOwner, elapsed )
local timeStep = physicsStep
local velocityIterations = 2
local positionIterations = 1
-- Don't pile up too many frames
counter = counter + elapsed
if counter >= physicsStep * maxFrameOverflow then
counter = physicsStep * maxFrameOverflow
end
-- Step
while counter >= physicsStep do
counter = counter - physicsStep
g_physicsWorld:Step( timeStep, velocityIterations, positionIterations )
g_physicsWorld:ClearForces()
luabind.raiseEvent( g_application, "onPhysicsStep" )
end
end )
This allows me to use the same game engine for different games as I can just choose to not include my 'physics.lua'(partially shown above), whereas normally I'd have to have two code bases for each project.
It pretty much allows me to make everything modular, as I have an '/autoexec' folder whose scripts get executed at app start, some other examples of scripts I can just drop into my game:
Screenshot key:
g_application:addEventListener( "onTick", function( eventListener, eventOwner )
if g_application.input:button( "F1" ).pressed then
g_application:takeScreenshot()
end
end )
Automatically play and cycle through any music in the '/Music' directory:
local var = {}
local func = {}
var.music = {}
var.musicObj = nil
-- Load all music
g_util.getFilesInDirectory( gdk.File.find( "Music" ), {"ogg"}, function( file ) table.insert( var.music, gdk.File( file ) ) end )
var.trackCount = table.getn(var.music)
luabind.log( "Found " .. tostring(var.trackCount) .. " tracks of music" )
-- Hooks
g_application:addEventListener( "onTick", function( eventListener, eventOwner, elapsed )
if not var.musicObj or fslSoundIsPlaying( var.musicObj ) == false then
--func.changeTrack()
end
end )
-- Functions
func.changeTrack = function()
-- Unload existing?
if var.musicObj then
fslFreeSound( var.musicObj, true )
end
-- Make sure we have music to load
if var.trackCount > 0 then
local trackIndex = math.random( 1, var.trackCount )
local trackFile = var.music[trackIndex]
var.musicObj = fslStreamSound( trackFile.url )
fslSoundSetGain( var.musicObj, 0.25 )
fslSoundPlay( var.musicObj )
luabind.log( "playing track " .. trackFile.url )
end
end
Manage the rendering pipeline(I could later expand this to handle fullscreen-shaders):
g_application:addEventListener( "onPreDraw", function( eventListener, eventOwner )
g_mainCamera:drawScene()
luabind.raiseEvent( g_application, "onDrawFrame" )
end )
And so on, my actual main loop code thus becomes ridiculously simple, it pretty much just calculates the elapsed time, raises the onTick event and checks for device loss and such. This makes debugging my code so much easier as I have much less to worry about.
My in game entities are virtually the same, you spawn one by name and it loads a script for it which can do nothing, or it can hook into various events and do something useful. Because the meat of how they work is in the scripts I don't have to store much information about them in my main engine, which means my data structures are far smaller and deleting entities isn't some complex process like it usually is.