Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for skippable cutscenes #1513

Merged
merged 9 commits into from
Sep 11, 2020
Merged

Support for skippable cutscenes #1513

merged 9 commits into from
Sep 11, 2020

Conversation

Semphriss
Copy link
Member

From now on, you can skip cutscenes!

Read the info for : Players  •  Level designers  •  Programmers


Info for players

If you're playing a level with a cutscene, hit escape, and the cutscene will instantly finish! Speedrunners and impatient players, this is for you!

(Applicable only to levels implementing the functionality - won't work yet for older levels! ;) If you want, you can edit these levels and submit your changes.)


Info for level designers

If your levels contain cutscenes, you can say goodbye to watching them every time you test your levels - now you can make them skippable by using these to simple functions!

How can my level support skippable cutscenes?

In your Squirrel scripts, simply add start_cutscene() at the beginning of your cutscene, and at the end, add end_cutscene(). Simple as that! Now, you can skip your cutscene simply by hitting the escape button on your keyboard :)

Will this break my level?

It shouldn't - all the code that happens in your script will be executed, and it will all happen in order - it just will do everything instantly instead of waiting :)

Still, it is always a good idea to try to skip cutscenes to make sure your level doesn't break. If you encounter a problem, let me know! :)

Good practices with cutscenes

There are certain types of cutscenes that shouldn't be made skippable. For example, cutscenes where the amount of time elapsed is critically important (for example, a door closing very slowly and relying on the fact that Tux can't move while it closes) shouldn't be made skippable to avoid letting the player pass under the door if the skip the cutscene and run fast enough. It is however a good idea to keep these cutscenes as short as possible.

Very recommended : At the end of each cutscene, try to "reset" time-based actions as much as possible. For example, if you use smooth fade-ins or fade-outs, add a final fade-in of 0 seconds to make sure the level is visible and clear once the cutscene is over. If your cutscene ends with, say, a final fade-in of 2 seconds, you space the final fade-in and the "reset calls" with wait(2). Make sure that end_cutscene() is called with those reset calls. (Before or after, doesn't matter)

Example :

start_cutscene();

Tux.deactivate();
Effect.fade_out(1.0);

wait(3.0);

Effect.fade_in(1.0);

wait(1.0);

Tux.activate();

// Without cutscenes, this line wouldn't be needed.
// It's just there so it won't fade in slowly after you skipped the cutscene.
Effect.fade_in(0.0);

end_cutscene();

Or :

Tux.deactivate();
Effect.fade_out(1.0);

// It doesn't matter if this line is before or after the first calls,
// as long as it's called before the first "wait()"
start_cutscene();

wait(3.0);

Effect.fade_in(1.0);

wait(1.0);

// It doesn't matter if this line is before or after the reset calls,
// as long as it's called after the last "wait()"
end_cutscene();

Tux.activate();
Effect.fade_in(0.0);

Info for programmers

How it works

(Requires understanding the info for level designers)

TL;DR It basically cancels all calls to wait(float) between start_cutscene() and end_cutscene().

Details :

  • When start_cutscene() is called, it stores a variable in the Level object to remember that a cutscene started. (The variable is in the level, so that if an unskilled level designer calls start_cutscene()but never calls end_cutscene(), the erronous state won't spread to other levels.)
  • When the player hits escape, instead of immediately popping the pause menu, it checks if there is a cutscene running. If yes, then it stores another variable in the Level object to remember that the user wishes to skip the currently running cutscene, and it does not show the menu. After this second variable is set, further escape keys will effectively show the pause menu, so that if, for any reason, the cutscene refuses to finish (see example above about unskilled LDs), the player won't be kept from opening the menu and effectively stuck inside the level.
  • When that second variable is set, the Squirrel scheduler will immediately resume ("wake up") all suspended VMs (running threads) and swap subsequent calls to wait(float)with wait(0). The call to wait is not completely ignored in case some scripts are coded like while (true) {wait(0.5), ...}, so that they won't freeze the game. Note : Only calls to wait that were executed after start_cutscene() (and before end_cutscene()) will be skipped. That is, if there is another script using waits but not cutscene functions, they will not be fast-forwarded.
  • When end_cutscene() is called, either after skipping the cutscene or displaying it normally, both variables are reset and all calls to wait(float) will work as expected.

Backwards compatibility

The addition is effectless on current levels. To use the system of skippable cutscenes, a level must make use of start_cutscene() and end_cutscene().

I tried to make the integration as seamless and simple as possible, so it won't for example, skip some important code. Still, it is good to always test each cutscene to make sure there aren't any bugs, visual artifacts, or other unwanted side effects.

Such side effects will most likely take the form of effects taking time to finish after the cutscene was skipped. For example :

start_cutscene();
Effect.fade_out(1);
wait(2);
Effect.fade_in(1);
end_cutscene();

When skipping, notice how the fade_in is still running. This happens because fade_in is non-blocking and still executed after the cutscene is over.

The "skip-the-cutscene" case scenario cancels calls to wait, so the code will be executed like this :

start_cutscene();
Effect.fade_out(1);
Effect.fade_in(1);
end_cutscene();

Notice how fade_in and end_cutscene are called at the same time. fade_in will still do something after end_cutscene has been called.

This :

start_cutscene();
Effect.fade_out(1);
wait(2);
Effect.fade_in(1);
wait(1)
end_cutscene();

...won't work either, as waitis ignored when skipping a cutscene.

The solution is this :

start_cutscene();
Effect.fade_out(1);
wait(2);
Effect.fade_in(1);
wait(1)
Effect.fade_in(0);
end_cutscene();

One final call to fade_in with a time of 0. This will make sure that once the cutscene is finished, all effects are in the state they are expected to be.

The "skip-the-cutscene" case scenario will execute it like this :

start_cutscene();
Effect.fade_out(1);
Effect.fade_in(1);
Effect.fade_in(0);
end_cutscene();

Effect.fade_in(0); will be effective immediately, correcting the problem.

Bug prevention

I made the system as resilient as I could against poor level designers. Wrongful calls (start_cutscene() twice in a row, start_cutscene() not paired with a corresponding end_cutscene(), orphaned end_cutscene() (without a start_cutscene()), etc.) will not result in undefined behavior, but will rather display a warning in the console and attempt to recover safely. Plus, the variables are stored inside the Level object, so that errors arising from a given level won't spread across the game or affect another level.

@Alzter
Copy link
Member

Alzter commented Aug 19, 2020

This seems very thoughtfully designed, I'm hoping it gets implemented soon

@Rusty-Box
Copy link
Member

@Semphriss I think cutscenes should also be skippable with 'enter' and the 'jump key' since these are usually used for menus and might be more intuitive than 'escape'.

@Semphriss
Copy link
Member Author

What I'm afraid of is the player accidentally pressing the action button at the beginning of a cutscene, which might happen, for example, if the cutscene happends when activating a switch, or when carrying an object, where there is a risk the player releases the key too late, or spams it, which might unintentionally result in skipping the cutscene.

I'd recommend using a key that is unlikely to be used in-game to avoid such problems, hence why I originally picked the escape button.

@Ordoviz
Copy link
Contributor

Ordoviz commented Aug 19, 2020

I suggest displaying “Press Esc to skip” if any other key is pressed.

src/object/cutscene_info.cpp Outdated Show resolved Hide resolved
src/object/cutscene_info.cpp Outdated Show resolved Hide resolved
src/object/cutscene_info.cpp Outdated Show resolved Hide resolved
src/scripting/functions.cpp Outdated Show resolved Hide resolved
src/supertux/sector.cpp Outdated Show resolved Hide resolved
@tobbi tobbi merged commit ccf64c8 into SuperTux:master Sep 11, 2020
Mathnerd314 pushed a commit to Mathnerd314/supertux that referenced this pull request Sep 13, 2020
* Skippable cutscenes support.

* Fixed the wrapper.cpp file.I rebuilt it the proper way (cmake -DGenerate_wrapper=on ..; make) so it fixed a few errors I made

* Removed unnecessary property (added while testing methods for skippable cutscenes)

* Skippable cutscenes now display a message in the top-left corner of the screen.

FIXME: The text is sometimes not displayed when testing a level from the editor.

* Fixed a problem with the cutscene skip message sometimes not showing

* Fixed coding style

* Improved code quality and removed unnecessary comment

Co-authored-by: Semphris <semphris@protonmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants