![]() |
||
|
|
|
Making of Muridae Massacre: Modularizing Game Logic Organizing a project’s scripts into logical modules is vitally important in maintaining both the project’s integrity and the sanity of its team. In programming traditional source/compile-based code projects, maintaining code integrity is somewhat obvious: one file should contain the code for a function that is used throughout the project. However, Dev’s scripting system is engineered for prototyping as well as performance; it is very easy to quickly piece together desired functionality to test a design feature. It takes discipline and a little foresight to properly structure a composition so that it remains logically organized and easily editable throughout production. The production goal with MM was to have central responsibility in the scripts for a single event or function, rather than copy identical schematic to multiple locations. This means that only one script needs to be edited in order to change the response to an event. Additionally, modularizing code creates cleaner relationships. Only one script should modify a certain value or respond to a certain event. For instance, in MM the only script that modifies the health attributes of the rodent is the damage handler; individual weapons and traps do not modify the health attribute directly. If the damage application algorithm needs to be changed, it only has to be changed in one place. While learning Dev during the inception of the MM project, several seemingly obvious methods were attempted by the team to logically modularize scripts. For one reason or another, they weren’t used—usually because they created more issues than they solved. At first glance, Behavior Graphs seemed to be the perfect method for modularizing complex code into a simple interface. However, there is no means to update a Behavior Graph except by editing it or deleting and replacing with a newer BG (which means re-linking all of the ins/outs). BGs are fantastic for use in common math functions and such, but aren’t practical for use with game logic or any other code that will constantly be changing throughout development. Another method tried early in the project’s production was the message system. It, too, seemed a practical way for sending data to a central script to process an event. However, there are two drawbacks: firstly, message delivery is delayed one frame. This may not seem like much, but when events trigger other events, which trigger other events, it starts to be noticeably delayed (for instance, damage -> death -> death animation, etc). Secondly—and the reason the MM team abandoned the idea—a Send Message BB may only send one message per frame. If a single Send Message BB is executed multiple times in a single frame, the messages are sent with improper values (for example, one BB on a 0-frame delay loop for a landmine explosion with several rodents damaged). The final solution that was implemented in MM is based on arrays. This system is only for handling events—it doesn’t work as the equivalent of the classical programming function, as it provides no way to return a value. However, this method has met the needs of the project and helped out greatly in logically organizing scripts. Arrays are used to store the information for an event. For example, the damage handler in MM uses an array that has columns for which rodent to damage, the amount of damage, the vector position source of damage, and the vector position point of damage (if applicable, for focused damage). When something like a landmine explodes and deals damage, it inserts a row into the array for each rodent damaged. Every frame, the damage handler script iterates over this pending damage array, applies the damage accordingly, then clears the array. This method has several advantages over alternatives for handling events. Although just a matter of convenience, adding schematic for a new event somewhere in a script is painless; Add Row BB automatically configures pIns for a specified array. Compare this behavior to Send Message BB, where pIns must be manually configured (or the BB copied from an already-configured source). Also, the event may be processed in the same frame it is triggered in, with proper priorities on all scripts involved. Finally, the pace at which events are triggered is up to the programmer. For high-priority or low-resource events, the array may be iterated every frame with a 0-delay loop before being cleared. Or, if the events require significant processing, they may be spread out over several frames. MM uses this technique frequently: damaging handling, creating physics concussions, spawning 3D text messages, etc. There are some caveats to be aware of, however. Primarily, the priority of the scripts involved should be set up to properly execute chain-of-event possibilities in a single frame. For instance, if the damage handler can trigger the death handler, which can trigger the score handler, the priorities should be setup to execute in that order. This isn’t a requirement of the method, but makes debugging a little less of a headache if something goes wrong. The example composition "event example.cmo" is a sample setup of a damage handler, very similar to the one used in the MM project. For more information on the technique, browse the commented scripts. |
Muridae |
| Copyright © 2001-2 Virtools SA |