Just a quick update this time, as there hasn’t been much visible progress this week. I didn’t write any actual code, but I did throw together a class diagram illustrating a very preliminary data model design. By data model, I mean those parts of the game modeling the game world, its inhabitants, and the way they represent and handle their interactions. You could see it as the bottom layer of the application, representing stuff unrelated to rendering or actual game logic:
Here’s the class diagram:
The diagram is a little wide and unwieldy for a web page, as usual with UML class diagrams. If only there were some kind of drop-in JavaScript component that adds proper zooming and panning to any SVG image, one that actually works. I’ve tried two but they both only made things worse… If I’m going to post more class diagrams I may have to revisit this. In the mean time, click tje diagram for a bigger version.
A few notes to go with the design
Let’s start by saying that the data model class diagram is clearly not intended as a blueprint or a specification. Some classes are only partially defined, or contain references to other classes that aren’t defined at all, because they are not really relevant for the purpose of illustrating the data model. I basically made a list of things I could come up with the game engine should be able to represent or handle, and tried to accommodate them in a simple class hierarchy. I will try to keep updating the diagram as the implementation progresses and the data model starts to evolve and converge to its final form, but for now don’t make more out of it than a very rough initial attempt at an architectural overview.
I chose to prefix all class names with ‘K14’, to avoid name clashes with external frameworks. In Objective-C land there’s no such thing as proper namespaces, so everyone prefixes class names with some (hopefully) unique enough characters, which kind of sucks, but that’s just how it is. I would have liked to prefix all class names with ‘2k14’, but class names can’t start with numbers, so I ended up with ‘K14’ instead.
The main class representing the game world is K14World
, which can be seen as
a representation of the game level data, and the umbrella under which all
dynamic game entities live. The K14World
class sets up the Box2D world using
game level data, and configures the Box2D simulation. The class provides a
step
method that takes a time delta, which will be the main interface to
advance the game logic and the Box2D simulation and will be driven by the game
main loop. The step
method is also the place where any global game logic
will be implemented, for example reacting to inputs, handling game win/lose
conditions, keeping track of player score, etc. The class will provide methods
to add, remove and retrieve game entities, and to perform world queries such as
finding entities visible from some location and viewing direction via the
peek
method. A K14World
instance is created with a list of line-segments
defining the level geometry, which may sound overly simplistic, but should
be expressive enough to define the geometry of the levels from the
original game. Besides the planet color the levels are basically no more than a
rectangular area with some polygons cut out where there should be ‘air’ (or
would that be vacuum?). In Box2D the surface-to-air transitions of the level
can be conveniently specified using line segments, so it seems natural to just
stick with them and not over-complicate things by dreaming up some elaborate
polygonal data structure or something. For rendering we will probably have to
do this in some way or another after all, but I’m not thinking about rendering
yet. One last thing to mention about the K14World
class is that eventually,
almost its entire interface should be scriptable, or in other words: it should
be possible to create world data for levels of the game completely using
scripts.
The other main class in the diagram is K14Entity
which will represent all
dynamic entities in the game: actors (the ship controlled by the player,
enemies, the reactor placed in every level), projectiles (bullets) and
stationary objects (fuel pods, the orb, …). Every game entity is backed
by a Box2D body and one or more fixtures, the latter of which are added
to the entity programmatically using the addFixture
method, which also
takes the shape and size of the fixture. How the shapes will be defined
isn’t really all that interesting for now, but it will probably come down
to either a box, a circle or a convex polygon. Initially, most (all?) entities
of the game can probably be represented as a Box2D body with a single
fixture, but I can imagine multiple fixtures are needed to create more
complex actors, for example to model actors with a non-uniform density
distribution (e.g. an elongated ship with 2 heavy bits at opposing ends
and 2 individually controlled thrusters). Most of the methods and attributes listed
with the K14Entity
class are directly related to Box2D concepts. There’s
basic body orientation, velocity, applying impulse/force to the entity,
and a convenience function peek
, which is similar to the identically
named method in K14World
, but queries a configurable field-of-view for
the entity.
Just like K14World
, the K14Entity
class has a step
method, which will be
called from the step
function of the world class. The step
method for
actors is where entity-specific game logic will be programmed, for example
applying force to the ship when thrust is engaged, joining the orb with the
ship when the tractor beam is activated close enough, deciding when a turret
should fire, reacting to collisions with the world itself or other actors or
projectiles, etc. Entities have a collision callback method onCollision
that
will be called when Box2D signals a collision, and the class will have to
provide some kind of way to join entities together, the specifics of which are
not really clear to me yet. For now I’m going with some kind of class
representing various types of joints (K14Joint
), which can be used to join an
entity to one or more other entities. For the ship and the orb, only a single,
static joint of a single type (distance joint) would be needed, but again, I
can imagine more interesting scenario’s that would require multiple, dynamic
joints (a ship that can lift multiple orbs at once for example, or use the
tractor beam as a crane).
Each entity will have back-references to the K14World
instance, and
can use it to signal various events, spawning new entities (projectiles,
for example) or removing them (destroying a turret, etc). The exact
distribution of game logic between entities and the world class is a
still a bit muddy and will have to be worked out further. For now, I made
the decision to put something like player fuel with the world class
via the K14Player
class for example, because I can imagine a scenario
where the fuel level would be affected by some global world condition
not known to the ship entity, but this could change if I get the idea
the game logic gets too scattered around and awkward.
One last thing to mention about the entity classes is the K14ScriptableEntity
class. Eventually, I would like to be able to fully script almost any aspect
of every entity, instead of having a few compiled-in entity base classes
that scripts can only manipulate within the limits of the native base
class interface. This would require an entity class that allows adding
arbitrary attributes to entities, which is what the ‘custom property’ methods
on K14ScriptableEntity
are supposed to model. I haven’t given this much
thought yet, but I keep it in the back of my mind so as to not work myself
into a corner. To get up and running quick, I will start with static,
compiled in entity classes though.
The coming week I’ll probably just go ahead and start hammering in the classes in the diagram, and see where I end up. If there’s anything I’ve learned about software development over the years, it’s that it’s never a good idea to over-specify early on in the development process. Big corporations may still love the waterfall model, writing requirements until you drop before you even have a single line of working code, but in the end, it always turns you can’t make the world (the customer, the framework, the hardware, or the problem itself) conform to your requirements, just because it took you so much time to write them down. In my opinion, iterating quickly, getting something up and running quickly, and from there on trying to keep a working system all the time, always trumps the rigorous long grind of trying to completely specify a system the details of which you almost certainly can’t fully appreciate yet.