|
Title: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 01:31:30 AM (http://hollowsdeep.com/tryptophan.png)
While the game I'm developing is largely interactive, only rarely taking control of the character, it still is loaded with lots of events in which I need to take control of several objects in the scene to create some sort of cohesive scene/event when they occur. I'll give an example scene, not unlike something that may happen in my game project: The player is running along, doing their thing. But when they pass a certain point, an event is triggered that causes a bunch of NPCs to come running in (you do not lose control, though). They all stop in their respective places, and after a short moment, control is then suspended and your character turns to face them in the same instant a bit of dialogue sprouts from one of them. The dialogue waits for player input to continue, and perhaps switches between characters as each speak, emulating a conversation, before control is returned to the player. Basically, I'm not sure what an efficient and non-kitten-destroying method of scripting this type of event would be. I'm not sure what a good way is to write the events themselves, how these events know/reference specific objects in the scene (especially ones that may not exist until the event is triggered, or even not until partway through the event), and what the best approach for doing this is that will allow me to give/take control away from the player, even if it's mid-event, without breaking the system. So I guess my question is directed towards people who have either written or worked with a system that's good for doing this kind of thing, if you have any suggestions (bonus points for actually supplying examples/evidence to support your suggestions), I'd love to hear them, and if possible this can even be a topic for discussing the coding/design of these types of systems in general. EDIT: Oh yes, I forgot to mention! The game is currently being coded in C#/XNA. I've written a full level editor for the game that I can add new stuff onto as I please. Title: Re: Coding Cutscenes/Machinima Systems Post by: Evan Balster on October 28, 2011, 01:33:42 AM (I told him this on Skype, just noting it's been discussed)
I recommend the cave story route of a crazy-simple homebrew scripting system: http://lotlot.net/misc/doukutsu/tsc.txt EDIT: I also predict someone is going to recommend you incorporate an actual scripting language, and advise you do NOT, unless you have more to use it for. It's a lot of work. Title: Re: Coding Cutscenes/Machinima Systems Post by: eclectocrat on October 28, 2011, 01:52:31 AM EDIT: I also predict someone is going to recommend you incorporate an actual scripting language, and advise you do NOT, unless you have more to use it for. It's a lot of work. In my experience, writing and debugging a script system is much more difficult than integrating a lightweight one. The general structure you might want to look at is that of a queue of actions. ie: Code: define gameloop: get input update timers check action queue <- here's the magic... draw things etc... So whenever any entity needs to act, you place an action in the queue. Most of the time, the action will just revolve around whatever the player is doing, but sometimes, during scripted sequences, you can push a hella-lot-of NPC actions and have them resolve in sequence. That's the general gist, but there's far more detail to it. You've got to decide how many actions you want to process per loop iteration, when to return to the loop for rendering, if you want to use multiple queue's for multiple actors, etc... Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 01:55:51 AM Also worth noting, I have no experience working with scripting languages or even anything with cutscenes at all before. Just to give a frame of reference, so if I ask something stupid, you're not all :epileptic: :concerned: :facepalm:
Title: Re: Coding Cutscenes/Machinima Systems Post by: Triplefox on October 28, 2011, 03:33:39 AM "Code as content" - which is what this is - is an area where custom syntax is really useful to lighten the iteration times. A timeline language with a tiny bit of instances/named variables is a good start - the last (working) one I used was a studio-internal thingy that looked sort of like this - it's been some years now:
Code: 0.0 turnTo(nearestInstanceOf("player")); ~describes a patrol cycle where the controlled instance looks at the player, walks somewhere, looks at the player again, walks back, then restarts.10.0 walkTo(43,43); 20.0 turnTo(nearestInstanceOf("player")); 30.0 walkTo(0,0); 40.0 goto(0.0); It was not actually as pretty as my example, being sort of hacked together from a shmup spawner language originally. The syntax was based around nested function calls, thus complex things were often Lisp-like, yet with C-style syntax. But the type system was too inconsistent to feel like Lisp, or like C for that matter. We didn't have a function to subtract two numbers until near the end of the project. Strings and numbers bashed up against each other in arbitrary ways. The overall concept showed promise though and we did ship, so it was not a failure. If you already have some form of scripting, you can bolt the timeline on by pre-processing the source to split up the lines into calls at various moments. General-purpose scripting languages could work for this; when they're powerful, though, they open up a can of worms with the integration by having their own type system, memory allocation, etc. I'd rather point you towards doing a custom solution - it's not really wheel reinvention since the purpose of this language isn't really to do computation, it's to fling things around the screen in cinematically interesting ways, and that can depend a lot on the game engine. For custom things here are your syntactical options, in roughly ascending order of complexity:
Forth-like: variables get pushed on a stack when declared, then words(nee function calls) pop them off and process them. Simple and super powerful, but also error-checks poorly since it's easy to end up with a stack of the wrong size. Lisp-like: nested expressions that get evaluated by travelling up and down a tree, implicitly retaining a stack to do computation. Very powerful like Forth, and checks a bit better since you can more easily compare function inputs and outputs. Others: C-like languages, Pascal, etc. These syntaxes mostly build on the tree forms of Lisp to add various bits of syntactical sugar. Added value is heavily debated - as Lisp boosters often point out, you can always write a macro to do an equivalent. (but to some extent this argument is repeatable all the way down to macroassemblers; treating code as data is basically our most powerful structuring tool, and it doesn't matter what face you give it) FWIW my current opinion on best bang-for-the-buck for a timeline language would come from something BASIC-like with some type checking. Something like this: Code: declare I int @0 I := getPlayerDist if I < 100 : 10 @1 goto 0 @10 say "You are close to me!" Here I call to some API function for the player's distance from the entity each second using a goto loop. If the player is less than 100 units away, the entity goes to @10 and says he's close, and then the script terminates. The extensions for different situations(the entity starts moving on the trigger, anims play, etc.) suggest themselves from here. You may have noticed that the "I" variable seems superfluous - this is something implied in the semantics I've picked out. Once we have a variable and we know what it is, logic and mutability is easy; but even adding the bit of sugar that would let us call getPlayerDist from "if" burdens the language implementation, since we no longer get to assume that the "if" statement always acts on pre-declared variables. This kind of decision is up to you, but it distracts from the core problem: It's supposed to do cutscenes. Instead I would point to other features: the type declaration makes it easy to allocate all the memory statically per script(always a nice thing) and make a few sanity checks at compile time. When code gets repetitive you could apply subroutines. The expectation is that no one script is going to be some monstrous engineering challenge, so you don't need to lighten the syntax very much, especially if you opt to include more powerful types and structuring tools like arrays and macro expansion. (And you always have the out of writing a hard part in the engine!) You'll also want to be able to tie collision into the system to listen to player-driven events like "hit" or "pushed". You can do a lot with polling like what I did in the example, but it gets pretty hacky. Listening to events, of course, motivates some decision on whether each entity gets parallel or procedural timelines when many events get fired. In the past I waffled on this, but now I would just point to ZZT-OOP (http://en.wikipedia.org/wiki/ZZT-oop), which is procedural per-entity and does the job tremendously well by running a global timeline per entity, letting the events work as labels and getting "locked" or "unlocked" in a stack. It suffices for the vast majority of stuff in these types of games(everything from hp counts in boss fights to one-time story events) - we expect characters to only do one thing at a time, so the need for combinations doesn't arise so often. ZZT-OOP also suggests message passing methods - simply apply names or tags to entities and allow them to direct each other to the named labels, as if they were events. There are always ways to inject parallelism back into the system by having a global controller, special-case code, etc. Last of all, do whatever you can to make it easier to iterate on these, or preferably to get them right in one pass. Having to play these scripted events over and over to fix minor issues can add up really quickly. Title: Re: Coding Cutscenes/Machinima Systems Post by: st33d on October 28, 2011, 04:06:47 AM In a recent project for Nitrome I had to build an event system.
What I discovered, was that to do it well I had to build a game engine within a game engine. Hear me out on this one... The LevelEvent hijacks the camera, and any elements that need to be a part of the event. The rest of the game is frozen (paused). Then the camera moves to the event location, simulates those elements in the event, and when everything is done the camera scrolls back to the player and releases control of those elements. The principal use of this is switches. You trigger a switch, then the game shows you what has happened - animating a door open or activating a fireball launcher. But this forms a perfect foundation for scripted events. You trigger a switch, then the game shows you some npc actions, you tidy it up, then get back to the gameplay. When you have full control of an event, conversation is simple. For storing a conversation I use this format: Code: (barry:hello there) (garry:alright mate!) And I capture those out into an array with the following regex operation: Code: public static const MATCH_BRACKETED_CONTENT:RegExp =/(?<=\()[^\(\)]*(?=\))/; public static const REPLACE_BRACKETED_CONTENT:RegExp =/\([^\(\)]*\)/; ... do{ var line:Array = conversation.match(MATCH_BRACKETED_CONTENT); if(line && line.length > 0){ speech.push(line[0]); } conversation = conversation.replace(REPLACE_BRACKETED_CONTENT, ""); } while(line && line.length > 0); Title: Re: Coding Cutscenes/Machinima Systems Post by: DeadPixel on October 28, 2011, 04:17:12 AM Since I assume you're the only coder on the project using an external 'language' to script in seems redudant since you're already using C# which is pretty easily parseable.
I haven't implemented this myself, but a simple system might be look like this: Code: struct ScriptCommand { public string Command; public object Data; public ScriptCommand(string command, object data) { this.Command = command; this.Data = data; } } // In your base game object Queue<ScriptCommand> commands = new Queue<ScriptCommand>(); public void AddScriptCommand(ScriptCommand command) { commands.Enqueue(command); } protected virtual void ProcessScriptCommands(GameTime gameTime) { if(commands.Count > 0) { ScriptCommand command = commands.Peek(); bool commandComplete = false; switch(command.Command) { case "walkTo": // Move the object. Vector2 position = (Vector2)command.Data; this.Position += Vector2.Normalize((position - this.Position)) * this.WalkSpeed; if(this.Position >= position) commandComplete = true; break; case "turnTo": // Turn... break; case "say": // Chat bubbles... commandComplete = true; break; } if(commandComplete) commands.Dequeue(); } } // In derived objects public void Update(GameTime gameTime) { this.ProcessScriptCommands(gameTime); } protected override void ProcessScriptCommands(GameTime gameTime) { // Handle object specific commands then process global ones from base. base.ProcessScriptCommands(gameTime); } // In scene... Actor myActor = new Actor(); myActor.AddScriptCommand("walkTo", new Vector2(30, 40)); myActor.AddScriptCommand("wait", 300); myActor.AddScriptCommand("say", "Nice to meet you!"); public void Update(GameTime gameTime) { myActor.Update(gameTime); } Then add in some timings/delays, flesh out the commands, and you're done. Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 04:27:15 AM Yeah, I think I know more or less how I want to code the events that actually control the objects, but it's the development process that's weighing on me. For example, scenes will take a lot of tweaking, meaning if I was just coding it right into the game engine, I'd have to run, watch, stop, change values, run, watch, stop, change values, etc. I'm already getting a headache from the tedium, and I'm just talking about it.
Title: Re: Coding Cutscenes/Machinima Systems Post by: DeadPixel on October 28, 2011, 04:30:58 AM So you're more worried about how to implement scripting controls from your editor, rather than how to implement a cutscene/machinima system?
Title: Re: Coding Cutscenes/Machinima Systems Post by: eclectocrat on October 28, 2011, 04:36:29 AM I like DeadPixel's example code.
If you really don't need conditionals at all, then why not read a plaintext file? Code: actorName.walkTo(30, 40) actorName.wait(300) actorName.say("Nice to meet you!") That should be very easy to parse and allow you to have a faster dev cycle. Just designate a special button during development that resets the actors and reloads the script file you are testing (for example 'P'). Keep a notepad instance open alongside your game and you can edit, press control-S, alt-TAB back to your game, press P to try the new script. Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 04:40:53 AM I'm more worried about it, but interested in the whole shabang. Already this is good, just want to hear what methods folks used, how well/bad they worked, so I can get some sort of useful perspective on it before I destroy my game's source.
Getting some good ideas for syntax stuff too, need to figure out how to parse files properly of course, never used regular expressions before (other than just copypasta-ing ones off the internet I needed), so that's good. It definitely seems like I'll have to add in instance naming to the editor, and use that in the scripts. Will probably add in a script editor as well, so I can write/test/write/test easily without ever having to re-build. This is probably the approach I am leaning towards, though it's gonna take me awhile to get the scripts saving/loading/reading properly, gotta make sure I do it right the first time, tired of having to re-write stupidly written classes. Title: Re: Coding Cutscenes/Machinima Systems Post by: DeadPixel on October 28, 2011, 04:45:45 AM Reflection can be your best friend in these cases! :) Being able to edit script values on the fly in-game can be very useful for quick iteration. Then you can just save out to file to load later once everything is kosher.
If your entities are rather straight forward (no complex data types as public members, etc) then XNA's XML loading can be an easy fit for you. You can then use the content pipeline to load your entities and their scripted data directly. Otherwise, doing it by hand isn't too hard either. Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 04:47:13 AM I guess a difficult situation for me to describe using this system is specifically scripts that need to create objects. Such as the example in my original post: character appear from the left, run in, and stop. My script needs to know which objects to create, and what name to give them (so it can give them commands).
One thing I can think of that'd likely be a simple/elegant solution would be a flag for "disabled" entities in the editor, where I can actually create objects but disable them, so when the scene starts, they are just tossed into a cache, where they keep their instance name. Then, I'll have simple create() and destroy() functions in the scripts for moving these objects to and from the disabled bin in the scene. Then I could place objects, give them all the starting parameters/positioning/names I want right in the editor using all the tools I've build, and would know how/what I'm referencing in the scripts. This sounds nice... HMMmmmm Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 04:48:57 AM Reflection can be your best friend in these cases! :) Being able to edit script values on the fly in-game can be very useful for quick iteration. Then you can just save out to file to load later once everything is kosher. If your entities are rather straight forward (no complex data types as public members, etc) then XNA's XML loading can be an easy fit for you. You can then use the content pipeline to load your entities and their scripted data directly. Otherwise, doing it by hand isn't too hard either. My second reply you ninja'd addressed this as well, but yeah, already using XML serialization for saving/loading the entire scenes via the editor, so I've already got a format working for the entities, though I think the "anything that's in a scene/event exists from the start, nothing is actually *created* by scripts" is a safer and less confusing solution for my purposes. Title: Re: Coding Cutscenes/Machinima Systems Post by: randomnine on October 28, 2011, 04:51:00 AM For player/NPC control, I recommend abstracting their control out to a Controller interface. This interface has functions like "shouldFaceLeft()", "shouldMoveRight()", "shouldJump()", etc - including ways to trigger special animations. The player's actor normally has a Controller attached which interprets keyboard input as specific signals. NPC actors have Controllers attached which run AI and fire off control signals. The actor code itself simply interprets these input signals - it doesn't care whether it's being controlled by a player, AI, or script.
During a cutscene, you override the normal controller with a dead simple one that lets you switch control signals on and off manually from code/script. You can add slightly more elaborate functions like "move left until x = blah" later. You restore control to the player/AI by reconnecting the normal controller. I use this setup in all my games now and it makes switching between player/AI control and various kinds of scripted behaviour a doddle. For actually lining up cutscene events... an action queue/timeline is a good approach, but whatever you do, take into consideration variable length events. This can involve things like moving an actor to a required position from an unknown starting position, letting players fast forward dialogue, or having dialogue stay up until players press a button. As for having to watch the whole thing and iterate to get the pacing and everything right... there's literally no way round that (though for sanity's sake, for cutscenes over 30 seconds or so, I'd suggest implementing a method of fast-forwarding to key points by simply setting all actor positions/states). If you can't launch your game and start a given cutscene in a few seconds, you either need to get your game launching faster or you'll have to build in a scripting language and support reloading scripts at runtime to get iteration times down. Title: Re: Coding Cutscenes/Machinima Systems Post by: Average Software on October 28, 2011, 04:59:41 AM I have something similar to this in my project, but their are some key differences since mine is a turn based tactics game. In mine, events occur on specific turns, in a very specific sequence.
I designed a quick and dirty scripting language, and the script for each stage is parsed and broken into event objects which are stored in a queue (well, actually a map of queues, since each turn could potentially have its own queue). On a turn, the game looks at the event queue, and if there is in anything in it, it suspends the game and starts processing the events until they're all gone. It isn't hard to whip up some kind of data structure for the events, I use this: Code: package Script_Events is -- Enumeration of all event types. type Script_Event_Type is (Message_Popup, Scroll_To_Hex, Highlight, Pause, Add_Unit); -- Event record. type Script_Event(Event_Type: Script_Event_Type) is record case Event_Type is when Message_Popup => Text: String_Lists.List; when Scroll_To_Hex | Highlight => Hex: Hex_Location; when Pause => Time: Natural; when Add_Unit => Unit_Hex: Hex_Location; Owner: Color; Personnel: Nullable_Personnel := None; Vehicle: Nullable_Vehicle := None; end case; end record; package Event_Queues is new Indefinite_Queues(Script_Event); package Event_Maps is new Ordered_Maps(Turn_Count, Event_Queues.Queue, "=" => Event_Queues."="); end Script_Events; The scripting language itself looks like this: Code: -- Event script for corporate spy tutorial on turn 0 message "In this lesson you will be introduced to the " "corporate spy and the dangers of defensive turrets." end message scroll 11 13 highlight 11 13 message "This is an anti-vehicle turret. After all units have moved, defensive " "turrets will open fire on all enemy units within range." "" "Anti-vehicle turrets only fire at ground based vehicles. Air vehicles and " "personnel are not affected by them." end message scroll 15 13 highlight 15 13 message "Here is an anti-personnel turret. These turrets only affect personnel units, " "vehicles may pass by them safely." end message scroll 5 13 message "You need to get rid of these fortifications. Unfortunately, you're lacking combat units " "at the moment so you'll need to be a little more creative." end message scroll 6 11 highlight 6 11 message "Here is a corporate spy. Corporate spies have the ability to capture enemy defensive " "turrets and supply stations. However, a structure cannot be captured if it currently " "lies within its owner's territory." end message scroll 11 13 message "The two anti-vehicle turrets lie outside of blue territory, and are therefore " "subject to capture." end message scroll 15 13 message "The two anti-personnel turrets are shielded from capture by the nearby billboards." end message scroll 23 14 highlight 23 14 message "This is a salt float. Salt floats provide you with salt, which you can use to produce salt " "batteries. Salt batteries are powerful artillery units that can make short work of ground " "based targets." end message scroll 5 13 message "You have a single wage slave awaiting deployment, and you need to get him to that salt " "float! Once it has been captured, build some salt batteries and use them to remove all " "Patty Prince structures from the map." "" "You have 50 turns to complete this mission. Good luck!" end message end turn on turn 40 message "Not done yet? Here's a hint:" "" "Placing your wage slave into a supply duck will allow it to pass the anti-personnel " "turrets safely." end message end turn on turn 50 message "This is the last turn. You haven't screwed this up, have you?" end message end turn If you know anything about writing parsers, doing your own scripting language is quite trivial, and if you do it right, it's very easy to extend it to support new options. Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 05:06:40 AM For player/NPC control, I recommend abstracting their control out to a Controller interface. This interface has functions like "shouldFaceLeft()", "shouldMoveRight()", "shouldJump()", etc - including ways to trigger special animations. The player's actor normally has a Controller attached which interprets keyboard input as specific signals. NPC actors have Controllers attached which run AI and fire off control signals. The actor code itself simply interprets these input signals - it doesn't care whether it's being controlled by a player, AI, or script. During a cutscene, you override the normal controller with a dead simple one that lets you switch control signals on and off manually from code/script. You can add slightly more elaborate functions like "move left until x = blah" later. You restore control to the player/AI by reconnecting the normal controller. I use this setup in all my games now and it makes switching between player/AI control and various kinds of scripted behaviour a doddle. Awesome, I actually re-coded the entire game engine and editor a few weeks back and included just this, for exactly this reason. It actually works quite well, especially considering most/all of my event instances are going to be physics objects that need to correctly interact with the solid surfaces in the game (that means angles, direction, slope speed, as well as playing the correct sound effects and spewing particles), so I'll definitely be glad of this setup later. Title: Re: Coding Cutscenes/Machinima Systems Post by: Hima on October 28, 2011, 06:30:01 AM I wrote homebrew basic scripting language as well. It was fun! But instead of having to go through all the string manipulation and parsing, I think you'll be better off using something like JSON or YAML. You mention you're using XNA, which unfortunately doesn't have a good YAML library yet, so you might wanna go with JSON.
The JSON file is basically just an array of command. So it'll look something like Code: [ ["ShowMessage", "Hello, my name is A"], ["WaitForKeyPress"], # Move A to (200,0), taking 2 seconds. ["MoveActor", "A", 200, 0, 2], ["WaitForSeconds", 2], ["ShowMessage", "Blahblahblah"], ["WaitForKeyPress"] ] Once you parse the JSON file, you'll get an array of commands. I'd have one object act as a cutscene manager that will keep executing the current command until you are forced to pause by something like WaitForKeyPress or WaitForSeconds. C# has Reflection so you can use String to call a method name. If not, then you can just use if else to check which method to call. Each method would accept an ArrayList as an argument. Then you have to work on picking up each argument from the array by yourself. I prefer using this method because it's very easy to work with JSON file. You also have an advantage of writing the cutscene script in google doc and use a tool to convert spreadsheet to JSON. The only downside is that JSON doesn't come with comment, but I customized the parser library that I use to read anything after '#' as a comment. Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 28, 2011, 07:36:06 AM Yeah, i'm familiar with reflection in C# as well as working with JSON, I'm not sure if I'd be comfortable with the syntax though, I prefer something a bit cleaner I can chug out, and don't think I'd mind writing a simple parser too much.
The spreadsheet conversion is clever! But I'm more interested in being able to work with it directly in-editor and making the change-tweak-change-tweak process as painless as possible without overdoing it. Title: Re: Coding Cutscenes/Machinima Systems Post by: lasttea999 on October 28, 2011, 09:55:42 PM Allow me to watch this thread; these methods sound useful for a variety of projects. I was thinking of directly programming this kind of thing, and thinking about how inelegant that seemed...
Title: Re: Coding Cutscenes/Machinima Systems Post by: BorisTheBrave on October 29, 2011, 04:00:05 AM One of the important aspects of cutscene coding is you want to wait for events, a lot. Waiting for the user to click next, e.g. Hence why people recommend scripting languages or state machines, they make it easy to block execution on user input without hampering the rest of your game.
As you are using C#, a rather robust language, some other options are open to you, simply writing the "script" in C#, and then using threading or co-routines. See how Unity does it (http://unity3d.com/support/documentation/ScriptReference/index.Coroutines_26_Yield.html). There's nothing stopping you creating your own creating your own "events" to wait on: Code: void script(World world) { bool result = yield return dialogYN("Take the quest?"); while(!result) result = yield return dialogYN("But thou must!"); Actor guardA = world.lookup("guardA"); Actor guardB = world.lookup("guardB"); guardA.walkTo(10,10); Anim anim = guardB.playAnimation("yawn"); yield return waitForAllEvents(new IsIdle(guardA), new AnimComplete(anim)); } At each place "yield return" is called, the co-routine engine (also known as a "trampoline function"), suspends your script function, and will resume it when the event completes. Internally, the compiler is creating a state machine for you, and saving all the relevant variables you need to be able to resume. C# itself is moderately flexible as a scripting language, as you compile it into independent assemblies that are loaded at runtime. Title: Re: Coding Cutscenes/Machinima Systems Post by: ChevyRay on October 29, 2011, 02:11:56 PM Thanks for the input, Boris, going to mull over that for a bit, lot's of new stuff to take in. Will respond with further input, I'm kinda putting together a few things in the engine right now, feeling things out (really don't want to have to undo a bunch of code later, so I'm treading carefully).
Your example is tight. Title: Re: Coding Cutscenes/Machinima Systems Post by: Sqorgar on November 02, 2011, 06:12:05 PM Try to boil it down to the most basic elements. You need two things:
1) Actions which cause the actors to perform things like walking around, playing animations, or appearing/disappearing. You need to be able to specify the target of each action in some way, like through a GUID or a dictionary keyed to actor names. 2) A way of coordinating these actions along a timeline, such that you can ensure that action B takes place after action A. This is further broken down into two needs: ...2a) Knowing when an action is finished. ...2b) Being able to perform an action at a specific, later point in time. First part is easy. Just use the Command pattern (I think that's the one) to encapsulate each action that you will perform. Create an action queue and a director which will grab actions out of the queue and disseminate them amongst the various actors. The director can also be given meta-actions, which inform the director to wait for something, change state, or convert actions on the fly. Complex actions involving multiple steps or participants may be broken up into several smaller actions by the director (such as a round of combat being broken up into A swinging a sword, B getting hit, B's hp draining, etc). Second part is where it gets tricky. Personally, I create an action queue on each actor, such that you can queue up multiple actions and the actor will perform them automatically to completion, in order. Then, the director will grab actions until it hits a director action to wait, and it will wait until all actors are not performing actions and have zero actions in their personal queue before grabbing more actions. I'm writing a turn-based game, which is sort of like dynamically writing a mini-cutscene each turn. I queue up a bunch of actions for the whole sequence at the front. In places where multiple actors must coordinate, the action will take care of timing by inserting appropriate delay actions into the actors' personal queues as needed. Then everything runs automatically until all the actions are complete and the director grabs the next set of actions. This would not work with your game since you don't want to give up player control as actions are happening, and my action queue assumes all simulation behavior is concluded before the queue is filled. I would not create a scripting language right off the bat, since scripting languages are just a friendly interface to a problem you've already solved. Title: Re: Coding Cutscenes/Machinima Systems Post by: zamp on November 05, 2011, 09:13:38 AM EDIT: I also predict someone is going to recommend you incorporate an actual scripting language, and advise you do NOT, unless you have more to use it for. It's a lot of work. Pffft. The day I implemented lua into my game to govern all scripts (npcs walking, spawning dieing) was the day I said booyah. Although I did make a special class to do sequential things. Here's an example script: Code: -- enable generator button function enableButton() level.getEntityFromRef("generatorOnButton").properties.enable = 1 end local d = as3.class.WorldDialog.new() d.playSound("sound_duct_tape") d.wait(2500) d.say("player", "I hope that holds") d.callback(enableButton) d.start() WorldDialog is a class that holds a queue of things to do and it just does them one after another. The wait queue item is a bit special since that's the only one that halts the script until the time provided has passed. Here's the class itself, written in Actionscript 3 but the logic behind it is the same no matter what language it's written in. Essentially it's a Queue Stack/Proxy class if you want to use the city-boy fancy class names. Code: package { /* snip */ public class WorldDialog { public function say(entity:String, message:String):void { _queue.push( { type:"say", entity:entity, message:message } ); } public function wait(time:uint):void { _queue.push( { type:"wait", time:time } ); } public function callback(func:Function):void { _queue.push( { type:"callback", callback:func } ); } public function playSound(sound:String, vol:Number = 1):void { _queue.push( { type:"playsound", sound:sound, vol:vol } ); } public function update(e:TimerEvent):void { if (_timer.t() && _queue.length > 0) { var obj:Object = _queue[0]; Game.L(this).debug("Processing dialog:", obj.type); switch (obj.type) { case "wait": _timer.r(obj.time); break; case "say": Game.level.getEntityFromRef(obj.entity).say(obj.message); break; case "callback": obj.callback(); break; case "playsound": Game.sfx.play(Game.r.getResource(obj.sound).getAsSound(), obj.vol); break; } _queue.shift(); if (_queue.length == 0) { _timer2.removeEventListener(TimerEvent.TIMER, update); _timer2.stop(); } } } public function start():void { _timer2 = new Timer(50); _timer2.addEventListener(TimerEvent.TIMER, update); _timer2.start(); } } } There are some problems with this system if you have quicksave capability in your game. If the player saves between a script and then loads the game the script resets and starts over. I "fixed" this problem by disabling saving whenever a script is running. Here's another script. This one will trigger when a player moves into a room. Player will then say NOPE and run out. Code: local playerRef = "player" -- create a dialog local dialog = as3.class.WorldDialog.new() dialog.freezePlayer() dialog.say(playerRef, "NOPE!") dialog.moveToTarget(playerRef,"controlTargetA", 1) dialog.lookAt(playerRef,"test") dialog.waitMovement(playerRef) dialog.unfreezePlayer() dialog.start() Title: Re: Coding Cutscenes/Machinima Systems Post by: BorisTheBrave on November 05, 2011, 09:54:38 AM Lua is excellent for these sorts of things, thanks to being one of the few languages with true co-routines (the other popular ones being Ruby and Scheme). No need for those pesky "yield" statements everywhere, like in my C# example, you can transparently have method calls that suspend execution. But you're not using that, which is a shame.
Simply shoving things on a queue means you miss out on many of the features of a scripting language. You cannot have any control flow, you cannot call a method on WorldDialog and have it return a meaningful result, forcing you to write callbacks. Title: Re: Coding Cutscenes/Machinima Systems Post by: zamp on November 05, 2011, 08:59:22 PM Thanks. I didn't know about lua coroutines. I'm pretty sure I'll use them from now on :coffee:
|