Switches, actuators

10 Feb 2016

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.