Add Review
Subscribe
Nominate
Submit Media
RSS
The Construction of Metis Part 2: Virtual Playwriting
Sailerius- 12/13/2012 09:31 PM
- 1828 views
And now for the much more exciting part 2 on my blog series about the development of Metis, the game engine behind Vacant Sky: Awakening. You can read the first part here.
The topic this time is on cutscenes, what goes into writing them, and why supporting them is a nightmare.
Case study: Ren’py
When developing a piece of software, a good way to start is by imagining you’re an end user of the software (in this case, the person making a game using your game engine) and writing out how you would like to interface with it. In this case, I’m going to imagine I’m writing a cutscene with this engine and come up with a convenient format.
To begin with, I’m going to indicate one engine which, in my opinion, makes the authoring of cutscenes as easy as conceivably possible. That engine is Ren’py, a particularly well-designed domain-specific engine for making visual novels. Since most of visual novel authoring is writing dialog, it was made to be the simplest possible task.
Taken from the Ren’py tutorial:
A line of dialog is represented by a line of code consisting of two strings: the name of the speaker and what they said. It’s easy to read and easy to write. Virtually no overhead (except for the quotes). I think that, in a perfect world, it could be simplified to something like Me: Yes… but we’ll cross that bridge when we come to it.
What makes this so good is that Ren’py was designed for a highly specific genre and its developer identified what the most common tasks would be, then made them as easy as possible. Since Awakening is such a story-based game, much of the content creation will be in writing dialog, so I want something as easy to use (if not easier) as this.
The waiting paradox
Let’s break down exactly what’s happening here:
1) Show a background image
2) Show Sylvie’s smiling portrait
3) Have Sylvie say a line of dialog
4) Wait for the user to advance to the next line of dialog)
5) Have the protagonist say a line of dialog
6) (Wait for input)
7) Fade out the current background image
8) (Wait for fade out to complete)
9) Fade in new background image
10) (Wait for fade in to complete)
11) Show a line of narration
Seems fairly straightforward and easy to implement. What’s so tough about this? If you’re a programmer, you probably saw the problem right off the bat.
What exactly do we mean by “waiting?”
Let’s say we’re on step 4. A line of dialog has just been displayed and we’re waiting on the user to push a key to advance. How exactly do you implement that?
A first attempt might look something like this:
This while loop will keep on spinning, doing nothing, until it detects that the action button has been pressed, after which point the loop will break and it’ll go on.
The problem with this approach is that while you’re waiting, your entire game is blocked, waiting on the loop to finish. You can’t do anything else. Background processes such as music aren’t updating. The UI isn’t updating properly. Depending on your graphics framework, it might stop drawing properly. This is called busy waiting.
So, let’s try another approach. Instead, let’s go into the game’s main update loop and put the wait there:
This approach requires you to keep track of when you’re waiting for the dialog to advance (probably with a true/false flag) and to put a check against it at the start of your update loop.
Good news: It works!
For a lot of games, this approach works fine. The problem arises when there turn out there are multiple things you’re waiting on. Let’s say, in addition to waiting for the action button to be pressed to advance dialog, you also wanted to play a different song when the current one ends:
Now let’s say you wanted to have animations, or some other gradual visual effect. For each one of these, you need a variable and a new condition in your update loop. This is annoying but manageable. The real problem is that, while you weren’t looking, your code has become a monster:
This is pretty much what your function to show dialog looks like now. What’s the problem here? It makes sense, right?
The problem is that you’ve broken your function up into two functions (pre-waiting and post-waiting) and it’s not clear from reading one of them how they’re supposed to behave together. You’ve entered callback hell, a dark place where all your functions are split into two, threaded together by a nightmarish web of conditional checks and function forwarding. Here, nothing is as it seems and following the logic of your game is nigh impossible.
If you can’t wait inside your functions and you can’t wait outside of them, what are you supposed to do? How do you avoid busy waiting without falling into callback hell?
The answer: It depends.
Asynchrony angst
The solution to this problem depends on what programming language you’re using. “But wait!” you say. “You haven’t said what programming language you’re using yet!” And indeed I haven’t. At this point, we’re still in the design phase of the engine. This is all part of the exploration of the design requirements. Now, we’re finally at the stage where our design requirements are going to start making some decisions for us.
Let’s look at some possible solutions to the waiting paradox.
Ruby: Fibers
Fibers are a form of lightweight threads which are useful for asynchronous tasks. In this case, it would look like:
It’s a bit of a simplified example, but the idea is sound. There’s a little bit of a learning curve, but this solves the problem neatly. The whole of the show dialog functionality is contained within the show dialog function, which keeps the code easy to follow and maintain.
Lua: Coroutines
Very similar to Ruby’s fibers in terms of usage, just in Lua instead of Ruby.
C#: Async & Await
A relatively recent development, the async/await feature of C# 5.0 takes the cake for one of my favorite new language features.
Simple and elegant. All you do is mark a function as async and then await it (aside: The actual implementation of WaitForInput() here is a little hairy, but I won’t sweat the details for now).
Design requirements revisited
So, we’ve got three possible ways to handle the problem of how to implement asynchronous commands in cutscenes. The choice depends on what language we’re using. So, now we have to choose what programming language we’re going to use. To narrow it down further, let’s revisit our list of design requirements.
The other bottleneck requirement is cross-platform deployment. Let’s look at how Ruby, Lua, and C# shape up in these regards:
-Ruby: Limited or no support for games on mobile
-Lua: Available on most platforms but none (yet) support Windows 8 apps
-C#: Available on everything
Lua has always been an agile language and quick to appear on other platforms. The lack of current Windows 8 app support doesn’t concern me as much as the fact that most of the cross-platform game frameworks using it are either too new or just flat-out suck. I tried out Moai several times, but the documentation is abysmal.
Although by default only available on Microsoft platforms, C# is also available on Android and iOS via Mono and has a very powerful framework for it called Monogame. And this is purely subjective, but I would much prefer to build a game engine in a statically typed language for the ease of debugging and architecting.
For that reason, C# was chosen as the language of choice for Metis, which will sit atop the Monogame framework.
The next part will dive deeper into the logistics of cutscene implementation with regards to scripting, and how I both had my cake and ate it, too.
The topic this time is on cutscenes, what goes into writing them, and why supporting them is a nightmare.
Case study: Ren’py
When developing a piece of software, a good way to start is by imagining you’re an end user of the software (in this case, the person making a game using your game engine) and writing out how you would like to interface with it. In this case, I’m going to imagine I’m writing a cutscene with this engine and come up with a convenient format.
To begin with, I’m going to indicate one engine which, in my opinion, makes the authoring of cutscenes as easy as conceivably possible. That engine is Ren’py, a particularly well-designed domain-specific engine for making visual novels. Since most of visual novel authoring is writing dialog, it was made to be the simplest possible task.
Taken from the Ren’py tutorial:
label start:
scene bg uni
show sylvie smile
s "Oh, hi, do we walk home together?"
m "Yes..."
"I said and my voice was already shaking."
scene bg meadow
with fade
"We reached the meadows just outside our hometown."
"Autumn was so beautiful here."
"When we were children, we often played here."
m "Hey... ummm..."
show sylvie smile
with dissolve
"She turned to me and smiled."
"I'll ask her..."
m "Ummm... will you..."
m "Will you be my artist for a visual novel?"
A line of dialog is represented by a line of code consisting of two strings: the name of the speaker and what they said. It’s easy to read and easy to write. Virtually no overhead (except for the quotes). I think that, in a perfect world, it could be simplified to something like Me: Yes… but we’ll cross that bridge when we come to it.
What makes this so good is that Ren’py was designed for a highly specific genre and its developer identified what the most common tasks would be, then made them as easy as possible. Since Awakening is such a story-based game, much of the content creation will be in writing dialog, so I want something as easy to use (if not easier) as this.
The waiting paradox
Let’s break down exactly what’s happening here:
1) Show a background image
2) Show Sylvie’s smiling portrait
3) Have Sylvie say a line of dialog
4) Wait for the user to advance to the next line of dialog)
5) Have the protagonist say a line of dialog
6) (Wait for input)
7) Fade out the current background image
8) (Wait for fade out to complete)
9) Fade in new background image
10) (Wait for fade in to complete)
11) Show a line of narration
Seems fairly straightforward and easy to implement. What’s so tough about this? If you’re a programmer, you probably saw the problem right off the bat.
What exactly do we mean by “waiting?”
Let’s say we’re on step 4. A line of dialog has just been displayed and we’re waiting on the user to push a key to advance. How exactly do you implement that?
A first attempt might look something like this:
while(ActionButton.NotPressed)
{
DoNothing();
}
AdvanceToNextLineOfDialog();
This while loop will keep on spinning, doing nothing, until it detects that the action button has been pressed, after which point the loop will break and it’ll go on.
The problem with this approach is that while you’re waiting, your entire game is blocked, waiting on the loop to finish. You can’t do anything else. Background processes such as music aren’t updating. The UI isn’t updating properly. Depending on your graphics framework, it might stop drawing properly. This is called busy waiting.
So, let’s try another approach. Instead, let’s go into the game’s main update loop and put the wait there:
function Update()
{
if(WaitingForDialogToAdvance and ActionButton.Pressed)
{
AdvanceToNextLineOfDialog();
}
UpdateRestOfGame();
}
This approach requires you to keep track of when you’re waiting for the dialog to advance (probably with a true/false flag) and to put a check against it at the start of your update loop.
Good news: It works!
For a lot of games, this approach works fine. The problem arises when there turn out there are multiple things you’re waiting on. Let’s say, in addition to waiting for the action button to be pressed to advance dialog, you also wanted to play a different song when the current one ends:
function Update()
{
if(WaitingForDialogToAdvance and ActionButton.Pressed)
{
AdvanceToNextLineOfDialog();
}
if(CurrentSong.IsFinished)
{
PlayNextSong();
}
UpdateRestOfGame();
}
Now let’s say you wanted to have animations, or some other gradual visual effect. For each one of these, you need a variable and a new condition in your update loop. This is annoying but manageable. The real problem is that, while you weren’t looking, your code has become a monster:
function ShowDialog()
{
ShowDialogContents();
WaitingForDialogToAdvance = true;
}
This is pretty much what your function to show dialog looks like now. What’s the problem here? It makes sense, right?
The problem is that you’ve broken your function up into two functions (pre-waiting and post-waiting) and it’s not clear from reading one of them how they’re supposed to behave together. You’ve entered callback hell, a dark place where all your functions are split into two, threaded together by a nightmarish web of conditional checks and function forwarding. Here, nothing is as it seems and following the logic of your game is nigh impossible.
If you can’t wait inside your functions and you can’t wait outside of them, what are you supposed to do? How do you avoid busy waiting without falling into callback hell?
The answer: It depends.
Asynchrony angst
The solution to this problem depends on what programming language you’re using. “But wait!” you say. “You haven’t said what programming language you’re using yet!” And indeed I haven’t. At this point, we’re still in the design phase of the engine. This is all part of the exploration of the design requirements. Now, we’re finally at the stage where our design requirements are going to start making some decisions for us.
Let’s look at some possible solutions to the waiting paradox.
Ruby: Fibers
Fibers are a form of lightweight threads which are useful for asynchronous tasks. In this case, it would look like:
def show_dialog
show_dialog_contents
wait_fiber = Fiber.new do
wait_for_input
end
wait_fiber.resume
show_next_dialog
end
def wait_for_input
until action_button.pressed
do_nothing
end
Fiber.yield
end
It’s a bit of a simplified example, but the idea is sound. There’s a little bit of a learning curve, but this solves the problem neatly. The whole of the show dialog functionality is contained within the show dialog function, which keeps the code easy to follow and maintain.
Lua: Coroutines
Very similar to Ruby’s fibers in terms of usage, just in Lua instead of Ruby.
C#: Async & Await
A relatively recent development, the async/await feature of C# 5.0 takes the cake for one of my favorite new language features.
async void ShowDialog()
{
ShowDialogContents();
await WaitForInput();
ShowNextDialog();
}
Simple and elegant. All you do is mark a function as async and then await it (aside: The actual implementation of WaitForInput() here is a little hairy, but I won’t sweat the details for now).
Design requirements revisited
So, we’ve got three possible ways to handle the problem of how to implement asynchronous commands in cutscenes. The choice depends on what language we’re using. So, now we have to choose what programming language we’re going to use. To narrow it down further, let’s revisit our list of design requirements.
The other bottleneck requirement is cross-platform deployment. Let’s look at how Ruby, Lua, and C# shape up in these regards:
-Ruby: Limited or no support for games on mobile
-Lua: Available on most platforms but none (yet) support Windows 8 apps
-C#: Available on everything
Lua has always been an agile language and quick to appear on other platforms. The lack of current Windows 8 app support doesn’t concern me as much as the fact that most of the cross-platform game frameworks using it are either too new or just flat-out suck. I tried out Moai several times, but the documentation is abysmal.
Although by default only available on Microsoft platforms, C# is also available on Android and iOS via Mono and has a very powerful framework for it called Monogame. And this is purely subjective, but I would much prefer to build a game engine in a statically typed language for the ease of debugging and architecting.
For that reason, C# was chosen as the language of choice for Metis, which will sit atop the Monogame framework.
The next part will dive deeper into the logistics of cutscene implementation with regards to scripting, and how I both had my cake and ate it, too.
Posts 

Pages:
1
LockeZ
I'd really like to get rid of LockeZ. His play style is way too unpredictable. He's always like this too. If he ran a country, he'd just kill and imprison people at random until crime stopped.
5958
Oh man, I have bad memories of working through this problem... in C... in an online multiplayer game. Imagine working through this in a way that also works in a multiplayer environment, and ALSO lets the player who's in the middle of the cut scene possibly be automatically following another character who isn't in the cut scene and keeps moving past it and starts group combat. FUN TIMES
LockeZ
I'd really like to get rid of LockeZ. His play style is way too unpredictable. He's always like this too. If he ran a country, he'd just kill and imprison people at random until crime stopped.
5958
The game existed for years without cut scenes, and then I added them. It was an ADVENTURE. Also an incredibly confusing mountain of nested recursion and bad hacks. Also, thinking about it now, probably a good lesson in the importance of engines that can actually do what you need them to.
I'm wondering how complex your cut scenes are gonna be. Some games like Disgaea or Fire Emblem have fairly simple ones that are just faces, dialogue, music and fading. But in a more complex game, you'd need a lot more options. Or I guess you could do everything by function calls in the middle of cut scenes.
I'm wondering how complex your cut scenes are gonna be. Some games like Disgaea or Fire Emblem have fairly simple ones that are just faces, dialogue, music and fading. But in a more complex game, you'd need a lot more options. Or I guess you could do everything by function calls in the middle of cut scenes.
How did you handle asynchrony in C? Callbacks? I can't imagine how else you'd do it, but God, that must be ugly.
Fortunately, my cutscenes in VSA aren't too complex ( http://rpgmaker.net/games/3872/images/24885/ ) due in part to the extremely limited budget. They're visual novel-inspired (as in Fire Emblem and Disgaea), but I'm planning some minor flourishes to make them more visually interesting.
Fortunately, my cutscenes in VSA aren't too complex ( http://rpgmaker.net/games/3872/images/24885/ ) due in part to the extremely limited budget. They're visual novel-inspired (as in Fire Emblem and Disgaea), but I'm planning some minor flourishes to make them more visually interesting.
LockeZ
I'd really like to get rid of LockeZ. His play style is way too unpredictable. He's always like this too. If he ran a country, he'd just kill and imprison people at random until crime stopped.
5958
It involved screwing with libraries of modal code written by someone way smarter than me, which are mostly based around pushing input prompts onto a stack for each player, and, I believe, getting the shell to scan the stacks repeatedly to see if anything has popped off. Also some voodoo and rain-dances, and the inputsys.c inherits some hidden file called odinic_invocation_module.c?
Pages:
1












