RiverMan Media

RiverMan Media 1

Newsletter

Keep up with info on our latest games, articles, and other projects. You can unsubscribe at any time.

Subscribe

XML RSS 2.0 RSS 2.0 

Object Oriented Game Programming: The Scripted Event System
Object Oriented Game Programming: The Scripted Event System - ActionLists
Written by Paul Stevens   
Thursday, 02 October 2008 03:37
Article Index
Object Oriented Game Programming: The Scripted Event System
ActionEvents and Commands
ActionLists
Example 1: Cinemas
Example 2: Gameplay
More Examples
All Pages

Let's review what we have so far. The Script class (which I refer to as an ActionQueue in code), can enqueue ActionEvents and Commands to a list. When it updates, it executes all of the commands at the beginning of the queue right away, then calls update() on the first ActionEvent it encounters. This ActionEvent could be a Behavior, an Effect, or a Robot Function. ActionEvents are removed from the head of the queue when they inform the script that they are done running, through the use of a callback() method. Once the entire script is done, it informs its listener, also by using a callback() method.

Scripts alone aren't enough
Here's what we are lacking with just straight scripts: we can only have 1 action running at once in the script, and sometimes several events need to happen at once. For example, let's look at the cinemas in MadStone. When you beat a level, the MadStone performs its animation as it moves down toward the ground, and once it gets the there landscape starts flooding with color as the MadStone sinks from view. Right now, let's just look at the first part of the cinema: The MadStone begins to move down as it plays its animation. The animation finishes, and shortly after, the MadStone actually hits the ground, setting off another series of events.

The problem here is that with just scripts, you can have the MadStone move downward OR have it play its animation, but not both at the same time. This is because the script has no way of knowing that you want to run two ActionEvents at the same time, it just always runs them in order. One way to get around this would be to combine its moving down action with its animation action into a single Effect (maybe call it MoveAndAnimateMadStoneEffect) and have the script run that, which would work just fine. Why don't you want to do this? Because your complicated cinemas are going to have dozens of these cases where you need to combine several Actions into a single item in the script. This would mean you are writing dozens of tiny, almost pointless classes to combine your effects. Changing the order and timing becomes a real pain as well.

Solution: ActionList combines several ActionEvents
What we need is a way to combine several ActionEvents so that they can be treated as a single ActionEvent by the script. The solution is to create an ActionList class. This is exactly what it sounds like, but with some important extra features. ActionList, just like ActionQueue, contains a list of ActionEvents. The difference here is that instead of treating the list like a queue and only running the first item in the list, the ActionList calls update() and redraw() on all of its members every frame. This means you can have several Behaviors, Effects, and RobotFunctions all acting in the same ActionList at once.

But how does this help us solve the queue issue we were talking about earlier? How can we treat an entire list of Behaviors/Effects/etc. as a single queue item in the script?

ActionList incorporates ActionEvent, ActionListener
Just like RobotFunctions, Effects, and Behaviors, ActionLists incorporates the ActionEvent aspect. All this means is that you can call update() and setListener() on it. The update() method updates all of the members in the list. When an ActionList is a member of a script, this allows all of the ActionEvents in the list to update() as if we were just dealing with one ActionEvent. The setListener() method guarantees that when an ActionList is inserted into a script, the script can set itself to be the listener of the ActionList, just like it does when you put a single Behavior or RobotFunction into the script.

ActionList also needs to be an ActionListener so that it can hear the callbacks() of the members in the list. This way it can figure out when the members of its list are done running and can inform the script.

ActionList and callback()
Now the question is, how do we deal with the callback() from an ActionList? With a single ActionEvent, the solution is obvious: it invokes "listener.callback(this)" when it is done running. But how do you decide when a list of several ActionEvents is done running? Is it when the first one is done? Not quite. Remember in the example from above, the MadStone finishes its animation before it is done moving, and we don't want the script to move on yet. Is it when ALL of the elements in the list are done running? Not necessarily. Imagine that we wanted to change how the MadStone cinema worked so that the animation plays in a loop while the MadStone sinks down. This might happen if you wanted say, the pumpkin MadStone to laugh continuously while it was sinking. One of the ActionEvents needs to repeat constantly, and the script only moves on when the other ActionEvent is finished.

Set listener during node insertion
What we need to be able to do is tell the ActionList which callbacks to be listening for. When an ActionEvent is added to an ActionQueue (script), the ActionEvent's listener is set to be the script itself. However, with an ActionList, we only want to listen to certain callbacks. All we need to do is add one extra boolean parameter to the insert method, listenToCallback. This will tell the ActionList whether or not it should set the ActionEvent's listener to be the list when the ActionEvent is added to the list. In the case of the laughing pumkin example, the AnimationBehavior would be added to the list without setting its listener, but the RobotFunction that moves the pumpkin would have its listener set to be the list.

ActionList calls back at first chance
Now we can group together several different ActionEvents (Behaviors, Effects, RobotFunctions, etc.) that all update() with a single call. They are all treated as a single ActionEvent so that they can be added to a script. And some of them will invoke listener.callback() when they are done running (where "listener" here is the ActionList). Whichever ActionEvent invokes ActionList.callback() first will then cause the ActionList itself to invoke listener.callback(). The ActionList's listener may be another ActionList, some other Object that is interested, or a script. The main purpose of this discussion was to examine the case when the ActionList's listener is a script, so let's see what happens now.

Finally: an ActionList as a script element
The first ActionEvent to invoke callback() on the list causes the list to invoke callback() on the script, and the script moves on just like it would with any normal ActionEvent. Finally, we can solve the problem of the laughing pumpkin MadStone very easily:

ActionQueue script = ActionQueue()

RobotFunction mover = RobotFunction...
AnimationBehavior laughAnim = AnimationBehavior...

ActionList animList = ActionList()
animList.add(mover, true) # listen to this callback
animList.add(laughAnim, false) # ignore this callback

script.enqueue(animList)

It will start moving, start laughing, and laugh until it hits the ground, at which point the script will continue with whatever is next.

Auto-remove passive ActionEvents
Of course, ActionLists aren't just useful in scripts. One of their most common uses is as an ongoing list of Behaviors or Effects, where you just throw an Effect into the list as it is created. The effects in the list run, and remove themselves when they are done running. The easy way to accomplish this is to add a little bit more to the ActionEvent aspect: a getter and setter for an "active" boolean. If an ActionEvent is not active (meaning it is done running) just remove it from the list. ActionEvents will set this flag on their own as they run.

Type summary
There is a lot of complicated typing going on here, so let's review what we've got before moving on. In this case, Slag aspects are being used just like Java interfaces. They require that implementing classes have the method stubs of the aspect.

underlying aspect ActionEvent
underlying aspect ActionListener

abstract class Behavior : ActionEvent
abstract class RobotFunction : ActionEvent
abstract class Effect : ActionEvent

class ActionQueue : ActionEvent, ActionListener
class ActionList : ActionEvent, ActionListener

Note that some instances of Behaviors and Effects may also be ActionListeners, depending on what they do.

Benefits of this type structure
Two of the most important things to see from this are that ActionLists and ActionQueues (scripts) are themselves ActionEvents. This means that you can put a script into an ActionList, an ActionList into a script, a script into a script, and an ActionList into an ActionList. All of these features combined make the system extremely flexible and easy to use. In fact, I frequently use all of these features when designing cinemas or other scripted events.

And you'll never have to worry about type safety, since you have taken care of it all using aspects. The only time casting is required in the whole system is when the scripts have to decide whether to execute() a Command or update() an ActionEvent. You could probably even get around this one cast operation if you wanted, it's just an implementation decision.

Now that we've covered what the Scripted Event System is and how it works, it's time for the payoff: what it can do.



 
Comments (5)
Nice
1 Friday, 16 January 2009 17:32
Thanks for sharing your ideas! I just may use them in my projects, although I'm coding in Flash AS3, i think it could work just as well.

Good job!
How to access intances of objects?
2 Wednesday, 20 May 2009 12:02
HexDump
Hi,

Thanks for sharing this nice iformation. I have implemented the ideo on c# but there's something I can´t find out to do it in a clean way. Imagine this script (Each step is one EventAction):

1) Play Anim
2) Create Particle system
3) Animate with a robot the Particle system position.

How do you handle the third point? You need the particle system instance the robot funcion needs to animate?

Thanks in advance,
HexDump.
RE: How to access intances of objects?
3 Wednesday, 20 May 2009 17:45
RiverMan_Paul
Thanks, I'm glad you liked the article! That's a very good question.

The easiest way to do this is basically what you said: instantiate the particle system while you are creating the script and pass it to the command (or behavior, or whatever) that is responsible for the animation. You can organize the particle system's code so that it doesn't load any data just for instantiating it. That way, it isn't taking up much memory until it is supposed to actually run, at which point some item in the script can call particleSystem.load(), and start things running. I almost always use this approach because it is the simplest.

If you really don't want to instantiate the particle system before hand, there are a few options. One is to set up a "global" variable somewhere and pass a reference of it to the parts of the scripts in step 2 and 3. That way, when step 2 creates the system, it's already ready for step 3, which knows to look at the same object. The particle system could be placed into any kind of data structure, as long as both parts of the script know how to access the particle system once its created.

Option 3 would be to create a special command that encompasses steps 2 and 3, generating a particle system and passing to the animation. Again there are quite a few choices for how to do this, but probably the easiest way would be to create a step 2.5 command that facilitates communication between steps 2 and 3, making sure that both have the same data. You could make the input parameters for this communication command general enough so that you can reuse it in other situations (it doesn't have to be completely specific to this problem).

Would any of those solutions work in your case?
More problems....
4 Wednesday, 20 May 2009 21:51
HexDump
Hi, and thanks for the answers.

Well, I solved the problem more ore less, I have to rethink about it, thanks for the options you offered.

I´m noticing that things are getting complicated. I for example waht to create an effect that is like a meteor rain. This leads me to think that I will need something to do loops, and if you add to this that you want to listen to collisions in the script things get more and more complicated.

For the collision thing I managed to create a WatColliison action that could do the job, but for the loop thing (because you need to create lota of balls that are particle system and do same for all, it is like having 1 script thread per particle system) I can´t get a good solution.

My game is something like Robot Wars where cinematics are really important.

Thanks in advance,
HexDump.
RE: More problems....
5 Friday, 22 May 2009 05:58
RiverMan_Paul
If you wanted to create something like a meteor rain, would it be necessary to use a complicated script setup, or would it work to have MeteorRain be it's own class that runs alongside the script?

Usually for complicated cinematics, I have an ActionList that encompasses and runs the main script (because scripts can be added to ActionLists, and vice-versa). The script adds ongoing effects (such as a meteor rain) to the list when necessary, and then the script tells it to stop when it is supposed to be done.

Add your comment

Your name:
Your website:
Subject:
Comment: