Please click on a title to expand or collapse the information on it.
Recent
HMTL5
HTML5 Game Engine
This is the project I use for my Html5 class instruction, which is focused on Advanced Gameplay, 2D physics, and Server/client game systems.
Less recent...
Unfortunately the rest of these samples are quite old, as it's difficult to get code samples from my most recent projects. I've been doing a lot of C++ engineering and gameplay work that is not represented here.
Networking
Froggle Networking Engine
This is some of the code from the networking engine I used in the student game project "Froggle". It was on of the first major systems we had in and complete, and early on we had an extremely fun and solid multiplayer game. Our hopes to get into the IGF contest drove us to focus on creating a solid single player experience, so multiplayer wasn't a feature we shipped with in the final version.
The basis of this framework is credited to Jonathan Hass from Microsoft (now at Google) while teaching the CS261 Advanced Networking class at DigiPen. The class was cancelled halfway through, so the implementation was left to do as an independent study. This system was designed to operate both on the PC and the XBox, and I hope in the future I have a chance to do multiplayer gaming on a console system.
- System Overview
- Serialization Layer
- Objects that derive from ISerializable can load or save data to any object that derives from IStreamable.
- Game Messages are serialized into Memory Streams, which is essentially a payload.
- Transport Layer
- Acts as a Winsock wrapper, and provides client management, reliable UPD, Quality of Service, and Voice transfer.
- This layer contains several classes: Transport manager, Packets, Payloads, Peers, and the Status Receiver interface.
- Messaging Layer
- This layer contains: Message Handlers, Message Manager/Pump, Message classes and factories
- Session Layer
- The only implemented status receiver
- Provides server/client connections, matchmaking, etc.
- Source Files:
- Messaging Layer
- MessageFactory.h
- MessageFactory.cpp
- MessageFactory_impl.h
- MessageHandler.h
- MessageManager.h
- MessageManager.cpp
- Messages.h
- MessageTypes.h
- MessageTypes.cpp
- Transport Layer
- Zip File:
- FroggleNetworkingSample.zip
Lua Game Object management
Lua Code Samples
In the battle of drawing a line between Game Engine and Application, Lua is always best placed on both sides. Meaning you can't just have Lua running logic without building and shaping a framework to it yourself.
I've worked on and have seen many projects take lots of different approaches to integrating Lua. Game Objects having script components; scripts creating their parent game objects; using coroutines or using update functions; running files as functions with unnamed parameters (a fun one); and so on
I've come to prefer letting the scripts be in charge of their game objects and using coroutines to run logic on them. I look at Lua as not a list of scripts, but a web of scripts with each thread potentially running and updating any number more. Why do I like this?
- I like being able to run multiple coroutines on a a single game object, without having to create multiple C++ LuaScript objects/components
- I especially like being able to create new functions on the fly within a closure, and sending them off to automatically update
- I like handling script errors within the calling script. If you just call these as functions and they have an error, the calling script itself would be the one that errors and stop (undesirable).
Downsides? Probably a few.
- Each of these autonomous coroutines in Lua could be autonomous LuaScript objects/components in C++, and then the error handling would still be on a per lua thread (highly desirable so that an object or all of lua comes to a halt when something goes wrong). But writing a new function within a closure in Lua and passing that up to a new C++ LuaScript seems like a waste when I can just add it to a table in the same thread.
- Which has better performance, calling lots of functions on tables, or resuming lots of coroutines?
Here's a code snippet for updating a list of coroutines while checking for errors
function UpdateCoroutineList( coroutineList, elapsedTime, running ) -- Go through the list of coroutines for k,v in ipairs( coroutineList ) do -- Check if they're dead and need to be removed from the list if coroutine.status(v) == "dead" then table.remove( coroutineList, k ) else -- Resume them with the given parameters local success, error = coroutine.resume( v, elapsedTime, running ) -- Handle any errors, and also print out the callstack if success == false then print( "Error resuming coroutine: " .. tostring(error) ) print( debug.traceback() ) table.remove( coroutineList, k ) end end end end
Here's my version of creating new object type defifitions (I've evolved this from things I've done plus what I've seen other DigiPen students do). It assumes that there's only one C++ Game Object class used for all types of in-game objects.
-- This function takes in a constructor (more like configuration function), -- and returns a class definition table / entity factory function (see __call metamethod) local function CreateNewEntityDefinition( ctor, typename ) local newTypeDefinition = { } -- Set the [] or . operator to be itself newTypeDefinition.__index = newClassDefinition -- Save the type name for future debugging newTypeDefinition.typename = typename -- Let's keep track of how many objects of this type get created. -- Make this newTypeDefinition.XXX if you want access outside -- the Print Debug function local objectCount = 0 local objectList = { } newTypeDefinition.PrintDebug = function() print( "Object type", typename ) print( "Total alive", objectCount ) for k,v in ipairs( objectList ) do print( v ) end end -- create a table, this will be set as the newTypeDefinition's metatable local mt = { } -- overload this table's () operator - hereby refered to as "functables" -- allows this table to be called as a function -- We'll use it to use this table as a factory function for the given type -- Create a closure that uses the constructor function parameter -- This is where all new instances of the entity type get created mt.__call = function( class_tbl, gameObj, ... ) -- FYI, class_tbl == newTypeDefinition, because of how the () operator works -- Wrapping the object in a table here allows us to have two levels of metadata: -- 1) This obj is a table in lua, so it can have new variables and functions added willy-nilly -- (the best part of Lua, right?) while still being able to have the same metatable of this type definition. -- 2) The gameObj parameter is the C++ GameObject, which can have metatable assigned to it giving it access to -- whatever library you bind through whatever method you please. I've been using Lunar lately (along with my -- own modified lite version below). local newObj = { } setmetatable( newObj, newClassDefinition ) newObj.parentObj = gameObj newObj.isDead = false -- A shortcut for registering a coroutine on this object newObj.RegisterUpdateObject = function( updateFunc ) if newObj.CoroutineList == nil then newObj.CoroutineList = { } end local newRoutine = coroutine.create( function() updateFunc(newObj) end ) table.insert(newObj.CoroutineList, newRoutine ) end newObj.ClearUpdateList = function() if newObj.CoroutineList ~= nil then UpdateCoroutineList(newObj.CoroutineList, 0, false) end newObj.CoroutineList = nil end -- increase the count count objectCount = objectCount + 1 table.insert( objectList, obj ) -- Call the constructor function -- This gives Entity definition files a C++ flavor to them if ctor then ctor( obj, gameObj, ... ) end -- The C++ object needs to be told to delete as well obj.Delete = function() -- Safety check to be sure that this object isn't deleted twice if obj.parentObj ~= nil then -- In this engine, the WorldScene is the allocator for new Game Objects WorldScene:DeleteGameObject( obj.parentObj ) obj.parentObj = nil obj.isDead = true objectCount = objectCount - 1 table.remove( objectList, obj ) end -- If this object had registered any coroutines under itself, then those need to be -- destroyed. Resume them one last time so they can do any cleaning up if needed obj.ClearUpdateList() end --!*** This is the new object of the specified type! ***! return obj end -- now that the metatable is setup, let's set it to the new type definition setmetatable( newTypeDefinition, mt ) --!*** This is the definition/configuration used to create new entities of a specific type ***! return newTypeDefinition end
Here are the global functions that are used to create a new type definition, and a new object of a specific type.
local ClassConstructorList = { } function RegisterObjectType( ctor, type ) local newClass = CreateNewEntityDefinition(ctor, type) ClassConstructorList[type] = newClass -- Since each new object of this type shares the same metatable, the user can add -- more functions to it giving each new instance those functions as well. return newClass end function CreateNewObject( type, ... ) local factoryFunc = ClassConstructorList[ type ] if factoryFunc ~= nil then -- Step 1 - Create a C++ game object -- In this engine, the WorldScene is the allocator for new Game Objects local gameObj = WorldScene:CreateGameObject() -- Step 2 - Configure it using the Lua game object definitions -- Here we are using the mt.__call metamethod, to make the table act like a function. local luaObj = factoryFunc( gameObj, ... ) -- Since luaObj and factoryFunc have the same metatable, -- both of these statements are the same: factoryFunc.PrintDebug() luaObj.PrintDebug() -- Step 3 - Save a reference to the Lua object in the C++ Game Object -- To process physics contacts in lua script, we can use userData and -- parent pointers to go from shape->body->Physics component->Game Object. -- This next line is how we can go from Game Object->Lua Object gameObj:SetScriptObjRef( luaObj ) return luaObj else print( "no factory function for this object type: ", type, factoryFunc ) end return nil end
Finally, here's a simplified example of an enemy game object type we used in Triple Trouble, a 2D physics based bumper/Pac-man-ish game. This uses the global RegisterObjectType() defined above, and utilizes coroutines for several 'snippets' of logic.
--******************************************* -- EnemyBox.lua --******************************************* -- You can create 4 levels of scope for variables here: -- 1) Global - everything running from the same master lua state has access to (just don't use the keyword local) -- 2) Local to this File - all new instances of this object type have access to it. Much like a static variable in a c++ class - great for general configuration. -- 3) Local to coroutine/function - the EnemyBoxClass:Update() has several of them. Close to a static variable inside a function when used in a coroutine. -- 4) Attached to the object itself - this is much like Local to coroutine/function, except it's accessible across all of the object's functions. -- Tom's notation: -- I usually put Global variables in all caps, eg PLAYER (but not functions) -- Local variables to the file I start off with Caps, eg "local SelfFreezeTime" -- The other two I start off with lowercase, eg "self.health" or "local elapsedTime" -- Contact response stuff local SelfFreezeTime = 3 local OtherFreezeTime = 2 -- Death related stuff local PickupExplodeSpeed = 350 local DeathCountdown = 2.5 local RespawnCountdown = 30 -- Movement configuration local TrackDist = 25000 local TrackSpeed = 8500 local HitbackSpeed = 9000 local ScareSpeed = TrackDist * 0.66 --!*** Step 1 - Write a constructor which takes in an already created ***!-- --!*** Lua game object and an already created C++ game object ***!-- local function Constructor( self, gameObj, posX, posY, angle, type ) self.type = type self.startPosX = posX self.startPosY = posY self.health = 3 -- Each game object can have only one physics component. -- We didn't set up a component manager for these yet. self.physComp = gameObj:CreatePhysicsComponent() -- The graphics component creation is done through a manager. -- Each object can have multiple graphics components. self.gfxComp = RCompManager:createBoxMesh( gameObj, BoxScale, BoxScale, GL_LINES ) -- Rigid Body configuration local isSensor = false local mass = 100 local centerX = posX local centerY = posY local linearDamp = 0.65 local angularDamp = 1 physComp:SetBody( mass, centerX, centerY, linearDamp, angularDamp, posX, posY, angle ) -- Collision shape configuration local radius = 10 local density = 1.0 local friction = 0.3 local restiution = 0 local sphereShape = physComp:AddBoxShape( radius, radius, density, friction, restitution, isSensor ) -- Register a coroutine for this object self.RegisterUpdateObject( self.Update ) end --!*** Step 2 - Register the object type ***!-- local EnemyBoxClass = RegisterObjectType( Constructor, "EnemyBox" ) --!*** Step 3 - Add any extra functions to the new type definition ***!-- -- "self" is an automatic parameter when the functions are defined this way -- Another way to define it so to use: EnemyBoxClass.Explode = function(self) function EnemyBoxClass:Explode() local px,py = self.physComp:GetPosition() self:Delete() for j=1,10 do local fx = 1 - math.random() * 2 local fy = 1 - math.random() * 2 local newPickup = CreateNewObject( "Pickup", px + fx * BoxScale * 2, py + fy * BoxScale * 2, 1.5) newPickup.physComp:AddImpulse(fx, fy, PickupExplodeSpeed) end end function EnemyBoxClass:CreateRespawnCoroutine() local RespawnCountdownFunc = function() local respawnCountdown = RespawnCountdown while running do local elapsedTime,running = coroutine.yield() respawnCountdown = respawnCountdown - elapsedTime if respawnCountdown <= 0 then CreateNewObject( "EnemyBox", self.startPosX, self.startPosY, 0) return end end end -- This registers an autonomous coroutine, not necessarily associated with this game object. RegisterUpdateFunction( RespawnCountdownFunc ) end function EnemyBoxClass:ProcessContactMessage( otherObject, position, normal ) -- PLAYER is a global reference btw if otherObject == PLAYER and self.active == true then local px,py = PLAYER.physComp:GetPosition() local x,y = self.physComp:GetPosition() -- Didn't make a lua vector class yet local distX = px - x local distY = py - y local magSqrd = (distX * distX) + (distY * distY) local d = math.sqrt(magSqrd) distX = distX / d distY = distY / d -- deactivate for a bit self.active = false self.activateTimer = SelfFreezeTime self.physComp:SetLinearVelocity(0,0) if PLAYER.currentColor ~= self.type or PLAYER.isFrozen ~= nil then -- deactivate myself for a bit, and stop all velocity self.active = false self.activateTimer = SelfFreezeTime self.physComp:SetLinearVelocity(0,0) -- deactivate the other object for a bit while bumping them back otherObject.isFrozen = OtherFreezeTime otherObject.physComp:AddImpulse( distX, distY, HitbackSpeed ) -- Add health for hitting the player if self.health < 3 then self.health = self.health + 1 end else -- Ouch! The player has hurt me so I need to bump back self.physComp:AddImpulse( distX, distY, -HitbackSpeed ) self.health = self.health - 1 if self.health == 0 then -- Create a new coroutine right here!!! -- This spins the box around for a few seconds before actually destroying him local DieAnimation = function() local deathCountdown = DeathCountdown local spinAngle = 0 local elapsedTime = 0 local running = true while running do deathCountdown = deathCountdown - elapsedTime if deathCountdown <= 0 then self:Explode() self:InitiateRespawn() return else spinAngle = spinAngle + elapsedTime if spinAngle > 1 then spinAngle = spinAngle - 1 end self.gfxComp:SetOffsetAngle( spinAngle * 360 ) end elapsedTime, running = coroutine.yield() end end -- This registers an autonomous coroutine, not necessarily associated with this game object. -- But because of the closure, self is already defined so it doesn't need to be stored with the object. RegisterUpdateFunction( DieAnimation ) end end end end function EnemyBoxClass:TrackPlayer() local px,py = PLAYER.physComp:GetPosition() local x,y = self.physComp:GetPosition() local distX = px - x local distY = py - y local magSqrd = (distX * distX) + (distY * distY) if magSqrd < TrackDist then local d = math.sqrt(magSqrd) distX = distX / d distY = distY / d if self.type == PLAYER.currentColor then self.physComp:AddForce( -distX, -distY, ScareSpeed ) else self.physComp:AddForce( distX, distY, TrackSpeed ) end end end function TestMarkerClass:Update() -- 1) Initialize local elapsedTime = 0 local running = true local angle = 0 local rotTime = 2.0 local curTime = 0 self.active = true -- 2) Update while running do if self.active == true then self:TrackPlayer() else -- Reactivate once the timer is up if self.activateTimer ~= nil then self.activateTimer = self.activateTimer - elapsedTime if self.activateTimer <= 0 then self.active = true self.activateTimer = nil end end end elapsedTime, running = coroutine.yield( ) if self.health == 0 then running = false end end -- 3) Cleanup, if needed end
Other Lua Samples
Other Lua Samples
- Lunar Lite - A modified version of Lunar where the class being bound does not need to be modified. I used this to bind Box2D classes without having to change the Box2D source code.
- Froggle Cursor Control - A script from the student project "Froggle" that did the 3D cursor's auto-targeting using PhysX capsule sweeps. The Lua integration was different from the code above, where each object was defined in a file that was ran as a function. It was a 'fatter' implementation, since the file had to be ran for each new instance.
Transcend Components
Transcend Components
This project was my first experience working with an abstract component based architecture. I have come to love using a composite or inheretence based class for game objects (similiar to the Source Engine), so I was excited to dip my toes into a heavier architecture and see what the fuss is about.
The components were dependent upon Boost signals to relay message between each other. This meant that Game objects in this engine were essentially pen-pals, not just with other entities, but to communicate common data to itself (such as the graphics component knowing the position of the physics component associated with the same entity). Messages are great for networking and thread safety, but in this case the overhead slowed development and was at times very cumbersome for scripts to get any information about the objects they represent.
- InputComponents.h and InputComponents.cpp
- CharacterComponent.h and CharacterComponent.cpp
I wasn't responsible for the input engine, however I was tasked with added a component that allowed for dynamic mapping of keys to events.
The player's physics component was a tricky guy to get right for a while. The PhysX character controller had most of the features we wanted, but didn't provide a few essentials - like the ability to be pushed by moving walls. In the end we used this component, which manipulated the rigid body's velocity to give us the desired behavior.