Since the last post I’ve been working in short bursts on implementing some simple particle system effects, which are now starting to take shape. That means time for a quick update.
Design decisions
The main purpose for adding particle effects was to add some simple visual feedback when the player or some other entity was hit. In the original game, a very simple explosion effect was used when the player or some enemy entity was shot, which would send a few single-pixel (remember, pixels on the Commodore 64 are pretty fat ;-) particles flying away from where the player or enemy used to be. A similar effect was used when the reactor was hit: the reactor itself would not explode but some particles would be emitted from the point of impact. At the very least, I wanted to be able to replicate these effects for 2k14: The Game. But I also set some additional requirements for myself:
-
Flexibility
Instead of limiting the particle effects to a few hard-coded configurations, I want to be able to compose particle effects by parameterizing particle effects in a way that would allow combining different parameterizations to get new effects. For example, the initial position of particles and the rate at which they are emitted, should be independent of their movement, rendering, physics properties, etc. A hypothetical ‘snow flake particle’ could just as well be emitted from a ‘cloud emitter’, as from an ‘explosion emitter’. An ‘explosion emitter’ could just as well emit textured particles that don’t collide with anything and simply fade away, as particles with full Box2D physics, that bounce around the planet surface and kill any entities hit by them.
-
Scalability
Even though we are at first only interested in very simple effects having less than a handful of particles, far less than 100, the implementation should leave room to scale the number of particles up dramatically. Let’s say up to 100K particles, to pick a nice round number. This would allow more advanced effects such as smoke, fire, fluid dynamics, etc. It’s highly doubtful 2k14: The Game will ever use these kinds of particle effects, the only reason I put it as a requirement is because it’s more fun to build particle systems that scale to these numbers ;-)
-
Optional physics integration
To save myself some work initially, I’d like to be able to create particles with full Box2D physics, that are affected by gravity, collide with other entities and the planet surface, have mass, etc. Besides cheating my way out of implementing particle physics myself, this would also allow particle effects for gameplay purposes, e.g. non-lethal particles impacting the player ship.
Box2D will most definitely not scale to 100K live particles on phone hardware, so Box2D physics should be an optional property of particles. If we ever wanted to add smoke or fire effects, or particles with very simple physics that don’t interact with other parts of the world, it should be possible to emit particles completely independent of the Box2D world.
-
Easy integration
It should be possible to easily create particle effects from Lua scripts, in a fire-and-forget fashion, for example as a result of gameplay effects. The engine code should not encode any logic as to when or how a particle effect is created, that’s all up to the gameplay logic implemented in the Lua domain.
Implementation
The straightforward way to implement particle system support would be to create some classes directly mapping to the most obvious concepts involved in a particle system. We would have something like a particle class’ that would implement the minimum representation of an individual particle, and act as base class for specialized particles for various particle effects. The particle base class would have properties like particle position, velocity, etc. and abstract interfaces for assigning initial values to them, and to update the particle with a timestep. Particle subclasses would implement the abstract interface, and optionally add additional properties such as particle size, texture coordinates, etc. A simple particle system class would compose a large number of instances of the particle class, and offer methods to emit them in various ways, updating them, and managing particle lifetimes.
For our purpose, this approach would (at least initially) be a perfectly fine solution with no real drawbacks. For small numbers of particles the runtime overhead of dispatching multiple virtual function calls for each particle update negligible, and as long as the number of ways the particle system or the particles themselves can be parameterized (ie: if only a few ‘particle system configurations’ need to be supported), we can get by with a simple particle system class and two, three, maybe four derived particle classes. These two assumptions violate the ‘scalability’ and ‘flexibility’ constraints outlined above though: when updating 100K particles each frame, virtual function call overhead will start to matter, and scattering data for each individual particle all over heap-allocated object instances will absolutely kill CPU cache performance. In addition to that, using inheritance and specialization to represent different combinations of particle attributes and behaviors will quickly either lead to an unwieldy forest of subclasses, or to a smaller number of particle ‘god classes’ that can be initialized/parameterized in a million ways to implement every kind of particle effect we can come up with. All in all, a different solution is needed.
Particle system architecture
To satisfy all of my self-imposed design constraints, I’ve opted for a component-based design. I’ve written a little bit about component-entity systems in a previous post in the context of refactoring the way scripting capabilities were added to games, planets and entities, so for a little more background on what they are about you can read more about them there. Back when I wrote that post, I already decided to keep component-entity systems in mind as an architectural pattern for other parts of the code for which it could be a good fit. As it turns out, particle systems are a perfect example of a problem where a component-based design really is a much better choice than classic OOP inheritance/specialization.
In the following sections I will quickly go over the components involved in the particle system implementation I’ve integrated into 2k14: The Game, which will (hopefully) make clear why the component-based architecture makes a lot of sense for this problem, considering the requirements I’ve mentioned earlier.
I’ll be honest and state upfront that what I’ve implemented wasn’t actually all my own idea ;-). I found this excellent series of articles by Bartlomiej Filipek, who goes over all the moving parts involved in advanced and efficient particle systems, converging to a solution that’s very close to optimal for what I’ve had in mind for 2k14: The Game. Please read Bartlomiej’s series if you’re interested in particle systems, because he goes into a lot more detail than I will here!
Particle data
At the center of the particle system implementation sits the most obvious component: the data structure that holds particle data. Obvious in what it does (store particles), but not so obvious in how it does that.
With scalability to hundreds of thousands of live particles as a self-imposed
requirement in mind, I went for a straightforward C-style structure-of-arrays
(SoA). The idea is to make separate, flat arrays for every particle property,
and group them into a structure. The counterpart to SoA would be an
array-of-structures, which is basically exactly what the name suggests: the
properties of a single particle are grouped into a structure, and the structure
is repeated in an array for each particle. The SoA K14ParticleData
structure
looks like this:
typedef struct K14ParticleData
{
unsigned int maxParticles;
unsigned int currentParticles;
GLKVector2 *positions;
float *angles;
GLKVector2 *velocities;
float *angularVelocities;
float *lifetimes;
float *decayRates;
float *sizes;
GLKVector4 *colors;
void **box2DBodies;
} K14ParticleData;
The main reason for choosing the SoA layout is to improve locality of reference. When updating particles, we typically want to update the same attribute for all particles from a tight loop, then move on to the next attribute. For example, first update all particle positions, then update all particle lifetimes, etc. Grouping particle attributes in a flat array by their types means that they are sequential in memory, and updating them one-by-one will maximize cache effectiveness: in an AoS layout the position of particle 1 would not be next to the position of particle 2, but N bytes separated from it, where N is the size of each particle structure. With enough particle attributes jumping through memory like this will greatly increase the chance of cache misses, which can kill performance.
Another advantage of the SoA layout is that in the future, we could make some
particle attributes optional, or add new particle attributes without having to
change the particle data structure. We could allocate a big chunk of memory,
then setup pointers into it for each particle attribute used. Right now, the
set of particle attributes is static, and determined by the layout of the
K14ParticleData
structure, Memory for each particle attribute array is
separately malloc
-ed and free
-ed on construction/destruction, which by
itself is not terribly bad, but means the arrays may not be consecutive in
memory, and need to be memcpy
-ed one-by-one when taking a snapshot of the
particle system for rendering.
Particle generators
The second component of the particle system implementation are the particle generators. The name ‘particle generator’ may be a bit misleading, because a particle generator does not actually generate any particles, instead it initializes (a subset of) the initial attribute values for newly allocated particles. Examples of possible particle generators are a ‘circular area position generator’ (to assign positions to particles within a radius of some point) a ‘random color generator’ (to assign random colors), or a ‘Box2D generator’ (to create a Box2D body to each particle that can be used to add physics properties to it).
More complex generators are possible as well, which initialize multiple attributes at once. One example from 2k14: The Game is the ‘explosion generator’, which will assign particles linear and angular velocities that simulate an explosion parameters by a point (origin), a force value, and an initial velocity (for exploding moving objects).
All particle generators have to implement a single method defined by the
K14ParticleGenerator
protocol, which will be called when new particles have
been emitted. For the K14ExplosionParticleGenerator
, this function looks like
this (note that this is a very simple proof-of-concept generator, the
velocities that are calculated and assigned are not very realistic):
/**
Generate particles.
@param count number of particles to generate
@param sequenceId sequence number of first particle to create (unused by this generator)
@param particleData particle data
*/
-(void) generateParticles: (unsigned int) count
sequenceId: (unsigned int) sequenceId
particleData: (struct K14ParticleData *) particleData
{
for (unsigned int i = particleData->currentParticles; i < (particleData->currentParticles + count); i++)
{
GLKVector2 dv = GLKVector2Subtract(particleData->positions[i], GLKVector2Make(_origin.x, _origin.y));
GLKVector2 dv = GLKVector2Normalize(dv);
GLKVector2 particle_force = GLKVector2MultiplyScalar(dv, randBetween(0.5f*_force, _force));
GLKVector2 particle_velocity = { _initialVelocity.dx + particle_force.x, _initialVelocity.dy + particle_force.y };
particleData->velocities[i] = particle_velocity;
particleData->angularVelocities[i] = randBetween(-5.0f, 5.0f);
}
}
The idea of having a very simple protocol that classes can implement to perform just the bare minimum, re-usable bit of logic is a good example of the single-responsibility principle, and allows mixing and matching various particle generators to create more complex particle effects.
Particle emitters
The particle emitters are probably the least exciting part of the particle
system design, so I will quickly skip over it. The main task of a particle
emitter is, unsurprisingly, to emit particles. Besides emitting particles, the
emitter also acts as glue between the particle system, and the particle
generators. The way this works is as follows: when creating a particle system,
one or more emitters need to be passed. The emitters are created using a list
of particle generators, an optional maximum number of particles to emit, and
the rate at which particles are emitted (in particles per second). Whenever the
particle system is stepped/updated, it calls an interface function
(emit:dt:particleData
) on all its emitters, each of which will allocate a
number of particles and call the generateParticles
method of its particle
generators to initialize them. The number of particles to allocate is
determined by the emission rate and the number of free slots in the
K14ParticleData
structure.
Right now, only a single particle emitter can be used for a particle system,
but this is just an artificial limitation because I don’t have a use case for
having multiple emitters yet. It’s not hard to come up with one though: for
example a fireworks effect could have multiple emitters with different
configurations of particle generators to make more interesting effects that
activate at the same time. The K14ParticleEmitter
class implements a simple
particle emitter, defined by its emit
method. The code for this method looks
like this:
/**
Emit particles.
@param dt time since last particle was emitted
@param particleData particle data
@return number of particles emitted
*/
-(unsigned int) emit: (NSTimeInterval) dt particleData: (K14ParticleData *) particleData
{
unsigned int count = 0;
self.updateTime += dt;
if (self.emitted < self.maxParticles)
{
if (self.updateTime > self.rate)
{
unsigned int free = (particleData->maxParticles - particleData->currentParticles);
count = MIN(free, (self.rate > 0 ? (unsigned int) self.updateTime / self.rate : self.maxParticles));
for (id<K14ParticleGenerator> generator in self.generators)
{
[generator generateParticles:count sequenceId:self.emitted particleData:particleData];
}
self.emitted += count;
self.updateTime -= (count * self.rate);
}
}
return count;
}
Particle updaters
Updating particles is implemented by particle updaters, which are very much like
particle generators. Just like K14ParticleGenerator
, K14ParticleUpdater
is a
protocol with a single method. For particle updaters the single protocol method
to implement is updateParticles:dt:particleData
.
Particle updaters can be combined in different ways to create different particle
effects, similarly to the particle generators. Because I kind of cheaped out for
the first iteration of my particle system by letting Box2D handle particle physics,
currently only a single particle updater is implemented: the K14Box2DParticleUpdater
.
This updater simply copies the position, angle and velocities of the Box2D body backing
each particle to the particle data structure, which means the particles will have
full physics behavior including collisions, bouncing, etc. The update method for
the K14Box2DParticleUpdater
looks as follows:
/**
Update particles.
This will copy the position, angle and velocities (linear, angular) of the backing
Box2D body for each particle.
@param dt update timestep
@param particleData particle data
*/
-(void) updateParticles: (NSTimeInterval) dt particleData: (struct K14ParticleData *) particleData
{
for (unsigned int i = 0; i < particleData->currentParticles; i++)
{
b2Body *body = (b2Body *) particleData->box2DBodies[i];
b2Vec2 position = body->GetPosition();
b2Vec2 velocity = body->GetLinearVelocity();
particleData->positions[i] = GLKVector2Make(position.x, position.y);
particleData->angles[i] = body->GetAngle();
particleData->velocities[i] = GLKVector2Make(velocity.x, velocity.y);
particleData->angularVelocities[i] = body->GetAngularVelocity();
}
}
Simple enough, right? I do consider this cheating though, because having a heavy-weight object like a Box2D body backing each particle completely defeats the purpose of a particle system designed to handle hundreds of thousands of particles. This is just a temporary solution to get something to the screen though, it’s just a matter of adding particle generators and updaters that don’t involve Box2D to create a particle effect with truly light-weight particles.
Putting it all together
With the particle data, generators, emitters and updaters in place, there’s
not much left for the main particle system class itself, K14ParticleSystem
. Its
main purposes are to bind together the particle data, emitter and updater, and
to expose an API to update the particle system. The single method implemented
by K14ParticleSystem
is update:dt
, which looks like this:
/**
Update particle system.
@param dt update interval in seconds
*/
-(void) step: (NSTimeInterval) dt
{
// Update particle lifetimes,
for (unsigned int i = 0; i < _particles->currentParticles; i++)
{
_particles->lifetimes[i] = MAX(0.0, _particles->lifetimes[i] - _particles->decayRates[i]*dt);
}
// Kill dead particles
kill_particle(_particles);
// Emit new particles
if (_particles->currentParticles < _particles->maxParticles)
{
_particles->currentParticles += [self.emitter emit:dt particleData:_particles];
}
// Update particles
for (id<K14ParticleUpdater> updater in _updaters)
{
[updater updateParticles:dt particleData:_particles];
}
// Flag particle system as 'done' if there are no live particles left, and no new particles can be emitted
_done = ((_particles->currentParticles == 0) && (_emitter.emitted == _emitter.maxParticles));
}
The code above should be self-explanatory. First the lifetimes of all live particles are updated, and those that drop below zero are killed. Then, new particles are emitted, and finally all live particles are updated. If after this process no live particles are left and the emitter is exhausted, the particle system is ‘done’.
Killing particles is implemented by a C-style function kill_particles
, which
for each dead particle will simply copy the attributes of the last live
particle in the particle data structure over the dead particle, and decrease
the the number live particles. This ensures that all live particles are always
consecutive (no gaps) in the first slots of the K14ParticleData
structure,
which makes allocating new particles as simple as bumping the live particle count.
It also allows the particle generators and updaters to always operate on consecutive
arrays without having to check for gaps/dead particles, which means we can write
tighter particle update loops.
I implemented kill_particles
as a plain C-style function for the simple
reason that I wanted to keep K14ParticleData
a C-structure (as opposed to
an NSObject
or a C++ class), but I also didn’t want to separate the ‘particle
killing logic’ from the data structure as it depends on the layout and fields
of the particle data structure. Now that I’m typing this it seems a little
arbitrary, as it should be trivial to wrap the C-style particle data structure with
an Objective-C class to allow proper initializers, destructors, copy semantics
and possibly serialization. I may change this in the future.
Particle rendering
I’m going to leave a more in-depth treatment of the particle rendering for a later post, for two reasons: first of all because the way the particle rendering is currently implemented is quite inefficient, and I already know I want to change it. Second, because this post is already getting a little to long to my taste, and I’d really like to move on with the code ;-)
I’ll quickly summarize the particle renderer in it’s current state. On every
step of the game loop, render commands are created for all active particle
systems, which are initialized with (a copy of) the particle data for each
system. When the renderer first encounters a render command for a new particle
system (as determined by the render command id, which is unique for each particle
system), it will setup a K14ParticleRenderState
instance. The particle render
state consists of 2 vertex buffer objects (VBO’s): one static (cannot be modified),
one dynamic (updated every frame as long as the particle effect is active).
The static buffer is initialized
with vertices defining a unit square, 4 vertices for every particle, up to the
maximum number of particles for the particle effect. The dynamic buffer is
initialized with vertex attributes derived from the particles: position, angle,
size, color, lifetime. Every attribute is repeated 4 times, once for every
vertex of the unit triangle corresponding with the triangle. When it comes to
rendering, only as many elements are used from these buffer as there are live
particles in the particle system.
Rendering the VBO is done using a vertex shader that will read the particle
position and angle from the vertex attribute array, setup a 2D rotate-translate
matrix from it, and transform the corresponding unit square vertex by it.
The remaining particle attributes are passed to the fragment shader as
varying
values. The fragment shader currently simply reads the color
value and multiplies it by the lifetime value.
On every frame, the particle positions, angles, colors and lifetimes in the dynamic VBO are updated, but the unit triangle is unmodified. This way of rendering could be described as “poor man’s geometry instancing”. We basically want to push 2 triangles (1 rectangle) and a set of scalar attributes per particle, but because OpenGL ES 2 does not have built-in default support for instancing, we emulate it by duplicating the unit triangle for each particle, and every particle attribute 4 times (once for each vertex of the unit square).
Suffice to say I don’t like this because even though it’s more than fast enough for the simple effects I’m rendering now, it just feels gross to unnecessarily copy and upload the same information 4 times for each particle. Fortunately, there’s a better way, which I will try to implement and document in the near future.
Lua integration
By design, the particle system design allows a practically infinite variety of
particle effects, by adding additional particle generators and updaters and
combining them the possibilities are limitless. Exposing all this flexibility
to the scripting layer is difficult though: we don’t have automatic binding,
so Lua bindings for every K14ParticleGenerator
and K14ParticleUpdater
would
have to be added manually if we wanted to create a one-to-one mapping of the
particle system API to Lua. Some particle generators can be parameterized
in many ways, requiring many different constructors, construction parameters,
default values, etc, which means manual binding could quickly grow unwieldy.
Instead, I’ve chosen to funnel interesting particle effects through a much
smaller Lua interface, which is defined by some preset particle system
configurations. Instead of directly creating emitters, generators and updaters,
Lua scripts pass in a ‘particle effect definition dictionary’ to a wrapped
native function particleEffect
, which will interpret the definition
and perform the required setup work.
Currently, only a single Lua particle effect has been implemented, which I’ve called the ‘rectangle explosion’. This effect is parameterized using an explosion origin, force, impact vector, an angular spread, and a number of per-particle attributes such as min/max size, allowed colors, etc. The following code snippet illustrates how Lua scripts create particle effects, in this case an explosion effect that is triggered when the player is hit:
if event.type == Game.Event["PLAYER_HIT"] then
-- Player hit. Create explosion effect, and post a delayed 'player killed' event
-- ...
local particle_system_parameters = {
type=Game.ParticleSystemType["RECTANGLE_EXPLOSION"],
duration=1.5,
numParticles=25,
origin=impact_point,
radius=1.0,
minSize=0.1,
maxSize=0.2,
force=5,
angle=math.atan(impact_vector.y, impact_vector.x),
spread=math.pi,
initialVelocity={ x=0, y=0 },
}
planet:particleEffect("explode_player", particle_system_parameters)
-- ...
The native wrapper function that implements the particleEffect
function
is relatively straightforward. I’ll post a small snippet from it that
implements the actual K14ParticleSystem
initialization here, just to
show an example of how the native API for particle systems is used:
// Parse particle system table and crate particle system
K14ParticleSystem *particle_system = nil;
lua_getfield(L, -1, "type");
K14ParticleSystemType particle_system_type = (K14ParticleSystemType) lua_tointeger(L, -1);
lua_pop(L, 1);
//...
switch (particle_system_type)
{
// ...
case K14ParticleSystemTypeRectangleExplosion:
{
NSDictionary *explosion_fields =
@{
@"duration" : @(K14LuaPropertyTypeReal),
@"numParticles" : @(K14LuaPropertyTypeInt),
@"origin" : @(K14LuaPropertyTypeVec2),
@"radius" : @(K14LuaPropertyTypeReal),
@"minSize" : @(K14LuaPropertyTypeReal),
@"maxSize" : @(K14LuaPropertyTypeReal),
@"force" : @(K14LuaPropertyTypeReal),
@"angle" : @(K14LuaPropertyTypeReal),
@"spread" : @(K14LuaPropertyTypeReal),
@"initialVelocity" : @(K14LuaPropertyTypeVec2),
};
NSDictionary *explosion_parameters = [scripting_component.vm stackDictionaryValueWithFields:explosion_fields];
/// \todo use a hard-coded set of particle colors for now
NSArray *explosion_colors =
@[
[K14Color colorWithUIColor:[UIColor redColor]],
[K14Color colorWithUIColor:[UIColor yellowColor]],
[K14Color colorWithUIColor:[UIColor orangeColor]],
[K14Color colorWithUIColor:[UIColor darkGrayColor]],
[K14Color colorWithUIColor:[UIColor lightGrayColor]],
[K14Color colorWithUIColor:[UIColor whiteColor]],
];
unsigned int num_particles = [explosion_parameters[@"numParticles"] intValue];
CGPoint initial_velocity = [explosion_parameters[@"initialVelocity"] CGPointValue];
NSArray *generators =
@[
[K14CircularAreaParticleGenerator generatorWithOrigin:[explosion_parameters[@"origin"] CGPointValue]
radius:[explosion_parameters[@"radius"] floatValue]
angle:[explosion_parameters[@"angle"] floatValue]
spread:[explosion_parameters[@"spread"] floatValue]],
[K14SizeParticleGenerator generatorWithMinSize:[explosion_parameters[@"minSize"] floatValue]
maxSize:[explosion_parameters[@"maxSize"] floatValue]],
[K14ColorParticleGenerator generatorWithColors:explosion_colors],
[K14ExplosionParticleGenerator generatorWithOrigin:[explosion_parameters[@"origin"] CGPointValue]
force:[explosion_parameters[@"force"] floatValue]
initialVelocity:CGVectorMake(initial_velocity.x, initial_velocity.y)],
[K14DecayRateParticleGenerator generatorWithMinLifetime:0.5f maxLifetime:[explosion_parameters[@"duration"] floatValue]],
[K14Box2DParticleGenerator generatorWithWorld:scripting_component.planet.world density:1.0],
];
K14ParticleEmitter *explosion_emitter = [[K14ParticleEmitter alloc] initWithGenerators:generators maxParticles:num_particles rate:0.0f];
NSArray *updaters = @[ [K14Box2DParticleUpdater new] ];
particle_system = [[K14ParticleSystem alloc] initWithEmitter:explosion_emitter updaters:updaters maxParticles:num_particles];
break;
}
// ...
}
Video
Obviously, I also made a video of some particle effects I quickly hacked into the game when entities or the player are hit. These are purely for illustration purposes, some finetuning is needed to improve the appearance and behavior of the effect.
Some unrelated tidbits
In parallel with the particle system work, I did some ‘collateral
cleanup’ that was either just bothering me, or was broken by stuff
added to K14Game
and K14Planet
classes to support particle system
effects. Most of it related to freezing/unfreezing Lua game state, and
one-off/post-create initialization of Lua wrapper classes. Nothing
terribly exciting so I’ll just summarize:
-
Delayed events
To allow effects such as exploding entities when they are hit, a mechanism was needed to introduce some time between the hit, and the ultimate removal of the entity from the game state and any possible further events it may trigger (respawning, for instance). The concept of ‘delayed gameplay events’ was added to the event handling logic in the Lua
Game
class. Instead of always processing gameplay events directly when they are generated, they are now queued up with a timestamp, and handled when the elapsed game time reached this timestamp. This means the event queue can persist over multiple ‘ticks’ of the gameplay update loop, which introduces some new problems serializing Lua state, as explained in the next list item. -
Lua wrapper class serialization
When freezing/unfreezing a game, properties of Lua wrapper classes for the game, planet and entities are now serialized completely from Lua, using the Lua marshal module. Previously, every Lua wrapper class had to advertise the names and data types of all instance properties to include when serializing, using a ‘property table’ class variable. The native scripting interface classes code would use the property table to fetch the property values and convert them to native types (
NSValue
,NSString
) that were included in theNSArchive
as anNSDictionary
. This was unnecessarily complex, and when I realized more complex/structured data types besides scalars and strings needed to be serialized (delayed gameplay events, for example) I decided to change it to something much simpler.Every Lua wrapper class can now implement optional
freeze
/unfreeze
methods, the former of which should produce a string representing serialized instance data for the class, the latter of which should take a string, parse it, and assign instance variables from the deserialized data. The scripting interface class will call these methods and simply store/retrieve the serialized string. Since we’re only serializing POD types (numbers, strings) and tables, no object references or Luauserdata
, serialization is trivial: just create a table of name-property pairs and serialize/deserialize using Lua marshal. -
Wrapper class initialization
One-off/post-create initialization of Lua wrapper classes used to be handled by the native scripting interface classes, by means of explicit calls to the
init
method of each wrapped instance. This had been bugging me for a long time already, because initialization order dependencies prevented doing theseinit
calls automatically on construction of theK14LuaComponent
scripting interface base class. Instead, post-create initialization calls were hard-coded at various ‘strategic’ (read: more or less arbitrary) points.To make a long story somewhat shorter, all one-off/post-create Lua wrapper class initialization is now purely done from Lua, no native code is involved. The trigger is the
Game:started()
callback which is always called exactly once for a game (restoring a game from a snapshot will not trigger this callback). This removes the need for explicit initialization calls from places that should not have to know about the scripting layer at all, such as the view controller that used to kick off the initialization process from theviewDidAppear
callback.
Next steps
At the top of my todo list is improving the particle system rendering, to eliminate the wasteful copying/duplication of particle coordinates and attributes. After that, I’d like to use the particle system API to create some more visual effects, to stress the API and to liven up the game a little. I may also experiment with particle systems that are not backed by Box2D, with significantly more particles, and more interesting rendering, for example smoke, fire, textures, etc.
Development scoreboard
Total development time since the last update increased by about 25 hours, a not-insignificant part of which was spent on refactoring the wrapper class initialization and serialization code. I wasted quite a bit of time using a different Lua serialization module which seemingly randomly corrupted serialized data, for example. Total development is now about 309 hours. SLOC count is at 8964, an increase of 1279. Lua SLOC count increased by 116 to a total of 889.