simon

simon

08/24/14

Programmer Corner: Online Cutcenes in Harmonia ED v1.4.0

Howdy, folks. One of the biggest features (if not the biggest feature) of Harmonia Engine Demo 1.4.0 is the ability to have server-side cutscenes. Cutscenes are nice, and just about every game has them. While, in concept, it’s a fairly easy thing to implement…

1. Write some dialogue
2. Add blocking
3. Fancy camera changes
4. Done!

…the client/server model and online nature of the game presented some challenges that took some mental gymnastics to overcome. It was an interesting puzzle to solve, so I’d like to share Harmonia’s solution for anyone brave/stupid enough as me to delve into online game development themselves. So, without further ado…

The Single-Player Approach, or, That Doesn’t Work Online!

Let’s say you want to have a scene in which your party enters a harbor and you’re suddenly ambushed by pirates who recently docked. If you’re scripting your cutscenes with a text editor, you might end up with something like this (pseudo-code ahead!):

camera (settings for a nice aerial view of our characters)
dialogue (Synival-the-pirate,
   "Avast, ye scurvy dogs!  The pirate king will conquer you!")

spawn ('entity_pirate' 10 times on the dock)
dialogue (entity_pirate*,
   "Arr, I'm a pirate!")

camera (quick zoom-in to our characters)
wait (camera)

jump (Hero-Jim)
dialogue (Hero-Jim*,
   "Damn!  This isn't good...  if Synival himself is here, the "
   "pirate gang's reach in these scenes has grown far indeed.")

choice ("What do you want to do?", "Fight!", "Retreat!")
if (choice_was ("Fight!")) {
   draw_sword (Hero-Jim)
   dialogue (Hero-Jim,
      "It looks like we have no choice.  To arms, men!")

   draw_sword (all-other-heroes)
   dialogue (all-other-heroes,
      "Yes, sir!")
   battle (Pirate-Harbor-Battle)
}
else if (choice_was ("Retreat!")) {
   dialogue (Hero-Jim,
      "We can't face this pirates right now.  Regroup back in the town!")
   enter_map (Harbor-Town, Harbor-Entrance)
}

This is a pretty straight-forward scene with some dialogue, camera changes, and basic blocking like “jump” and “draw_sword”. It gives you a choice to fight the pirates or return to town. In an offline mode, the game could stop time while the scene executes, then safely return your characters to their original places and start the game clock again. However, if playing online, this isn’t an option; it would be quite rude for one player to pause the entire server! Events will continue to run and outside influences could disrupt the scene. For example, the harbor could be destroyed by cannon fire. A “petrify-in-10-seconds” debuff before the scene began could kill all the characters mid-scene. And, probably most common of all, the client could disconnect at a point after the pirates spawned, but before the player chose to fight or retreat, leaving the area in an incomplete, bugged state.

Scene Objects and Persistent States

To prevent broken behavior like this, the solution used in Harmonia was not to execute scenes linearly, but use a server-side scene object with a modifiable state. Character objects are linked to the scene, and the scene object itself keeps track of dialogue and its current point of script execution. Server and client sync the scene object’s state so, in the event of a disconnect or other shenanigans, players can pick up right where they left off. Also, because the characters are still linked to the scene object while offline, a “don’t attack me while in a scene” flag is still on, so your party won’t be devoured by a family of hungry bears before reconnecting. This kind of immortality may result in some exploits or trolling, but it’s easy to turn off later if becomes a problem 😀

Multiple Characters, Multiple Scenes, Multiple Cameras

Harmonia lets you play as several characters at once which can be selected via the HUD or the 1 through 0 hotkeys. They can also split up and explore completely different areas of the map. Because of this, characters manage their scenes individually. If Jack is buying items at the local shopkeeper, Jill, who is regenerating HP back in the inn, may not want to be interrupted by the bartering occurring 5 buildings away. In addition, the town drunkard could interrupt Jill with some quest dialogue while she’s sitting around, which wouldn’t interrupt Jack’s shopping spree.

Here’s a giant gif of two characters walking around while each other’s scenes are taking place. This scene scene has no dialogue, but sets the camera and lasts (I think) 10 seconds before ending:

Harmonia Scene Interruption
Harmonia Scene Interruption
When a character has a “Speech” icon above their head, it informs the rest of the world that they’re in a scene. In this state, they can’t be pushed, attacked, and they can’t be issued any orders until they’re out of the scene.

Programming-wise, this isn’t too difficult to accomplish. Because the server-side scene object remembers its state, and because the server syncs the scene with clients, it’s easy enough to make sure clients are always looking at scene data from their current character. The client remembers its own camera state outside of the scene, so whenever it re-enters gameplay mode, it can pop the scene camera state and return to normal.

A Simple, Finished Cutscene in Harmonia

Check out this cuddly li’l Rollcoon, up to some wacky antics!

Rollcoon Cutscene
Rollcoon Cutscene

Harmonia’s script code is pretty simple. The script below uses the following commands:

set (property, value)
   Assigns 'value' to 'property'.  In this scene, it's changing the camera positioning.
dialogue (id, character, text)
   Creates or appends to a dialogue window belonging to 'character'.
   'id' is a unique identifier used to keep track of which dialogue has already been read.
beat (character, script)
   Runs the script 'script' on 'character'.
   Used to modify properties of characters or issue orders like jump() or move().
wait (id, type, parameters)
   Pauses execution of the scene/script until certain conditions are met, specified by 'type' and its 'parameters'.
   'id', like with dialogue(), is a unique identifier used to keep track of which wait() commands have already executed.

Here’s the script:

[scene_get_out_of_my_house]
   set (camera, "x=-3 y=-12 height=-1.5 rot_y=-30 rot_x=22.5 dist=5 "
                "lock_pos lock_rot_y lock_rot_x lock_dist")
   dialogue (1, speaker, "What are you doing in here?!  Get out of my house!"
                         "`NGo on, scram!!")
   wait (1, dialogue)
   dialogue_clear ()

   set (camera, "x=-1 y=-9 height=-1.5 rot_y=-60 rot_x=22.5 dist=8 "
                "lock_pos lock_rot_y lock_rot_x lock_dist time=0.25")
   wait (2, time, 0.25)
   beat (speaker, beat_get_out_return)
   wait (3, time, 2.00)

   narrate (2, "Wait a minute - why did HE leave?  This scene makes no "
               "sense!`N"
               "I hope this little demo made your world a little bit "
               "brighter.  Ta-ta!")
   wait (4, dialogue)

And finally, here’s the code from the script ‘beat_get_out_return’, used by the beat() command:

[beat_get_out_return]
   off (scene_welcome_rollcoon_house)
   move (internal (spawn_x), internal (spawn_y))

This is a simple beat that tells the Rollcoon to move back to its spawn location further down the map – he didn’t spawn in his house 🙂 It also turns off the ‘scene_welcome_rollcoon_house’ script, which is what put him in the house in the first place. With that script gone, he returns to his original location and runs an alternate scene, specified elsewhere.

Next Project: Menus and Dialogue Trees

This model has everything necessary to program a menu system for shopkeepers, quest characters, and any other random event. Implementing the current system took a long time, so I’m taking a break from it for now 😛 When my brain solidifies (it’s a pile of mush right now thanks to exhaustion and the common cold), that may be the next project.

Ta-ta!
— @Synival

Leave a Reply