A business trip followed by a short vacation means few updates since the last post. I did spend some time on the game before my trip and on the flight back home, and now that the jet-lag has worn off a little I’m trying to pick up development a little.
Entity sprites, revisited
The most interesting bits for this post are related to entity sprite rendering. The initial idea was to render each entity using a single rectangular sprite, where each entity class provided one or more sprite frame images to visualize its various states. This design constraint was not really the result of a deliberate thought process, I just built it this way because it most closely matched the fixed-size sprites the original Commodore 64 version was limited to. For the modernized iOS version it’s a little naive and unnecessarily restrictive, though.
The problem using single sprites with the same size, position and rotation as the entity itself, is that it’s not easily possible to use sprites larger than entity bounding box, and it is not possible to visualize entity state that is independent of the the entity position or rotation. Additionally, any sprite rendering effect I would like to implement in the future would be indiscriminately applied to every part of the single entity sprite. As an example, if I wanted to render a circular shield around the ship when the tractor beam is active, it would not be possible to draw the shield as a circle larger than the ship itself, and it would not be possible to dynamically change the color of the shield without changing the color of the ship. At least not without ugly hacks like encoding cues for the renderer inside the entity texture itself, or oversizing entity sprites and rendering them larger than the entity bounding box to have some extra space to play with.
To make entity sprite rendering more flexible, I changed the single string-type
spriteFrame
property of the K14Entity
class and its derived classes, to a
new property sprites
, an array of instances of a new class K14Sprite
. The
K14Sprite
class encodes a sprite frame name, and a position, size and
rotation to allow drawing sprites independent of the entity bounding box.
In addition to these properties, K14Sprite
instances can encode rendering
effects or cues. At this time, the only rendering effect I’ve added is a tint
color, which is used by the entity fragment shader to change the color of the
sprite by multiplying it with the (greyscale) sprite texels.
The K14Ship
entity is currently the only entity that is using multiple
sprites, depending on whether the tractor beam (which activates the shield when
nothing to tractor is in range) is activated. The following code snippet shows
the implementation of the K14Ship sprites
property:
-(NSArray *) sprites
{
K14Sprite *default_sprite = [[K14Sprite alloc] initWithSpriteFrame:K14_ENTITY_DEFAULT_SPRITE_FRAME
position:self.position
size:self.size
angle:self.angle];
NSMutableArray *sprites = [NSMutableArray arrayWithArray:@[ default_sprite ]];
if (self.tractorBeamActive)
{
if (!self.orbAttached && (self.tractoredEntity != nil))
{
K14Sprite *tractor_sprite = [[K14Sprite alloc] initWithSpriteFrame:K14_SHIP_TRACTOR_SPRITE_FRAME
tint:[K14Color colorWithRed:1.0f green:1.0f blue:0.0f alpha:1.0f]
position:CGPointMake(self.position.x, self.position.y + self.size.height)
size:TRACTOR_BEAM_SPRITE_SIZE
angle:0.0f];
[sprites addObject:tractor_sprite];
}
else
{
K14Sprite *shield_sprite = [[K14Sprite alloc] initWithSpriteFrame:K14_SHIP_SHIELD_SPRITE_FRAME
tint:[K14Color colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f]
position:self.position
size:SHIELD_SPRITE_SIZE
angle:0.0f];
[sprites addObject:shield_sprite];
}
}
return sprites;
}
Shooting
One major gameplay feature that was still missing was shooting, so I added it
on the flight back after my vacation. There really isn’t all that much interesting
to say about it, since all the bits and pieces were already there, just not
tied to any touch handling, and not integrated into the action replay system.
Implementing the feature was just a matter of adding an additional touch area
and triggering a ‘shoot’ command event when it is activated. This exposed a few
bugs in the way the K14Inputs
class handled queued command events, and in the
action replay command event classes, which were all easily resolved.
Assorted fixes and improvements
Besides the new features I mentioned above, I also spent some time tying up some loose ends and implementing some behind-the-scenes fixes. Without going into too much detail, here’s a summary:
-
Fixed a bunch of memory leaks, reduced memory usage
Using the XCode Instruments application I was able to hunt down a few memory leaks, mostly related to OpenGL state that was not properly released after closing the game screen. Things like the
CGBitmapContext
used to initialize the sprite atlas texture, and the OpenGL texture objects themselves. Additionally, I reduced total memory consumption by over a factor of 2, by releasing the sprite atlas texture image after uploading it to the GPU, and by allocating only the number of atlas sprite frames required for each entity, instead of always allocating a 4x4 atlas. The game still uses about 30 MB of RAM for a typical planet, which I find a little on the high side for what you get, especially considering the original Commodore 64 version had to get by with 64 kilobytes ;-). I’ll have to revisit the memory footprint later. -
Fix planet teardown
While trying to track down the memory leaks mentioned above, I ran into a problem with the way I implemented entity destruction. Whenever the reference count of a
K14Entity
dropped to zero, its backing Box2D body (and associated fixtures, joints, etc) was automatically deleted by theK14Entity
destructor This lead to race conditions when deallocating aK14Planet
instance, when Box2D state was deallocated beforeK14Entity
state that referred to it (either directly as a backing Box2D body, or indirectly as e.g. a tractored entity). Strangely this race condition did not result in crashes before, probably shuffling around something in somedealloc
method changed the destruction order and triggered the problem.The solution is straightforward and can act as a template every time you have a data structure of interconnected objects that may have mutual references to each other or to backing state, some part of which has to be deleted as a result of an update operation. Instead of immediately dereferencing objects whenever they are considered ‘dead’ and tearing down backing state from their destructors, mark the objects as ‘stale’, dereference them atomically by sweeping the ‘stale set’ after the update operation, and deallocate backing state explicitly afterwards. For the case mentioned above this meant setting a ‘dead’ flag for entities flagged for removal and deactivating their Box2D body, then sweeping the entity set after each update operation, removing all references to dead entities, and destroying their backing Box2D body explicitly.
-
Change planet initialization sequence
Instead of automatically creating a
K14Ship
andK14Orb
entity from inside theK14Planet
constructor, client code is now responsible to create these explicitly. I’m not even sure anymore what problem this solved, but at any rate it makes planet creation more predictable when the position of the ship and orb depends on some other aspect of the planet that may not be known from inside its constructor (e.g. if the orb needs to be positioned relative to some other entity such as its pedestal, more on that later ;-) -
Split the orb into two entities
Instead of representing the orb and pedestal as a single entity which was lifted in its entirety when tractoring it, there are now separate entities for the orb and the pedestal. When tractoring the orb, the pedestal is left behind on the planet surface like it should.
-
Fixed unit tests
All unit tests using
K14Planet
orK14Entity
were broken after earlier changes to the way planets and entities are created and initialized. Unit tests using a hand-crafted action replay to simulate and test gameplay situations were also broken as a result of changes to the action replay classes. These were all relatively easy to fix. -
Minor gameplay tweaks
Changed some gameplay constants related to ship handling, changed the size of some entities.
Video
As usual, here’s a video showing some recorded gameplay to illustrate the visible changes I’ve talked about in the paragraphs above. As you can see, the game is slowly starting to become playable :-).
Fun fact: this replay was recorded on an iPhone 6, then replayed in the iOS simulator, set to simulate an iPhone 5. Even though the replay is compiled to x86 code running in a simulator, which is set to simulate a different piece of hardware, the replay is a perfect reproduction of the iPhone 6 gameplay. This illustrates two things, 1: the action replay functionality appears to be pretty solid, and 2: the iPhone simulator is pretty awesome ;-)
Next steps
One thing I still want to implement before moving ahead with new features is smarter sprite texture atlas packing. Right now, each entity has a sprite atlas created as a grid of equal-sized square sprite frames, which means the sprite images are scaled and stretched when creating the atlas to make them fit. When rendering an entity, the sprite is resized again, to match the aspect ratio and size of the entity. All this scaling and stretching results in image quality loss, so I’d like to avoid it by allowing non-rectangular, non-uniform sprite images in the same texture atlas, and directly storing the source images for each sprite frame in the atlas without scaling or stretching them.
Besides the texture atlas packing there’s a few minor things I’d like to fix, such as projectiles flying through the planet surface and such. I’ll start thinking about new features and improvements after these are fixed.
Development scoreboard
All of the above added about 7 hours of development time, which brings the total development time to ~147 hours. SLOC count increased by 127 to 1749.