|
Page 5 of 6
Although we just saw how the Scripted Event System works in conjunction with Behaviors and Robot Functions, we haven't really seen the true power of this union yet. Behaviors and Robot Functions were not originally written to be used in cinemas, they were meant to let you share very diverse functionality between unrelated objects. For example, your save menu uses the same RobotFunction to fade out as your defeated enemies, your monkey uses the same Behavior to animate as your MadStones, etc. This allows you to create totally new, high-functioning objects in the game or in the GUIs quickly and easily by building them with a shared code base of Behaviors. This usually covers a great deal of their capabilities for free.
Now we'll look at the other places Behaviors and Robot Functions are normally used and see what the Scripted Event System brings to the table.
Scripts for GameObjects
I quickly realized that this system had great uses far beyond just cinemas. All sorts of objects in the game could benefit from scripts just like they benefit from Behaviors. One of the first uses I thought of was applying the Scripted Event System to the monkeys' actions in Primate Panic, such as fruit picking. When you throw a monkey and it touches a piece of fruit, a few things happens in quick succession. Here are the important ones:
1. The fruit is logically removed from the grid so that it can only be picked once.
2. The monkey quickly slides to align itself with the grid square of the fruit.
3. The monkey shakes the fruit up and down.
4. A sound effect starts to play.
5. A "dew drop" visual effect starts to play.
6. The fruit starts to fall.
7. The monkey decides what to do next and hops off the fruit.
Replacing Old State Machines
Just like my first idea for a cinema, my first idea to accomplish this fruit picking action was to use a state machine. The difference was that there were only about 7 actions that needed to happen when a monkey picked a fruit, as opposed to dozens or hundreds for a cinema, which is why the state machine worked reasonably well. Still, the code was fairly complicated, tough to read, and reproduced in large part when the monkey performed similar actions. This all seemed like bad design.
The Scripted Event System was a perfect fit. Instead of using several states with unrelated commands thrown into each, all I needed was a script. Here's a much better way to do it in pseudo-code, improved just a bit from the way I actually coded it:
# Create a script where the listener is the monkey
local ActionQueue script = ActionQueue(this)
# The two movement RobotFunctions (x and y) are combined
# in an ActionList. The two robots finish at the same
# time, so we'll just listen to one of them.
local ActionList sliderList = ActionList()
local RobotFunction xMover = SlideFn(monkey.xPtr ...)
local RobotFunction yMover = SlideFn(monkey.yPtr ...)
sliderList.addFirst(xMover, true)
sliderList.addFirst(yMover, false)
script.enqueue(ClearSquareCommand(fruitCoords))
script.enqueue(sliderList)
script.enqueue(ShakeBehavior(this, fruit)
script.enqueue(PlaySoundCommand( SOUNDS.pick ))
script.enqueue(PlayEffectCommand( DewDropEffect() ))
The Scripted Event System made the fruit picking actions much simpler to code, more flexible, easier to read, and far fewer lines than my state machine version. The last 5 lines of code correspond directly to the first 5 actions of the description we saw earlier. It would be tough to represent the five actions much simpler than that.
I like this example because it also leverages one of the most important aspects of the system: that ActionLists can be part of scripts. In this case, we just used two Robot Functions to slide the monkey on to the grid square. Since the two functions have to operate at the same time, we encapsulate them into an ActionList and just listen to one of the callbacks.
The system is not a straight jacket
You might be wondering about the last two steps (the fruit falls, and the monkey decides what to do next before jumping off the fruit). Of course, you could handle both of these using commands and put them at the end of the script. However, this isn't really necessary. It's much simpler just to have the monkey listen for the end of the 5-part script (which it is already) and then perform those last two actions on its own, in reaction to the script ending. I personally prefer this way because the commands that would correspond to those last two actions are very specific and depend on a lot of context (particularly the second one). Why make complicated commands that you don't plan to reuse anywhere else when you can easily avoid it?
The Scripted Event System is all about convenience. If some actions are easier to implement outside of the script, put them there! Often times, there are many different ways to apply the Scripted Event System to the same task. How you choose to organize the parts of the script, the complexity of each item in the script, and what happens outside the script is all up to you. Finding the way that suits you best is an art form. It's actually a lot of fun! The Scripted Event System provides you with a bunch of new options, and you just pick the one you like the best.
|
Good job!
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.
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?
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.
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.