Experience the power of Robot functions in your game code. You’ll use them everywhere!
What is a Robot Function?
A robot function is a small object that accepts a reference to an external variable, and manipulates that variable over a given period of time. At the end of the specified time, the value of the variable will be set to a target value. The external variable reference is passed in when the robot function is created, and the value is modified based on the robot function being used, and a few other initializing parameters. I call them “Robot Functions” because you all you do is pass them a reference and a couple of parameters, and then just call update() on them every game cycle and they do the work on their own.
I came up with robot functions for use in MadStone, as an expansion to the “Behavior System” that I invented for Primate Panic (there will definitely be an article on behaviors soon). Robot functions stand on their own though, and don’t need to be connected to any other systems to function. However, when you put them in the context of bigger systems, such as the “Scripted Event System” (article on this soon also), they become incredibly powerful.
What are they for?
Any time you want to change a value–any value–over a period of time, with a specific function curve. Here are some ideas:
- A GUI widget needs to slide into view at a constant speed. Give the robot function a reference to the widget’s x coordinate, set the end value of the robot function to wherever you want to widget to end up, and set the duration to how long you want it to take.
- An effect needs to fade out. Give the robot function a reference to the effect’s alpha, the ending value is 0.
- A trap door needs to rotate 90 degrees to open. Give the robot function a reference to the fireball’s rotation value, pass in 90 degrees as the ending value.
- A ship that was just destroyed needs shrink and wink out of existance. Pass in a reference to the ship’s image scale, have the robot reduce it to 0.
- Something is burning and should turn black. Use three robot functions to simultaneously reduce its red, green, and blue values.
In any of the above cases, you can use a different robot function to change “how” the variable reaches its final value. For example, let’s say that you want a gui window to fall in from the top of the screen. You could use a “slide” robot function that changes the window’s y coordinate at a constant rate. But maybe instead you want it to look like it’s falling with gravity. All you have to do is change to a constant acceleration robot function. Is it’s veloity curve a little too steep with constant acceleration? Switch to a sine robot function. It will bring the y value from its starting point to it’s ending point using a smooth sine wave. If implemented correctly, the only thing you will have to change when switching between these robots is… the name of the robot you use when calling the constructor.
How do they work?
There are plenty of different ways you could implement the concept, but it makes the most sense in an object oriented language like Java or slag. I’ll explain how I implemented my version of them for MadStone. I’ll do my examples in Slag because it’s really easy to see what’s going on from Slag syntax.
RobotFunction is an abstract superclass. Almost the entire behavior of your robot function objects can be done in this superclass. You’ll see how in a second, and why it’s important. The RobotFunction class does the following:
- Contains a constructor method that all of your specific (child) robot functions will use. the constructor takes 4 values: the external variable reference, the end value, the duration, and a reference to the listener.
- Contains a variable that keeps track of time elapsed. Every time update() is called, the time elapsed should increase based on how much time has actually passed.
- Stores the starting value of the external variable passed in, stores the ending (target) value, which is also passed in.
- Contains an “active” boolean, which will keep the robot from updating if active is false.
- update() method: updates time elapsed. If the time passed is greater than the duration of the robot function, set active to false, set the external variable’s value to the end value, and inform the listener. Otherwise, call an abstract method implemented by subclasses, updateFunction().
- updateFunction() method: This method is abstract in the superclass. In most robot functions, this is the ONLY method that subclasses need to override. This is where the robot calculates what the value of the external variable should be based on the time elapsed and the type of robot.
Here’s a summary of how it will look in code:
abstract class RobotFunction
varReference : RealPtr # RealPtr is a class that just contains a single Real64 value
startValue : Real64 # The initial value of the varReference
endValue : Real64 # The target value of the varReference
duration : Real64 # how long the robot will manipulate the value, in seconds.
currTime: Real64 # how much time has elapsed since the robot started to update()
active : Boolean # an easy way to turn the robot on and off
listener : ActionListener # A reference to whatever object is waiting for the robot function
# to be done, if any.
method init(varReference, endValue, duration, listener):
active = true
currTime = 0
startValue = varReference.value
if (active == false) return
currTime += GET_TIME_PASSED() # since the last update
if (currTime >= duration)
varReference.value = endValue
active = false
if (listener isNot null) listener.callback(this)
abstract method updateFunction():
# Slide robot function is a subclass of RobotFunction:
# it changes the value at a constant rate
class SlideFn : RobotFunction
realReference.value = startValue + (timePassed / duration) * (endValue - startValue)
timePassed / duration is the percentage of the duration that has elapsed. endvalue – startValue is the total amount that the value should change by the end of the duration. Multiplied together, the percentage of the duration that have passed and the total change gives you the current change. This is added to the starting value of the variable to give you the final value for this update.
As an added bonus, your robot functions will work properly even if the game’s frame rate is not constant. This is because the update() function polls the game every time it updates to see how much time has passed since the last update, and changes the external variable’s value based on the total PERCENTAGE of the duration that has passed. As you can see, we don’t have to rely on any previous data to calculate where the value should be at any give time. No matter how crazy the frame rate is, at any given update, the value will be exactly where it is supposed to be, based on a constant change over time.
ActionListener is a Slag aspect (or Java interface) that contains a “callback” method, so that the robot function can inform its listener when it finishes. At the simplest level, this allows you to do something when the robot function is done without polling the robot every frame.
Let’s look at a slightly more complicated example, an acceleration robot function. Actually, the acceleration function is only a few more lines than the slide function. All it requires is one extra variable and one extra line in the constructor. Here’s what it looks like:
# Accel robot function is a subclass of RobotFunction:
# it changes the value at a constant acceleration.
class AccelFn : RobotFunction
accel : Real64
method init(varReference, endValue, duration, listener):
prior.init(varReference, endValue, duration, listener)
# now we just calculate the acceleration, using the formula for displacement:
accel = 2.0 * (endVal - startVal) / (duration * duration)
# Use the same displacement formula to calculate the value.
realReference.value = startValue + 0.5 * accel * currTime * currTime
Here is a simplified example of how the AccelFn is actually used in MadStone to get the menus to fall from the top of the screen (glossing over some details). Basically, the window image’s y coordinate reference is passed to an AccelFn, and this updates until the time has elapsed and it has reached the middle of the screen. Then, the gui’s state changes to active, and the menu is ready for use.
class FallingGUI : GUI, ActionListener
windowX : RealPtr
windowY : RealPtr
windowImg : Image
fallFunction : RobotFunction
state = FALLING : Int32
windowX = RealPtr(2.0)
windowY = RealPtr(-5.0)
windowImg = Graphics.WINDOW
# add widgets....
# The duration is 0.65 seconds
fallFunction = AccelFn(windowY, 3, 0.65, this)
# don't need to worry if the function is done--it turns itself off.
method callback(ActionEvent e):
# This gets called when the robot is done.
if (state == FALLING)
state = ACTIVE # (activate the widgets)
And all I would have to do to make it slide down with a constant velocity rather than fall is change 1 line:
fallFunction = AccelFn(windowY, 3, 0.65, this) becomes....
fallFunction = SlideFn(windowY, 3, 0.65, this)
Even the parameters stay the same.
But this is just the tip of the iceberg. My next programming articles will put robot functions in the context of the Behavior System and the Scripted Event System. If you liked the idea behind robot functions, you’ll probably really like those articles.