With most of the under-the-hood stuff discussed in the last few posts out of the way, in this post there’s finally some fresh additions to the game to discuss again :-). I’ve finalized support for modifiable planet components, and implemented some very basic switches that can trigger gameplay events, in this case hooking up a servo to an actuator to move around parts of the planet topology.
Actuators
Because most of the preparation work had been done already in the form of servos’s, planet definitions and render buffers, all that was needed to put verything together was some way to express the kinds of modifications we would like to apply to the planet topology, and to be able to hook them up to a servo. For this, I introduced the ‘actuator’ concept, which can be seen as a natural extension of the ‘servo’ concept.
Actuators transform an input signal to some kind of effect on the game world. In theory, this could be anything, but for now we are mostly concerned with actuators that modify planet topology. An actuator can be bound to a servo, which provides it with an input signal, defines the time interval during which the actuator should be active, whether it should loop, etc. Just like the servo does not need to know how its signal is interpreted by any actuators it is bound to, the actuator requires no knowledge about the signal that drives it. It just maps the signal value to the operation it implements, and applies it when asked to do so. It’s up to the game- and planet scripts to make sensible combinations of servo’s and actuators.
Actuators are implemented using an abstract base class K14Actuator
, and
concrete subclasses that implement the actual actuator logic. The K14Actuator
interface is very simple: it has a single apply
function that takes a
K14PlanetComponent
instance, and a servo
property from which the actuator
signal value is read. The servo
property is read-write and can be nil
to
disable (temporarily or indefinitely) the actuator. The actuator is applied to
a planet component by calling its apply
function as part of the planet step
method, changing one or more of its properties. For example the planet topology
can be changed by setting the component polygon
property, which will ensure
the new polygon is triangulated, and its Box2D edge-chain fixture is updated to
reflect the new shape. Other component properties that might be interesting to
modify from an actuator could be the component color, its transform matrix (to
translate, rotate, scale the component), etc. The possibilities are basically
endless ;-)
The following simplified design diagram shows the classes involved, with just the properties and methods that are relevant to the actuator logic:
Currently only a single actuator type K14EdgeMoveActuator
is implemented,
which (unsurprisingly) moves an edge of the planet component it is applied to.
When the actuator is applied to a component, it will lookup the edge the
actuator was constructed with and move it along its edge normal, by the
movement distance calculated from the actuatuor’s servo value. Moving the edge
is done by constructing an (infinite) line through the original (unmodified)
edge of the planet component, offsetting it by the movement distance, and
intersecting it with the two lines through its previous and next edge in the
current (possibly modified) component polygon. This way the reference point
for the edge move (the original edge) is always the same, and the movement
vector (the edge normal scaled by the actuator value) can be interpreted as an
absolute displacement. Additionally, multiple edges of the same component
polygon can be moved by applying multiple actuators in succession, without
canceling each other out. The following figure illustrates how the edge move
actuator would work on a polygon of which the topmost edge has already been
offset:
The edge move actuator at this time does not handle moving edges in a way that would result in disappearing (zero length) edges or self-intersecting polygons, it’s up to the planet script to prevent this from happening.
All of the actuator code is implemented as part of the core engine, using
native code. Creating actuators and binding them to servo’s is done by the Lua
Planet
subclass that implements planet logic though. To test the actuators,
I’ve added a new planet Planet1
which besides the main planet surface
component also has a second rectangular component representing a ‘hatch’ that
is opened and closed by means of an edge move actuator. The following code from
the Lua Planet1
class creates the actuator:
-- Initialize planet
function Planet1:init()
local actuator_def = { type=Game.ActuatorType["EDGE_MOVE"], edgeName="right" }
self:createActuator("hatch_0_actuator", "hatch_0", actuator_def)
end
Looks easy enough right? ;-). The parameters to the createActuator
call are
the name of the actuator, followed by the name of the planet component to apply
the actuator to, and the actuator definition itself. Eventually, I’d like to
be able to directly define actuators in the JSON planet definition, but for now
this works fine as well.
Two minor things worth mentioning are related to performance considerations:
first of all the K14Servo
class now has an optional resolution
parameter,
which if set to a non-zero value will cause the servo signal to be quantized
to the requested resolution. For example setting the servo resolution to 0.1
will result in the signal value to be incremented/decremented in steps of at
least 0.1. In addition to that, the K14EdgeMoveActuator
will automatically
cache its servo value. If the servo value did not change, applying it to a
planet component will not change any of its properties. Together, these two
things prevent planet components from being updated, retriangulated, resubmitted
to the renderer, etc. on every frame. Increasing the resolution of a servo
bound to an edge move actuator saves some precious cycles in exchange for
some ‘jagginess’ in the edge movement.
Switches
Next up after the actuators were switches, to be able to add ‘doors’ that open
and close in response to the player triggering the switch, for example by
shooting it. I’m not going to spend a lot of words on these, because the
implementation is very straightforward. I basically added a new entity type
with an entity-entity collision handler that will send a new event type ‘switch
triggered’ to the Lua Game
instance, which will forward it to the Lua
Planet
class that will handle it. By relaying the event through the Game
instance (like all other gameplay events), things like sound effects or other
side-effects of the switch being triggered can be added later. The switch
entity is displayed using a hemisphere sprite, similar to the switches in the
original game.
The following snippet shows the Lua script for the switch entity, which is called ‘RoundSwitch’. As you can see it does not amount to not much more than a sprite and a collision callback:
-- Implements the 'RoundSwitch' entity
RoundSwitch = {}
RoundSwitch.__index = RoundSwitch
setmetatable(RoundSwitch, { __index = Entity })
-- Static properties & constants
RoundSwitch.size = { width=1.0, height=0.7 }
RoundSwitch.entityType = Entity.Type["STATIC"]
RoundSwitch.spriteFrameImages = { default="round_switch_sprite_default" }
-- Constructor
function RoundSwitch:new(entity)
-- Create wrapper instance
local instance = Entity:new(entity)
setmetatable(instance, RoundSwitch)
-- Create fixture
instance:createBoxFixture("round_switch", RoundSwitch.size, 1)
return instance
end
-- Return current entity sprites
function RoundSwitch:getSprites()
local default_sprite =
{
spriteFrame="default",
position=self:getPosition(),
size=self:getSize(),
angle=self:getAngle()
}
return { default_sprite }
end
-- Entity-entity collision callback. This will fire a 'switch triggered' event to the game
function RoundSwitch:didCollideWithEntity(entity)
if entity.entityType == Entity.Type["PROJECTILE"] then
self.game:postEvent(Game.Event["SWITCH_TRIGGERED"], self, {})
end
end
I’ll skip the event handler in the Game
class because it doesn’t do anything
interesting, it just checks if the Planet
class for the currently running
game has a switchTriggered
method, and if so calls it. More interesting is
the code that triggers the switch logic:
-- Called when one of the open/close switches is triggered
function Planet1:switchTriggered(switch)
local game = self:getGame()
local open = { type=Game.SignalType["LINEAR"], period=2, offset=0, inclination=(1.1-8) }
local wait_open = { type=Game.SignalType["UNIFORM"], period=3, value=(1.1-8) }
local close = { type=Game.SignalType["LINEAR"], period=2, offset=-(8-1.1), inclination=(8-1.1) }
local elapsed = game:getElapsed()
game:createServo("hatch_0_servo", { open, wait_open, close }, elapsed, elapsed + 8, 0.05)
self:setActuatorServo("hatch_0_actuator", "hatch_0", "hatch_0_servo")
end
When the switchTriggered
method is called, a new servo hatch_0_servo
is
created with a compound signal which first goes down from 0 to -7.9, waits for
3 seconds, then goes back up to 0. The servo is then bound to the
hatch_0_actuator
, which will move the rightmost edge of the hatch_0
component by the servo value, where 0 means ‘fully closed’, and -7.9
corresponds with (almost) the full size of the hatch and means ‘fully open’.
I’m not very happy with the hard-coded offset
and inclination
values, that
directly correspond to movement distance, so I’ll have to figure out something
better later. A percentage or ‘minimum distance to adjoining edge’ would be
better.
Unlike to the servo/actuator logic and the changes required for rendering, the
switch logic is completely implemented on the Lua side of the game. This allows
fully dynamic and configurable switch behavior implemented by the Game
and
Planet
classes, while all the heavy lifting is done by native code. From the
Lua perspective, actuators and servo’s are used in a ‘fire and forget’ way:
once the actuator is created, the simple act of assigning a servo will ensure
the actuator will do its work for as long as the servo provides a signal.
The renderer
The only changes to the renderer/render buffer code are related to updating the
render commands for planet components. Render commands for each planet
component are now recreated whenever the component topology changed, which is
tracked by means of the lastUpdateTimestamp
property of the
K14PlanetComponent
instance. Inside the renderer itself, a similar mechanism is
used to update render state for polygon render commands: using the name
and
timestamp
properties of the K14RenderPolygonCommand
the renderer determines
when render state such as vertex/index buffers need to be released and
recreated. Whenever the timestamp of the render command increases, the render
state needs to be recreated.
Video
After a long and dark lapse without any gameplay video’s, we finally have something new to show ;-). The following video shows a replay of the test planet I created to test the switch and actuator logic:
Next steps
Still high on my to-do list are animation, special effects such as explosions, and more flexible sprite coloring. Right now I’m leaning towards sprite coloring, I’d like to implement some kind of palette-based sprite rendering where sprites are created using color channels, which are mapped to a configurable palette by a shader. This would allow changing sprite colors to match the planet surface, or to palette-cycle their colors at runtime. I’ll have to see what I’ll tackle first.
Development scoreboard
Implementing the actuators and switches took about 5 hours, for a total
development time of ~280 hours. Native code SLOC count increased by
399 lines, for a total of 7571. Lua SLOC count went up by 22 lines, most
of which were spent on the Planet1
and RoundSwitch
classes. Some
stray testing/debugging code was removed from other Lua classes. Lua SLOC
count is now at 671.