Monday, February 10, 2020

GameEngine design: script-to-code

Somewhere in 2009, I added support for actions triggered by the script-part of my game engine but implemented in C++. This is the cornerstone of "GEDS" game engine. This is how run-time monsters spawning and sound effects work. This is also how level load request are processed.

The design survived the years, so let's see how responsibilities are split.

  • Anything that will start invoking code upon game event (collision, new input, animation completion, etc.) is captured in a *Gun instance. The instance carries all the parameter needed for firing a specific kind of code. E.g. An instance of the LevelGun capture the specific level script to load. An instance of GobGun captures which type of monster to spawn, etc.
  • Configuring the *Gun instance happens at script parsing time. In other terms, most of the *Guns could actually be constant objects.
  • The c++ code instanciating an *Gun must be located from the name of the action on the script (with a using [action-name] ([arguments]) as [holster-slot-number] statement). That requires every type of "gun" to have its associated "factory" class, so that an instance of each "factory" can be registered into an std::map to be the link between actionname and the *Gun constructor.
  • Amount of support code for a new *Gun or a new *GunFactory is minimal. Each class have only one (virtual) method, either create() or shoot().
  • *Gun instance typically forget about what they 'shot' as soon as shooting is done. Often, the created object is registered as an animated item at the game engine, which will take care of running its code periodically. This may lead to the need for an additional "progress" object when there isn't any yet, like with the "TrackSequence" following the instructions (e.g. shooting more guns) as a sound track unrolls in the music player.
  • when processing script expressions, a palette of "guns" (the multi-slot holster, if you want) is received in addition to the set of variables accessible to the script. However, the transitions between states are the one capturing those palettes.
  • The GameScript is responsible for recording every *Gun instance created, so that they can all be reclaimed when the level is destroyed and we switch to something new.
  • There is one guns palette per "state machine file", with the constraint that all the states used by a single "character" in the game have to be described within the same file, this means that a "character" can only use up to 16 different sounds+special effects.
  • The GameScript has the ownership of all these palettes. Reusing guns from parent's palette is explicitly requested. The GameScript knows when to stop using a given palette for new transition and switch to a new/old one because it has seen input/end statements. There is room for improvement here.
Lots of guns. But anyone in GEDS can at most carry 16 of them at a time.
Clearly, the major limit comes from fixed-size palettes of gun, which itself comes from the byte-code nature of the (parsed) script expression where only 4 bits are dedicated to the storage of which-gun-to-use. This is perfectly ok for a Mario-like game, where the distinct number of actions per character remains low. I would definitely need to extend this (and other parts of the game engine like sprite memory management) if I was to create a run-and-gun game where you can pick up lots of different weapons, each requiring a different kind of amno sprite and sound effect.

No comments: