
I got home from RIT last Saturday, and naturally, the first thing I do is stay up all night coding. I had a really fun 24 hours. Sean and Peter came over to have a cookout type deal, and then afterwards we transformed my dining room table into NSR Headquarters. Armed with 6 cups of coffee and an unlimited supply of hard boiled eggs, we stayed up until about 7:00 AM working on the Invasion Engine beta. Peter had prior engagements, but Sean and I (after a brief 5-6 hour nap) continued our campaign that culminated in about 1000 lines of new code.
To folks who have worked on large projects, 1000 lines of code doesn't seem like a whole lot. The code we've written, however, is in the "core engine systems" category - the scripting interface and networking were the targets of our endeavor.
As the evening began, there wasn't much in the code base other than some of the classes' general structure set up, maybe some simple state manipulation for the most part, a lot of empty methods.
We came up with a wonderful idea while eating, and split the engine into two separate programs which only share some code: a client, and a dedicated server. This makes multiplayer trivial, and singleplayer games will simply launch a dedicated server and control it via a local socket connection.
Another great idea was in how we manage scripted objects (which is pretty much everything in the game's world). Any object you'd see ingame - a rock, a pistol, a schizophrenic space squid, etc. - is based on a Lua script. Specifically, it's an instance of a Lua class in the Object-oriented sense. Through meticulous studying of various Lua books and our own stunning intelligence, we now have a script system that lets us strongly associate particular Lua objects to "physical" objects in the game world.
Let us suppose that someone has written a script for a Lazer Pistol.
-- Make us a class that derives from "doodad"
LazerPistol = Class(doodad)
-- set up "static" values
LazerPistol.initialAmmo = 100
-- called when we're instantiated
function LazerPistol::created() do
-- Set up instance fields
-- (it'll create an instance field 'ammo')
self.ammo = self.initialAmmo
end
function LazerPistol::use() do
-- Seems like the thing to do
self.ammo = self.ammo - 1
self:shootTheGun()
end
-- Other functions to complete the LazerPistol
And in another script - perhaps the map - we want to spawn a new Pistol.
-- Call our special C function that creates
-- the new doodad ingame (with networking)
-- and calls the 'created' function of it
local myNewPistol = doodad.New(LazerPistol)
myNewPistol:use() -- This works here!
When this lua object is created, this not only makes a new Lua table that can have its own functions and state, but it also creates a new object in the game that's automatically synchronized over the network, given physical properties, etc. And the greatest part is, the Lua scripts don't ever need to care. In fact, if someone wants to create a derivative object, it works exactly how one would derive from a Lua table in general:
-- Suppose somewhere we want to alter the
-- behavior of this particular gun.
-- Redefine the 'use' function
myNewPistol.use = function(self) do self:doSomethingElse() end
-- Now, let's make a new pistol based on these
-- changes, using myNewPistol as a template.
myDerivedPistol = doodad.New(myNewPistol)
And voila - a new pisol would fall from the sky and have all the same modified behaviors as the original pistol. And this inheritance goes the other direction, too. We plan on specifying default behaviors for doodads and characters so that if a doodad doesn't have a script, the default doodad will figure out a sensible mesh, physics body, etc to create for it. More complicated doodads can then override these functions if they need to (which, if it's anything other than a flower pot, they will). It's really exciting, and last night we wrote the code that allowed us to actually write scripts very similar to the examples above, and see them working.
Our last stroke of brilliance was in networking. Today (Memorial Day), Sean and I spent some quality time with our dear friend malloc and memcpy, setting up a packet serializer/deserializer, and making the sockets work right. End result, we now have a client and server that can connect, discuss how ready the client is to join the game in progress, and properly transfer data between the two states, sometimes routing packets to the associated ingame objects. Additionally, the client is set up to transmit all the player controls to the server and handle local character prediction. All that we need to do now on the Networking front is to come up with a full list of packet types, and send/receive them when notable events take place.
The 3d graphics environment will start up properly on the client, too, but at this stage of our committment to Invasion, that's gotta be taken for granted :)
And besides, we're not doing anything involving graphics yet - we're still drawing byte-block diagrams and looking at Wireshark hex dumps.
As a final side note, the whole night we were working between my Gentoo desktop, and an Ubuntu laptop. And it was glorious. I'm quite thrilled to say that most of the development of this cross-platform engine is being done on various Linux machines, if only to prove to people that it's no harder (I would argue even easier) to develop games on Linux compared to Windows. But that's a rant for another day - I do want to have at least another few hours of sleep before I go and try to hunt down a summer job.
~Karantza