• Welcome to Freedom Reborn Archive.
 

Cutscenes in FF - by Dr. Mike - 9/30/2004

Started by joemama, February 01, 2007, 06:01:46 PM

Previous topic - Next topic

joemama


Cutscenes in FF - by Dr. Mike - 9/30/2004

#####################################



And another one. Couldn't help myself, honest.

CUTSCENES IN FREEDOM FORCE
by Dr Mike

Cutscenes are one of the trickiest and least obvious bits of code in FF.
This covers how they work, and common pitfalls to avoid.

Here's a cutscene from the Strangers, abbreviated a bit:

lose_cs=[
(
    \"peace()\",
    \"wait(3)\",
   \"see()\",
),
(
    \"Camera_Fade(0)\",
    \"revive('queenofhearts')\",
   \"teleport('queenofhearts','pos_ql1')\",
   \"Camera_LookAtMarker('pos_ql1',-200,-12,-90,0,CPM_SNAP,CA_MOVE)\",
   \"wait(1)\",
),
(
    \"turn('queenofhearts','pos_prisoner9')\",
   \"speak('queenofhearts','MISSPCH_13_QH_L09')\",
),
(
    \"Camera_LookAtMarker('pos_ql3',-200,-20,120,0,CPM_SNAP,CA_MOVE)\",
    \"walk('queenofhearts','pos_ql2')\",
   \"speak('queenofhearts','MISSPCH_13_QH_L10')\",
),
(
    \"Mission_Lose()\",
)
]

Here's a bit of data from FFX:

FFX_ENERGYSHIELD_CUSTOM=[
[\"default\",\"effect_ffx_energyshield\",\"effect_ffx_energysparks\",\"area\"],
[\"optical girl\",\"effect_ffx_blueshield\",\"effect_ffx_purplesparks\",\"ranged\"],
]

Notice the similarity?
Yep, they're both just big lists.

A CUTSCENE IS JUST A LIST OF LISTS OF STRINGS!

This is very important to realise if you're using a Python editor for syntax checking.
The code within the cutscene is not going to get syntax checked because its just string data.
As far as the Python editor is concerned, the two pieces of data above are of the same form.

When I call the cutscene, using

cshelper.play(lose_cs)

or just

play(lose_cs)

it passes this large wad of string data to the cutscene player, which will then take each string and try to execute it.
The heart of this is Python's exec function, that takes a string argument and tries to run it as a piece of code.

I can for example, go:

fn='kill'
char='hero_1'
command=\"%s('%s')\"%(fn,char)
#this has now assembled the string \"kill('hero_1')\" into the command variable.
exec(command)

as a long way of saying

kill('hero_1')

The cshelper.play function is asynchronous, in that it sends this data to the cutscene player and forgets about it.
Any commands after the play command then get executed immediately, so dont do this:

play(spawn_cs)
regDeath('baddie','onBaddieDie')

spawn_cs=[
(
    \"wait(3)\",
),
(
    \"spawn('baddie','thug_with_bat','pos_baddie')\",
)
]

because 'baddie' is not due to be created for three seconds, and the regDeath will fail and do nothing.

THE onStepCB FUNCTION.

Short for on-step-callback. Now you know.

Why are cutscene command grouped into blocks?
When the cutscene player is told to execute a custscene,.it will go through all functions in the first block, and then pause until onStepCB is called, then execute all commands in the next block, pause until onStepCB is called again, and so on.

Each block needs one and only one execution of the onStepCB function to be called for the scene to run smoothly.

You'll rarely actually call onStepCB directly, but it gets called through these functions:

wait(delay) - calls it afterv 'delay' seconds.
null() - calls it immediately
speak(char,tag) - calls it when the character has finished their dialogue line
moveCB(char,pos) - called when a character finshes their move

and so on,
examples of directly calling it:

Object_PlayEffect('hero_1','effect_ffx_blobby','onStepCB')
AI_Animate('hero_1','melee','onStepCB')

onStepCB takes a single event argument, and so is suitable for these functions.

Have a look at the example above:

The first two blocks call 'wait', the next two call 'speak'.
The final one doesn't need a call to onStepCB, because its the last one.

GETTING FANCY

I do break the rule above sometimes.
Say I have two lines of dialog (both 10 seconds long) I want a character to saym and want them to shrug halfway through the first one:

talk_cs=[
(
    \"startCS()\",
   \"look('hero_1',-200,0)\",
    \"speak('hero_1','line1')\",
   \"wait(5)\",
),
(
    \"AI_Animate('hero_1','drop_pole')\",
),
(
    \"speak('hero_1','line1')\",
),
(
    \"endCS()\",
)
]

The first block has a wait AND a speak in it!
After five seconds the wait will trigger, and the cutscene will execute the second block, and our hero will shrug by performing the animation.
The speech is still running at this point, and when it terminates, then the cutscene player gets a seocnd onStepCB and move son to the third block.
This triggers the second speech, when that ends the final block executes.

CALLING LOGIC IN A CUTSCENE

Sometimes you want something to happen in a cutscene depending on the state of the game.
Say Ive introduced a secondary objective to reporgram a computer, and want a character to respond to this in the closing cutscene.

I can write a function in the main code:

def talkAboutComputer():
    if Mission_GetAttr('reprogrammed'):
        AI_Animate('hero_1','power')
        #I did it! I reprogrammed the computer!
        speak('hero_1','line3')
    else:
        AI_Animate('hero_1','melee')
        #that poor misguided computer! if only I could have helped it!
        speak('hero_1','line4')

and call this from the cutscene.

win_cs=[
(
...
),
(
    \"ff.talkAboutComputer()\",
),
]

Now - either way it branches, a speak command gets triggered, so this function is guaranteed to call onStepCB
once and only once.

To call functions in your mission script from a cutscene, you need to add ff. to them as above, since the main script file is always treated as ff.py internally.

To have an optional line of dialogue:

def talkAboutComputer():
    if Mission_GetAttr('reprogrammed'):
        AI_Animate('hero_1','power')
        #I did it! I reprogrammed the computer!
        speak('hero_1','line3')
    else:
        null()

If the computer wasn't reprogrammed, then the function returns immediately calling onStepCB.

catwhowalksbyhimself