As has become tradition since I started with 2k14: The Game, summertime has been very slow in terms of progress on the game. I’ve only recently started to pick up work on tying up some minor loose ends, and thinking about interesting larger improvements I’d like to explore.
This post will quickly go over the small improvements I did recently, which are mostly gameplay related, and almost completely implemented on the Lua side of the game. Nothing illustrates the gameplay improvements better than seeing them in action, so I made a video that shows a few short action replays I used to develop and test the new game logic:
Explanation of the improvements, in order of appearance:
Attaching/reattaching the ship-orb joint
Previously, the ship-orb joint was only created after the orb was fully lifted, by means of a distance joint that connected the two entities at a fixed distance exactly equal to the player’s tractor beam range. This approach had two downsides. First of all, the logic for connecting the ship and the orb was unnecessarily complicated: the joint had to be created at exactly the right moment, otherwise the joint would push or pull the player to exactly the right distance, as distance joints are not flexible. Second, the fact that the joint was only created after flying out while tractoring, there was no visual feedback when tractoring the orb before lifting it. The renderer only has access to engine-level constructs (such as joints), but does not now about game- or entity flags inside the Lua game logic (such as ‘orb being tractored’).
The solution was to use a Box2D rope joint instead of a distance joint. Rope joints are very much like distance joints, except that their distance value represents a maximum distance. When two entities are joined at a distance less than the maximum distance, they don’t experience any kind of force from the rope joint. This makes it much easier to model the the ship-orb connection: just create the joint when the orb is being tractored, and remove it when it is un-tractored before it is lifted. The renderer renders the joint if it is present for visual feedback when the orb is being tractored.
To track attaching/detaching the ship-orb joint, two new gameplay events are added: ‘orb attached’ and ‘orb detached’, which will create or destroy the orb joint. These work together with the single event used before, ‘orb lifted’, which is emitted by the ship entity script when it detects that the ship and orb entities are at a distance equal to the tractor beam range. This event used to implement all the ship-orb joint logic, but now only sets a flag that indicates the orb is being lifted.
Crashing or getting hit while lifting the orb
When the player crashes while the orb is being lifted, both the ship and the orb entities now explode using a particle effect. The sequence of gameplay events for this case is: disable ship and orb, remove ship-orb joint, explosion effect at ship and orb location, respawn player and orb at their starting locations.
Orb hit while lifting
Similar to the previous item on the list. If the orb is hit while it is being lifted, both the ship and orb are exploded and will respawn. This case is handled exactly like the ‘ship crashed while lifting orb’ case, but is triggered by a different gameplay event (‘orb hit’ instead of ‘player hit’).
Leaving the planet without the orb
When flying past the limits of the planet (‘leaving the planet atmosphere’) without carrying the orb, your mission has failed . This will respawn the player without deducting a life.
To tell the player that their mission has failed, I implemented a very simple system in the game engine for displaying text effects, which is explained in a little more detail in a later section of this post. I also added a new event type ‘text effect’, to create text effects from Lua scripts, which is used by the ‘mission failed’ logic to show a message ‘MISSION INCOMPLETE’ to the player.
The ‘mission failed’ logic is triggered by the ‘player left atmosphere’ event, which will check whether the orb is being lifted. If not, it will fire a new event type ‘mission failed’ which in turn will fire a ‘respawn particle effect’ event at the ship location, followed by the ‘mission incomplete’ text effect event, and finally a ‘respawn player’ event. The events are posted with appropriate delays to allow the particle and text effects to finish.
Successfully leaving the planet with the orb
The end goal for the player is to leave the atmosphere while carrying the orb. Just like the ‘mission failed’ logic, the ‘mission complete’ logic is implemented using a new event type triggered from the ‘player left atmosphere’ event. The sequence of events triggered by the ‘mission complete’ event is: disable player and orb, break ship-orb joint, respawn particle effects at ship and orb location, two text effects ‘mission complete’, and ‘bonus’, scheduled with a small delay, and finally a ‘game exit’ event that will drop the game back to the level select screen.
Obviously, at some point in the future the sequence of actions after a ‘mission complete’ event should ultimately result in a new event type ‘next planet’ or something similar, which will load up the next planet instead of dropping to the level select screen.
Similar to particle effects, text effects are classes adhering to a simple
protocol, in this case K14TextEffect. The only interfaces mandated by this
protocol is that implementing classes have text, color, height and
transform properties, for visualizing it, duration and done properties
for managing label lifetime, and a step:dt method to update the label on
every frame. Implementing classes can do whatever crazy stuff they like in
their step methods: fade-in/fade-out, changing the transform to move, scale
and/or rotate, changing the text itself, etc. The renderer will read the
properties of each active text effect on every frame, creating and modifying a
K14FontLabel to render them accordingly.
Right now, only a simple, static text effect is implemented, which does not modify any of its properties, and just disappears when the amount of time elapsed over all invocations of its step method exceeds the duration it was created with. At a later point, I will add more text effects, for example to show score labels that move and fade out when hitting something. Probably text effects will end up very much like particle effects, where multiple ‘text effect update’ classes can be composed to make new text effects.
As you may have noticed, I’ve been using the word ‘event’ a lot in the
description of the improved-, and newly implemented gameplay features. This is
not a coincidence: with the engine capable enough to support almost all of the
mechanics required to setup, run and render all basic features of the game, all
work on the actual game logic is happening in the Lua parts of the game, which
are mostly implemented as a simple [event loop/message pump][event-loop] using
a central message bus with a single listener (the Lua
Game class that implements
all message handling).
I will save the detailed discussion about how this event loop is implemented and how I think it should be improved for a later post. Suffice to say that while implementing the gameplay features discussed in this post I was starting to experience the limitations of the overly simplistic current approach, and have been thinking about better ways to implement event-driven logic. I’m still working this out in my head, and as usual I’m gravitating towards a solution that’s elegant, powerful, and very interesting from an intellectual point of view, but totally over-engineered for the kind of game I’m making ;-). But I’m going to continue on this path anyway, out of curiosity how it will work out. More about this in the next post.