460 likes | 609 Views
The Picture Worth a Thousand Bugs:. A System for Rigging Complex Game Missions. Bruce Oberg, Programming Lead. Sucker Punch Productions. The Project. Sly 2: Band of Thieves Released in September 2004 Action / Adventure 2 years in development Staff of 35 17 art 4 design 5 rigging code
E N D
The Picture Worth a Thousand Bugs: A System for Rigging Complex Game Missions Bruce Oberg, Programming Lead Sucker Punch Productions
The Project • Sly 2: Band of Thieves • Released in September 2004 • Action / Adventure • 2 years in development • Staff of 35 • 17 art • 4 design • 5 rigging code • 5 engine code • 4 test
What are we talking about? • “Rigging” • Gameplay code for objectives • Not in engine, compiled with art • Written in Scheme (i.e. lispish) • “Complex Game Missions” • Sharing world geometry • Sophisticated setup • Varied success/failure conditions • Parallel sub-missions • Permanent world changes
Sly 2: The Plan • A system for organizing this gameplay code • Goals • Quick navigation to missions • Easy checkpointing • Minimize inter-mission cruft • Encapsulation of storage • Surprises • Great Debugging • Great Visualization
The Sly 2 DAG • Each node is a “task” • A smallish play item (e.g. open door) • Tasks can have one of four states: • Unavailable • Available • Complete • Final (mostly like Complete)
The DAG Rules • All tasks initially Unavailable • A task becomes Available once all its predecessors become Complete • All predecessors of Available and Complete tasks must be Complete • All successors of Available and Unavailable tasks must be Unavailable
Changing task states • Tasks become Complete in response to requests from: • In-game art objects/triggers • Cut scene sequences • Themselves(!) • Completion requests are rejected when task is notAvailable • No error, just dropped
How to think of Progress • A “front” of Available tasks moving through the DAG
Where’s the beef? • Gameplay code attached to tasks as callbacks • Callback Types • State changes • Engine triggered events • Other signals • Callbacks do real work • Triggering cut scenes • Object management
Live Demo - Progress • The start of “France” from Sly 2, showing three tasks • Hands-off sequence: Sly runs across roofs to safe-house • Player exits safe-house • Hands-off sequence: Sly climbs pipe to dialog on roof • First mission trigger point visible in world
What was happening? Run-up Roof Intro Satellites Mission
The Run-up • Run-up task set to Available • Starts “hands-off” sequence • Suspends controller • Custom camera • Puts all characters on paths • At end, Run-up task requested to be marked Complete (and succeeds)
The Safe-house • Roof Intro task set to Available • Player control now • Safe-house exit door Completes Roof Intro task
The Roof • Roof Intro task set to Complete • Starts “climb to roof” sequence, including binoc-u-com dialog • Satellites mission is Available, so start point visible during dialog
Missions: Groups of Tasks • Our definition: a contiguous sub-DAG of tasks • Rule: one entry task and one exit task • When exit task becomes Complete, all mission tasks (and predecessors) are marked as Final
Forcing the Issue • Request mechanism fine for simple gameplay progress • The big win: • Checkpointing / Debugging • How? • Forcing the state of tasks • Choose a target task/state • Set all states so that DAG rules are being followed
Sly 2 Checkpoints • Tasks with an associated location and character • A checkpoint task is marked as the latest onewhen it becomesAvailable • Upon death or other failure, code can ask for a rollback to the latest checkpoint
More on Checkpoints • Rolling back: • Resets world • Forces checkpoint task to Available • Puts character at checkpoint location • Can roll forward or back to any checkpoint at any time • Hugely useful for reproducing bugs
Live Demo - Checkpoints • Use “Reset to checkpoint” debug menu to jump to different missions • “Tasks” debug menu can tweak any task at any time • Can go forward and backward
An Algorithm: State Forcing • Start with a task and a desired state • Recursively walk backwards through predecessors • Stop when predecessor is already in desired state • Mark predecessor with desired state • Skip predecessors already marked
State Forcing A B C D E F H G J I A
State Forcing Second Phase • Go through all tasks in “predecessors first” order • Set each task to its desired state • Recursively walk forward through successors • Skip tasks marked with a state
1 A B C 2 B D E 3 D G 4 G I “Predecessors First” A B C D E F H G J I A
1 A A Skipped B B C C 2 B B Skipped D D E E 3 D D Skipped G G 4 G G No Change I I “Change Successors”
Creating graphs • Don’t bother with WYSIWYG editing • Complex problem • Graph manipulation pretty rare • Textual input more than adequate
GRAPHVIZ • We used the GRAPHVIZ package from Bell Labs (www.graphviz.org) • DAG snapshots super quick • Engine walks DAG and writes text file (“.dot” format) to PC • Small batch file calls dot.exe with a few options and launches result
DAG Images Became Crucial • Instant visual game status • Attached to bugs • Documentation tool: • Mission reordering • Episode comparisons
Our Dirty Little Secret • “Focus” • One task marked as current • Focus changes use same mechanism as state changes (e.g. requests / callbacks) • Both Available and Complete tasks can get focus • Focus for missions as well • Didn’t work as planned
Out of Focus • The Dream • Focus task connected to help • “What am I doing?” • Focus count maintained • Skipping cut scenes • The Reality • Tasks very fine grained • Help based on search of DAG • Used for arbitrary signaling • Lots of Set/Clear pairs • Bounced around
A look at a task • Tasks defined in text files • Source control works well • Compiled with art files • Changes to tasks or DAG do not require engine rebuild • At run-time, instances of C++ classes created
Task Name: unique across entire game Class: creates C++ class instance Linkage: defines edges in DAG Checkpoint includes location and character Helper property: when task is in this state(s), object is added to world, otherwise removed Dimitri start task /name: t1_follow_start /cls:game_task /mission: m1_follow_dimitri /add_predecessor: t1_follow_intro /checkpoint: entrance_follow_retry /checkpoint_wid: f_nightclub_exterior /checkpoint_player: sly /add_object_on_states: waypoint_boat_bell (available)
Standard Callback Non-standard Setup / Teardown Sophisticated help Dimitri start task (define OnEnterTaskState (lambda (this) (define boat (find-object oid:f_dimitri_boat)) (case this.state ((gts:Available) (boat.LightsOn)) ((gts:Unavailable gts:Final) (boat.LightsOff)) ((gts:Complete) (boat.LightsOff) (case this.focus_count ((3) (do_voice tbid:dimitri_remind)) ((4 5) (do_voice tbid:dimitri_remind_roof)) ) ) ) ) )
Stuff we’d change • Arbitrary task grouping • Chains: groups of tasks within a mission • Heists: groups of missions • Standardized handling of major transitions • Replace focus with signals • Better detection of “stuck” scenarios
Benefits of the DAG • Debugging • DAG images super useful • Instant visual description of game state • Run-time Tracing • All DAG requests/changes retained in printable form • Traced in real time, or post-hoc via ring buffer • Checkpointing built in
Benefits of the DAG • Mission / task rearrangement trivial • Save Game a simple dump of states • Riggers never worried about persistence, ever
Questions? Answers! www.suckerpunch.com www.graphviz.org