Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I've been struggling with how to implement scripting in my game engine. I only have a few requirements: It should be intuitive, I don't want to write a custom language, parser and interpreter, and I don't want to use threading. (I'm certain there's a simpler solution; I don't need the hassle of multiple game logic threads.) Here's an example script, in Python (aka pseudocode):

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    alice.walk_to(bob)
    if bob.can_see(alice):
        bob.say("Hello again!")
    else:
        alice.say("Excuse me, Bob?")

That epic piece of storytelling poses implementation problems. I can't just evaluate the whole method at once, because walk_to takes game time. If it returns right away, Alice would start walking up to Bob, and (in the same frame) say hello (or be greeted). But if walk_to is a blocking call that returns when she reaches Bob, then my game gets stuck, because it's blocking the same thread of execution that would make Alice walk.

I considered making each function enqueue an action -- alice.walk_to(bob) would push an object onto a queue, which would get popped off after Alice reached Bob, wherever he was. That's more subtly broken: the if branch is evaluated immediately, so Bob might greet Alice even if his back is turned to her.

How do other engines/people handle scripting without making threads? I'm starting to look in non-game-dev areas, like jQuery animation chains, for ideas. It seems like there should be some good patterns for this sort of problem.

share|improve this question
1  
+1 for the question but especially for "Python (aka pseudocode)" :) –  Ricket Sep 4 '10 at 19:00
    
Python is like having a superpower. –  ojrac Sep 4 '10 at 19:59
add comment

3 Answers

up vote 3 down vote accepted

The way something like Panda does this is with callbacks. Instead of blocking it would be something like

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    def cb():
        if bob.can_see(alice):
            bob.say("Hello again!")
        else:
            alice.say("Excuse me, Bob?")
    alice.walk_to(bob, cb)

Having an on-complete callback allows you to chain these kinds of events as deeply as you want.

EDIT: JavaScript example since that has better syntax for this style:

function dramatic_scene(actors) {
    var alice = actors.alice;
    var bob = actors.bob;
    alice.walk_to(bob, function() {
        if(bob.can_see(alice)) {
            bob.say('Hello again!');
        } else {
            alice.say('Excuse me, Bob?');
        }
     });
}
share|improve this answer
    
It works enough for a +1, I just think it's wrong for designing content. I'm willing to do some extra work on my end so that the scripts look simple and clear. –  ojrac Sep 4 '10 at 19:58
1  
@ojrac: Actually this way is better, because here in the same script you can tell multiple actors to start walking at the same time. –  Bart van Heukelom Sep 4 '10 at 20:48
    
Ugh, good point. –  ojrac Sep 4 '10 at 21:36
    
To improve readability however, the callback definition could be nested inside the walk_to() call, or put after it (for both goes: if the language supports) so that code that gets called later is seen later in the source. –  Bart van Heukelom Sep 4 '10 at 22:31
    
Yeah, Python isn't really great for this kind of syntax unfortunately. It looks a lot nicer in JavaScript (see above, can't use code formatting here). –  coderanger Sep 4 '10 at 23:22
show 1 more comment

The term you want to search for here is "coroutines" (and usually the language keyword or function name is yield).

Coroutines are program components that generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations.

The implementation will depend first of all on your language. For a game you want the implementation to be as light weight as possible (lighter than threads or even fibres). The Wikipedia page (linked) has some links to various language-specific implementations.

I hear Lua has built-in support for coroutines. So does GameMonkey.

UnrealScript implements this with what it calls "states" and "latent functions".

If you use C# you could look at this blog post by Nick Gravelyn.

Additionally the "animation chains" idea, while not the same thing, is a workable solution to the same problem. Nick Gravelyn also has a C# implementation of this.

share|improve this answer
    
Nice catch, Tetrad ;) –  Andrew Russell Sep 4 '10 at 10:09
    
This is really good, but I'm not sure it gets me 100% of the way there. It looks like coroutines let you yield up to the calling method, but I want a way to yield from a Lua script, all the way up the stack to the C# code without writing while (walk_to() != done) { yield }. –  ojrac Sep 4 '10 at 18:34
    
@ojrac: I don't know about Lua, but if you're using Nick Gravelyn's C# method you could return a delegate (or an object containing one) that holds the conditional for your script manager to check (Nick's code just returns a time which is implicitly a conditional). You could even have the latent functions themselves return the delegate, so you could write: yield return walk_to(); in your script. –  Andrew Russell Sep 5 '10 at 0:55
    
Yield in C# is awesome, but I'm optimizing my solution for simple, hard-to-mess-up scripting. I'll have an easier time explaining callbacks than yield, so I'll accept the other answer. I'd +2 if I could. –  ojrac Sep 5 '10 at 3:06
1  
You don't normally have to explain the yield call - you can wrap that in the "walk_to" function, for example. –  Kylotan Sep 6 '10 at 14:24
show 5 more comments

not going threaded is smart.

Most game engines work as a series of modular stages with stuff in memory driving each stage. For your 'walk to example', you usually have an AI stage where your walked characters are in a state where they shouldn't be looking for enemies to kill, an animation stage where they should be running animation X, a physics stage (or simulation stage) where their actual position is updated, etc.

in your example above, 'alice' is an actor composed of pieces that live in many of these stages, so a blocking actor.walk_to call (or a coroutine you call next() on once per frame) would probably not have the proper context to make lots of decisions.

Instead, a 'start_walk_to' function would probably do something like:

def start_cutscene_walk_to(actor,target):
    actor.ai.setbrain(cutscene_brain)
    actor.physics.nocoll = 1
    actor.anims.force_anim('walk')
    # etc.

Then, your main loop runs its ai tick, its physics tick, its animation tick, and its cutscene tick, and the cutscene updates the state for each of the subsystems in your engine. The cutscene system should definitely track what each of its cutscenes is doing, and a coroutine driven system for something linear and deterministic like a custscene might make sense.

The reason for this modularity is that it just keeps things nice and simple, and for some systems (like physics and AI), you need to know the state of everything at the same time to resolve things properly and keep the game in a consistent state.

hope this helps!

share|improve this answer
    
I like what you're going for, but I actually feel strongly about the value of actor.walk_to([another actor, a fixed position, or even a function that returns a position]). My goal is to provide simple, understandable tools and handle all the complexity away from the content creation part of the game. You also helped me realize that all I really want is a way to treat each script as a finite state machine. –  ojrac Sep 4 '10 at 18:39
    
glad I could help! I felt like my response was a little off topic :) definitely agree with the value of an actor.walk_to function for accomplishing your goals, I'm looking forward to hearing about your implementation. –  Aaron Brady Sep 4 '10 at 20:19
    
It looks like I'll go with a jQuery-style mix of callbacks and chained functions. See accepted answer ;) –  ojrac Sep 5 '10 at 3:08
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.