It’s been a while since the last update, but fortunately that doesn’t mean I haven’t made any progress. Over the last 2 weeks I’ve tried to spend an hour or so every now and then, setting up some skeleton classes based on the preliminary data model introduced in the previous post.
As expected, I ran into some oversights and deficiencies of this first attempt at a data model, so I made some adjustments and got a relatively clean set of skeleton classes representing the game world (which I decided to refer to as ‘planet’ from now on), and the various entities (actors only, at this point) of the game. I concentrated only on the data model class methods concerned with constructing and initializing a planet, and populating it with the first batch of actors, which I decided to limit to the player ship, fuel pods, turrets and the nuclear reactor present on each planet.
My primary goal at this point is to get to a more-or-less minimal API that allows setting up the first planet of the original game excluding the orb (which I think will be a static entity that may need some special treatment), including any necessary Box2D setup required to start stepping through the physics simulation. Right now, I guesstimate that I’m about halfway to this goal. Before I get into specifics, let’s first have a look at what this fabled ‘planet 0’ looks like.
As you can see, planet 0 is kind of boring, which makes it a perfect environment for our experiments. There’s a simple, easy to replicate surface, exactly one of each of the initial set of actor entities, and no switches or traps.
First thing I did before writing any code was to install a Commodore 64 emulator and find a tape image of the game. Yes, I’m aware using copyrighted ROMS is considered illegal by some, but this is all in the name of science, not to actually play the game for free. I used to own a legal copy of the game years ago anyway and don’t remember selling, donating or throwing away the tape, so I’m taking my chances. The emulator I used is Vice, which has proved excellent so far and runs natively on OS X.
Using Vice, I took screenshots of the first planet and stitched them together to get the above image, which is not only useful to pad out this post and break the wall of text, but also as a frame of reference for the relative sizes and distances of the planet itself, and all the stuff it contains. It seemed like a good idea to decide on a coordinate system for the game world pretty early on in the process, so I took the planet 0 overview image above and made a quick sketch containing the relevant distances. As I didn’t want to make an exact science out of it, I just picked the fuel pod as a reference point, for the simple fact that it is almost perfectly square, and because it’s dimensions seemed roughly equal to the smallest dimension observed for any of the other planet entities. It seemed only natural to make the fuel pod 1 by 1, leaving it to the imagination whether that would be 1 by 1 meters, yards, inches, or double-decker buses. Using our newfound dimensional anchor I sketched a back-of-the-envelope (literally) ‘blueprint’ containing the distances along the planet surface:
Data model changes
As I mentioned earlier, the moment I created an XCode project and started writing the first few data model classes, I ran into bad design decisions I made to get to the preliminary data model. Turns out my impression that drawing-board design without actual code rarely works well, has proven true yet again.
First of all, I decided to rename the
K14World class to
only does this look and sound a lot more interesting (planets are cool), but
it also makes it easier to separate ‘planet’ as in ‘level of the game’ from
‘world’ as in ‘what Box2D uses to refer to the physics simulation environment’
when talking about these things.
The next thing I didn’t really think through very well was the construction
and life-cycle management of game entities. Eventually, the main game loop
will update entities through the
K14Planet class, which means it should have
some kind of repository of active entities, and provide functionality to add,
remove, retrieve and uniquely identify them. This screams for a factory pattern,
where entities are always created through a factory method on
I removed the
addEntity method from the
K14Planet class, and replaced it
createEntity method that takes a string-type class name. Based on this
K14Planet will determine the actual class type of the entity
to create, generate a unique identifier for it, instantiate the entity class,
and register the resulting instance by adding it to an internal dictionary
of active entities. For now, the entity class name is simply chosen to be
the same as the name of the actual class representing the entity type, but
I implemented the factory method using a table to allow more flexible class
names later, which may prove useful if something along the lines of a
K14ScriptableEntity is ever implemented. Imagine creating an entity using
a classname of
K14ScriptableEntity.Turret, for example. The
will be fully responsible for life-cycle management of entities, so it will
still need to a
removeEntity method or something similar that safely removes
the entity from the Box2D world and destroys the entity instance, but I haven’t
implemented this yet.
Another oversight was entity setup itself. As one of the design goals for the
game is full scriptability of entities, any kind of entity setup should be
exposed through our own (scriptable) interfaces, not hard-coded inside (for
K14Entity class or requiring direct calls into Box2D. In
particular, this applies to setting up Box2D bodies, and adding fixtures of
various shapes to it. Remember that fixtures define the shape, mass, joint
attachment points, etc of the (possibly multiple) parts of entity bodies. The
entity body itself is basically ethereal without fixtures, so to say. Not only
K14ScriptableActor some day be able to setup a body with multiple
fixtures of various shapes and densities, it should also be able to manipulate
fixture properties after the entity has been created, using a scriptable
interface. Think changing the density of a particular fixture of an entity for
example, or removing a fixture based on some kind of condition. To realize
this, I introduced simple wrapper classes for the Box2D concepts of ‘fixture’
and ‘shape’ (shape is basically a property of a fixture), called
K14FixtureShape (the latter isn’t actually
a class by the way, but a protocol implemented by classes such as
K14BoxShape, which is more Objective-C-ish than using an abstract base class
with concrete subclasses). Analogous to how an entity is created through a
factory method on
K14Planet, fixtures are added to entities through a factory
K14Entity, which takes a
K14FixtureShape, a name for
the fixture, and the fixture density as parameters. Note that the size of the
shape combined with the density value determines the mass of the fixture, and
the mass of all entity fixtures combined determines the total mass of the
entity. Later on, it should be possible to retrieve, and remove entity
fixtures through methods on
One other minor change I made is related to the way the planet surface is
passed to the
K14Planet constructor. Instead of allowing a list of
edge-lists, I simplified the interface to only accept a single, contiguous edge
list. I also imposed some restrictions this edge list has to adhere to: the
vertices of the first and last point should have the minimum and maximum
x-coordinate of any vertex in the edge list, respectively, and the y-coordinate
of the first and last vertex should be the same. These two restrictions ensure
the surface runs left-to-right and can be ‘stitched’ at its endpoints to make
the planet ‘wrap around’ like it does in the original game. Both restrictions
are checked in the
K14Planet constructor and violating them will throw an
assertion. Some time in the future it may be necessary to define a flexible
interface to setup planet geometry, allowing more complex planet shapes, but as
far as I can tell it should be possible to define any level of the original
game using the current definition, so let’s keep it simple for now.
Here’s a quick detail-view of the current state of the planet-, and entity-related classes, including only those bits that are actually already implemented. Eventually I will integrate these in the larger data model, when development progressed a little further and the design has stabilized a bit.
Current project state
At this point in time ‘2k14: The Game’ consists of an XCode project containing
a few skeleton classes representing the game world (
K14Planet) and the
entities I mentioned earlier. The skeleton classes expose factory methods to
create entities and fixtures, and implement a few basic entity properties such
as position, angle, etc. Most of these properties map to the Box2D body and
fixture objects backing the entity classes, which are already created and added
to the Box2D world. The Box2D world itself is created from the
constructor, and initialized with edge shapes representing the planet surface.
There is no game loop, and no
step methods on
so even though it’s already possible to setup a planet, create entities and add
fixtures to them, everything already backed by Box2D objects, there is no way
to actually run the Box2D simulation yet.
In the following days I will add whatever is needed to be able to create and populate planet 0. The plan is to present some code snippets to illustrate how the planet setup works, taken from actual, working code.
Development time scoreboard
Now that I’ve started to write some actual code, I’m going to try to keep track of the amount of time spent in XCode, or at least a rough estimate of it. Setting up the initial XCode project and writing the skeleton classes took me about 4 hours total up to now, one hour of which was spent dicking around the build settings to get Box2D compiled into my project, and renaming/moving stuff in the directory structure XCode creates by default when you set up a new project (no, I don’t want directory names that have spaces and colons in my project directory). After the project was set up and I started to add classes according to my initial data model, I ran into all these bad design decisions I elaborated on in this post, which meant I had to start refactoring before I even had any working code. Hopefully things will be smoother from now on ;-)