Implementing cutscenes

I have been adding cutscene support to my engine over the past couple of days and it has raised a couple of interesting and related questions:

Do SCUMM and the Thimbleweed Park engine support nested cutscenes? Meaning starting a cutscene from within a cutscene. It looks like (the excellently succinct) SCUMM-8 by @Liquidream maintains a stack of cutscenes to support this, which makes me think that the original SCUMM engines might have too? I’ve been trying to think of situations in TWP or classic Lucasarts games that may have required nested cutscenes but I can’t think of any…

Second question: how should the engine manage local (room-scope) threads with cutscenes? Should they be killed when starting a cutscene or preserved for afterwards?

For example, say the camera is in a kitchen room and there are two local threads running: a dripping tap thread that was started in the kitchen’s enter() function, and a microwave thread that was started when the player used the microwave. Now for some reason cutscene starts.

Let’s say that the engine is set up to destroy the local threads when a cutscene starts, so the tap and microwave threads are deleted. The camera cuts to a mansion room containing a clock. The mansion room enter() function is called which starts a clock ticking thread.

When the cutscene finishes the camera returns to the kitchen, the local threads, including the clock thread, are destroyed and the kitchen enter() function is called which starts a new tap dripping thread. But the microwave has now stopped. Bug!

So perhaps the engine should not destroy the local threads when starting a cutscene. It could just keep hold of the existing list of room threads, but not update them while in a cutscene. The cutscene “context” could have its own list of local threads to add to and update over its lifetime.

Using this technique, when the cutscene starts the tap and microwave threads are stored suspended to be restarted again later. The clock thread is added to a separate list inside the cutscene. When the cutscene ends, the cutscene threads are killed and the dormant list containing tap and microwave threads are awoken. However when the camera returns to the kitchen, its enter() is called which starts up a second dripping tap thread! Not good.

This is making me think that killing all local threads when entering a cutscene is the simpler thing to do. Until we consider a case where a cutscene starts that takes place in the same room. In this situation we don’t want to kill all the room threads or the player will notice the tap and microwave suddenly stop when a brief in-room cutscene starts.

I’m sure there is an elegant solution to this problem, with minimal special-case code, but I can’t figure it out. It would be great if @RonGilbert or @David could provide some insight into how they handle this tricky problem.

2 Likes

I wouldn’t start a thread in the enter function without checking beforehand whether said thread is already running. Or, I would avoid to call the enter function if we’re returning from a cutscene. Enter should refer to a character entering, meaning that the room wasn’t in use.

But what I would do is differentiating between local threads, that exist just for ambient (as a dripping faucet), and global threads, that have an influence in the world’s state (as a microwave). I mean, if you turn on a microwave, you expect it to work even after you exit the room. In fact, this is what I usually do in real life :stuck_out_tongue: so a “local” thread gets killed because it makes no difference to the world if the camera isn’t watching, but a “global” thread persists. As soon as your dripping faucet is needed to fill up a sink, then it becomes global.

I’d also differentiate between cutscenes that are intended to be kind of like movie flashbacks - as the cutscene in Monkey Island where LeChuck learns from Bob that there’s an aspiring pirate in town - and cutscenes that are in real time, as Delores entering the verbal fight between Chuck and Franklin. However, this second type of cutscene is usually centered on the playing character, so I suppose they start in the room. “Movie” cutscenes should suspend all threads, “real time” cutscenes should behave as if the player did all those movements.

3 Likes

I recently might have recently a “nested” cutscene, when in Zak you use the blue crystal just before meeting Annie for the first time. The cutscene with Annie starts, and when the new verb “switch” is shown, the scene cuts away to the street where Al (the Caponian) appears. Then the cutscene with Annie continues.
The fact that the second cutscene doesn’t interrupt the first one, but only when (a part of) the verb UI is shown, makes me think it is not really nested cutscenes… Or perhaps it is supported, but simply blocked by a check whether display UI is off, as is the case in all other cutscenes.

I have a video of that I could share if you’re interested.

2 Likes

This name makes SO MUCH sense! :joy:

1 Like

Al Capone… yes
(but credit to @tasse-tee for coming up with Al the ALien)

2 Likes

:joy:
I wish I’d used your logic when I invented his name!

2 Likes

Why and when do you want nested cutscenes? A cutscene interrupts the whole game. If you show a flashback in a flashback then this is one (single) cutscene. If both flashbacks are triggered with a timer then the second flashback should wait until the first one finished - otherwise you would confuse the player.

But if you need nested cutscenes:

Is this a/the stack in the interpreter or does he really implement a (second) separate stack for cutscenes? In the latter case: Why this?

For me a cutscene is just a function call where the current process stops and calls the new function.

Pause all running threads. And un-pause them after the cutscene finished.

Why? :slight_smile: Just freeze all other running threads during the cutscene.

Sounds like a thread management and testing nightmare… :wink: (“Was this thread local or global?”)

Firstly, thanks for checking out/mentioning SCUMM-8 - glad to hear that my little project may be of some help to someone else building their own engine.

As has already been mentioned - no, I’m pretty sure this is not the case (at least in the case of the original SCUMM engine).
While my main goal of SCUMM-8 was to keep as true to the original SCUMM API, as was possible/practical - I did take some liberties here & there (e.g. to capitalise on features of newer “tech” or to work/fit better within the limits of the PICO-8 Fantasy Console). This is an example of that “divergence”.

While I agree with the other poster here that “nested” cut-scenes are unnecessary - I decided to include support for them because it was relatively cheap in PICO-8/Lua to use threading (Coroutines) and because I could imagine the possibility of (perhaps logic depending) one cut-scene needing to start another & then coming back to exactly the same point again - rather than having to add lots of logic to the Room ENTER() to make it seem “seamless”.
In all honesty, I can see myself pulling this out in future, as gawd knows I could do with reducing the token count (it’s a PICO-8 thing - those that use it will know what I mean! :weary:).

Again, you’ve already been given some useful advice here. The only (potential) issue I could foresee by freezing local threads is if you plan to support local cut-scenes (e.g. one where you don’t actually leave the room - like animating a character performing a series of tasks when the player can’t do anything) - because if you are using threads for animations (e.g. a dripping tap or roaring fire), you don’t want to stop that animation.

In times like that, I animate them with “background” (or global) scripts - so they are not affected by the “freezing” part, but I’d probably also have Room.ENTER() and EXIT() code to start/stop the animations, so that they are not using resources when they are not even visible.

If you have any more questions - feel free to shout. Likewise, would be interested to know more about your engine, when you’re ready to “SHOW ENGINE” :wink:

3 Likes

Can you give an example for this?

But you know which threads are running (because you started them somewhere). So it should be easy to freeze only some of them. (Like: “if (timer1 == 60) then freeze thread_microwave and start cutscene_edna5”)

btw: The “local” cut-scenes you are mentioning don’t exist: All things happening in the room are normal (or in your terms “background”) threads. If necessary, you can just remove the possibility to control the hero by the player.

2 Likes

Hi Someone (nice handle!) :wink:

TBH, I can’t think of many situations that couldn’t be ultimately be achieved with just a single (albeit potentially complex) cut-scene. Even cutting back & forth between multiple characters, rooms & title cards (like in Monkey Island or DOTT, for example) could’ve probably been achieved in SCUMM by simply changing the active/selected Actor or Room. Hence why - although my engine currently supports it - it’s probably a bit superfluous ATM.

(I think I got a bit carried away when I saw references to where SCUMM auto-increments/decrements a cursor flag when starting/finishing a cut-scene) :thinking:

Sure, but wouldn’t it be nicer if the game designer didn’t need to have that knowledge? (e.g. make it a feature of the engine, by having some way to classify script threads when they’re created)

Oh ok. It’s just that “local” cut-scenes are referenced several times in the SCUMM Manual/Tutorial/Glossary (although I’m not sure it’s ever really defined), so I figured it could just mean a cut-scene that stays within a single room. More proof that I should never assume! :wink:

2 Likes

As a game designer I would prefer to have full control of what is when happening. I would like to decide if and when the microwave stops microwaving. Maybe it’s necessary that the microwave works during a cutscene.

But sure, it would be nice if I could say: Freeze all actions and start the cutscene. But this could be easily achieved by making it an option :wink: or by design.

Ah, Sorry. I thought you invented the term “local”.

1 Like

This makes a lot of sense. I think careful consideration of whether a thread should be global (persistent) or local (temporary) can probably solve a lot of problems.

I think I’m looking for some well-defined rules that will work in most situations.

1 Like

Yes. I get the impression that lots of cutscenes in adventure games are faked. Once you’re in a cutscene and the player has lost control, the scripter can go to town and cut the camera left, right and centre so to speak.

At first I was toying with the idea of large portions of a game existing in a cutscene - like those movies that start, then enter a flashback for the majority of the movie, before cutting back to the opening scene to conclude the story (sorry can’t think of an example offhand!). I guess this is like the whole movie comprising one of the Thimbleweed Park flashbacks.

@RonGilbert states that his save game system cannot save during cutscenes due to the complexity of Squirrel’s innards so the flashbacks in the game must simply be composed of flashback-specific code and data, otherwise saving during the character flashbacks would not be possible.

Again my question: Why would you like to do that, resp. which problems would you like to solve? Why do you can’t you just suspend the threads?

btw: Which programming language are you using?

The flashbacks are implemented as cutscenes?

Suppose you have a room, kinda like Elaine’s party in MI2. The guests talk, move, do stuff in a (local) thread.

Then you use an inventory object, who knows, an ultrasound whistle. Cutscene! The UI goes away, the characters looks in the camera and says “let’s try this”, animation of him taking the whistle and blowing. This is a “local” cutscene, so you don’t want the guests to stop moving, you keep their thread running.

But then the room changes. A dog in the garden raises his ears and runs to the house. Now that the camera is in a separate room, should the guests still move and talk? Probably not, fine, I agree.
Then the dog enters the room and begins to sniff you. Now the cutscene is “local” again, so the guests should move again.

Fine, we can solve this by starting and killing the guests thread in the room enter and room exit functions.

But, now, suppose you also have a timed element. If you put a song on the stereo, a cutscene starts where the host, in a control room upstairs, says “ooh, I love this song” and begins dancing, and after the song ends, another cutscene starts when he says “that was fun, now back to monitoring my guests”. This timed frame is vital because it is the only one where you can “summon” the dog, because if the dog enters the room when he’s watching, he goes downstairs to scold him and kick him out of the house.

How should the thread that takes care of checking the song time behave?

Suppose the part of the cutscene where the dog runs to the house is pretty long, because the dog is far away and it follows his path. Should the logic be “well, you blew the whistle while the song was running, so it’s fine”, even if then the song ends and you’re supposed to see the host going back to his monitors? I wouldn’t like it. And in this case the two threads are related (the song is needed to allow the dog to enter), but what if the song is used for something else, not the dog? The song ends while you’re watching your dog cutscene, but as you killed all threads, the flag “host is distracted” has never being put to false.

And if you pause that thread instead of killing it, you might have a time frame where the thread still counts time but it’s not aligned with the song, so you randomly get a “that was fun” cutscene without the audio cue.

So, you keep the song timer thread on a global queue, and the guests thread on a local queue. When the camera leaves, local threads get killed / paused, but global threads keep running. And if the song ends while the dog is still walking to the house, you get… :drum::drum::drum: a nested cutscene!

These are things that can be avoided by redesigning the puzzles, of course. But this separation between local and global scripts happens for example in AGS.

2 Likes

Thanks for clearing that up. I’m pretty sure it’s worth avoiding to keep things simple.

I think the simplest thing to do with local/room threads wrt cutscenes is nothing. If a cutscene starts that does not require a camera cut then the existing room threads are unaffected. If the cutscene requires the camera to change room then the threads will be destroyed as usual and the rooms’ enter/exit code can take care of any special cases.

It’s just a hobby project based on Ron’s blog posts. Ever since I played Monkey Island I wanted to write an adventure game and an opportunity to learn from the master was too good to pass up. Writing tools was always a pain in the past, but ImGui has changed all that. Coupled with SDL2, Squirrel and a JSON library, you can just focus on the fun stuff. Excellent fun - just wish I had more time to spend on it.

3 Likes

I don’t see a problem here: That’s part of the main game logic and consists of simple “if” statements:

while(wait for input)

if guybrush starts music
   then show cutscene where the man dances

if guybrush whistles and music is not playing:
   then start a cutscene where the dog enters the house, look around and walk back in the garden.
else if guybush whistles and music is playing:
   then start a cutscene where the dog enters the house and kicks the man in the control room.

if music stops and the man wasn't kicked out
   then show the cutscene where the man stops dancing.

endwhile

There is only one cutscene running at a time and no threads are related. The main script keeps track of those threads and the current situation/state of the game. You can easily add more situations, for example that the dog needs some time to run around the house - that’s similar to the test if the music is (still) running.

To be able to test if the music is running, you have to keep the state of the music in a (global) variable: If the music thread starts, it sets the (global) variable is_music_playing to “true” and if the thread ends it sets the variable to “false”. (The variable hasn’t to be global depending on the thread model and framework you are using.) As an alternative you can just check if the thread that plays the music exists (and/or is running). :slight_smile: The music (or any other thread) hasn’t to care about the current game state. Same with the running dog.

The test if the man was kicked out of his control room is simpler: After the corresponding custscene played, the main script above just saves this fact in a variable (man_kicked = true).

Why would you like to kill everything? :wink:

No. First, I don’t need a separate timer, just the thread that plays the music. That threads knows when it ends (and if I would suspend the music thread for some reason the thread should stop playing the music). And the if-statements above in the main script take care that your situation can’t happen - assuming that the main script waits until each cutscene is finished.

I’ll have a look at it.

That doesn’t take timing into consideration. It kinda works as my “Kill Yourself” engine, where there were no timed events. Your loop only considers the starting time of the interaction, which is fine, but that’s not what I described. “If Guybrush whistles and music is playing” is not what I want, because it’s not a matter of when he whistles, but when the dog enters the room. What if I whistle right before the music stops, but then the dog arrives too late?

Again, why should the main script wait? Where’s the concurrency? A while loop is not how you manage concurrency. You end up having an engine that only allows for a linear execution of things, because it has to check the state of the first cutscene before checking if the conditions are met to start a second cutscene.

In your model, as soon as you start a cutscene, you’re done. It’s as if you paused the game and watched a youtube video, only to resume the game after the video ends. If you have more than one thing going, you have to wait until the cutscene ends. So, supposing the dog and the music are unrelated, you’ll have to wait for the dog cutscene to stop to see the dance stop cutscene.

Your model works for “movie-like” cutscenes that are not happening in real time, like when they show you LeChuck’s ship after you exit the Scumm Bar. But not for those where you just lose control of the game, but the script is basically playing for you.

No. The music works exactly like a timer. Just replace “music” with “timer”.

No. The if-statements are testing if the music has stopped. But we can add an dog timer …

… that’s just another “exception” that the script has to deal with:

loop {

if guybrush starts music then start thread music endif
if guybrush whistles and the dog is not walking already then 
   start custscene that shows the dog starting to walk in the garden;
   and after that start a thread dog_walking with a timer;
endif
...
if music stopped and the dog_walking ended
   then show the cutscene where the man stops dancing.
else if music stopped and the dog_walking not ended
   then let the dog walk back in the garden
endif
}

You just have to check if both threads are running (or remember the states in a variable - that depends on the implementation).

Because that’s the way a cutscene is meant to work: It interrupts all current actions. Otherwise you haven’t a cutscene.

Because there is no concurrency needed. You are trying to add concurrency even if it’s not necessary.

No. It waits for events and reacts appropriate. Like other “game loops” do. (Could it be that you are working a lot with functional programming?)

No. Why do you think so? That would mean that an imperative program is only able to produce movies? In my model a cutscene just runs another script, that can have its own logic and its own threads. I just don’t differ between local and global threads.

But that is exactly what the pseudocode above does: The game loop/while waits until an event occurs - for example that the music ends. If the script starts a cutscene it waits until the cutscene is finished.

I could change the script in an event driven architecture if you like, but I still don’t need to differ between local and global threads (or need to tell a thread what else is going on).

In that case you have a movie. In your dog example the player gets the controls back. He interacts with the scene (he is able to whistle or to walk around or do other stuff). So you haven’t a cutscene in that particular moment.

I’m probably paying insufficient attention, but your definition of a cutscene above sounds indistinguishable from a previously rendered or recorded movie to me. Which is quite different from the dynamic in-game cutscenes in, say, Warcraft III. (Where the fight continues while your hero has a little cutscene.)