Level Scripting

Introduction

This section describes how level scripts can be implemented. The first things to know is how the game engine and the script interact with each other.

Functions

First, the game engine will call pre-defined functions on particular events. The script then can (but doesn't need to) implement these functions to implement some level-specific logic. Here's a brief overview of the functions:

init()
This function is called exactly once at the beginning of the level. It should be used to set up the objectives describing what has to be done to complete the level.
update(elapsedTime)
This function is called for every frame, passing the elapsed time between the previous and the current frame as argument.
onLanded(p)
This function is called whenever the player's ship lands on a platform, passing the platform object as argument.
onTakeOff(p)
This function is called whenever the player's ship takes off from a platform, passing the platform object as argument.
onCratePickup(crate)
This function is called whenever the player's ship picks up a crate, passing the crate object as argument.
onDestroyed(object)
This function is called whenever an object is destroyed (e.g. a turret).
onSwitch(s)
This function is called whenever a switch is hit by a projectile, passing the switch object as argument.
onObjectiveReached()
This function is called when all objectives of the level is reached. The implementation then can either just finish the level or add new objectives.

gameControl

Additionally, every level script has access to a global variable called gameControl that can be used wherever you want. Actually this variable represents an interface (a collection of methods) that can be called in Java-style syntax. Here are some examples for the most important methods:

gameControl.finish();
Tells the game engine to finish the current level.
gameControl.addObjective(objective);
Adds a new objective to the current level.
var object = gameControl.getObjectById(2);
This is probably the most important method, since it allows to get any level object by a given id . The returned object then can be accessed as it could be in the Java-world allowing you to do quite "everything" with the object, unleashing the full power of level scripting.

Please refer to the JavaDoc of the interfaces in the moagg.model.object package to get an overview of the available interfaces that any game object may implement.

gameControl.setStatusText(text);
Calling this method will set the status bar to the given text. It's mainly useful to inform the player during he plays though the level.
gameControl.setPopUpText(text, ...);
This method is similar to setStatusText() , but the given text will be shown in a pop-up window. Note that you can pass a single String or even multiple String arguments in case you want to display multiple lines of text.

Helper-functions

In addition to the gameControl variable, the file common.js will automatically be included to every level script. It contains different helper-functions to make the life of a script writer a bit easier.

random(max)
Returns a random integer number greater or equal than 0 and less or equal than max .
shuffle(array)
Randomizes the order of the elements in array and returns the shuffled array.
addLandOnPlatformObjective(platformId)
Adds a "land on platform" objective passing the ID of the platform to land on.
addBringCrateToPlatformObjective(crateId, platformId)
Adds a "bring crate to platform" objective passing the ID of the crate and the target platform.
addDestroyTargetObjective(objectId)
Adds a "destroy target" objective passing the ID of the target to be destroyed.
createTimer(delay)
Returns a new GTGE Timer object with the given delay. For usage, please refer to the JavaDoc of this Timer class.

Examples

To fully understand level scripting, you should take a look at the tutorial levels (or levels of the other missions).

tutorial01.xml

The first tutorial level is quite simple. You just have to land the ship on the second platform.

  <gamecontrol>
  <![CDATA[
    function init() {
        addLandOnPlatformObjective(2);
    }

    function onObjectiveReached() {
        gameControl.finish();
    }
  ]]>
  </gamecontrol>

The first thing that comes into view is the CDATA XML-block wrapping the actual script. It's sole purpose is that you don't need to escape special characters within the script.

When looking at the init() function, you can see that a helper-function is called to add a "land on platform" objective to the level. This is one of the helper-functions that is defined in common.js . The argument 2 refers to the id of a platform defined in the playground section of the level.

Finally, the onObjectiveReached() function just calls gameControl.finish() . That means, the level will finish after the ship has landed on the platform with the id 2.

crates.xml

In the next example tutorial, you have to pick up some crates and bring them back to the home platform.

    function init() {
        for (var id = 10; id <= 15; id++) {
            addBringCrateToPlatformObjective(id, 1);
        }
    }

    function onObjectiveReached() {
        gameControl.finish();
    }

The content of the level script is quite similar to the first example. The main difference however is that six "bring crate to platform" objectives with respective crate id s are created in the init() function using a for loop. That means, only after all six crates were brought back to the home platform (with the id 1) the level will finish.

switches.xml

This tutorial demonstrates how to connect switches to magnets and barriers so they are turned on/off when the switches are hit by a projectile.

    function init() {
        for (var id = 10; id <= 13; id++) {
            addBringCrateToPlatformObjective(id, 1);
        }
    }

    function onSwitch(s) {
        switch (s.getID()) {
        case 30:
            gameControl.getObjectById(22).toggle();
            break;
        case 31:
            gameControl.getObjectById(23).toggle();
            break;
        case 32:
            gameControl.getObjectById(22).toggle();
            gameControl.getObjectById(23).toggle();
            gameControl.getObjectById(25).toggle();
            break;
        case 33:
            gameControl.getObjectById(22).toggle();
            gameControl.getObjectById(23).toggle();
            gameControl.getObjectById(26).toggle();
            break;
        case 34:
            gameControl.getObjectById(20).toggle();
            gameControl.getObjectById(22).toggle();
            break;
        case 35:
            gameControl.getObjectById(21).toggle();
            gameControl.getObjectById(23).toggle();
            break;
        }
    }
    
    function onObjectiveReached() {
        gameControl.finish();
    }

Remember that the onSwitch() function is called whenever a switch is hit by a projectile. What then happens in the function body is just a simple switch/case decision block, where dependend on the id of the switch particular objects (magnets and barriers) are toggled. This is done by getting the objects by their id and calling the toggle() method on them, which takes care of changing the state of the object between "activated" and "deactivated".

The most important point to know about level scripting is, that the scripting language allows you to call any methods of any object you retrieved via gameControl.getObjectById() . Regarding best practices, it's recommended to use only methods from the interfaces defined in the moagg.model.object package, because these methods ought to be documented and stable.

turrets.xml

This tutorial demonstrates how to create a new objective after the initial objectives are reached.

    var state = 0;

    function init() {
        addDestroyTargetObjective(10);
        addDestroyTargetObjective(11);
        addDestroyTargetObjective(12);
    }

    function onObjectiveReached() {
        switch (state) {
        case 0:
            addLandOnPlatformObjective(2);
            state = 1;
            break;
        case 1:
            gameControl.finish();
            break;
        }
    }

When playing through this level, the onObjectiveReached() function is called two times: when all three turrets are destroyed and when the ship lands on the target platform. The variable state is used to distinguish these two calls.

Question: Try to think about what would happen if the script looks like this:

    function init() {
        addDestroyTargetObjective(10);
        addDestroyTargetObjective(11);
        addDestroyTargetObjective(12);
        addLandOnPlatformObjective(2);
    }

    function onObjectiveReached() {
        gameControl.finish();
    }

Answer: The level can still be solved by destroying the turrets and landing on the target platform. But the behavior differs if you land on the platform before destroying the turrets. In this case, the level would finish after you destroyed the last turret. Since we want the player to land on the platform after the turrets are destroyed, the objectives must be split up like shown in the original example.

barriers.xml

This tutorial demonstrates one possible way to create objectives one-by-one.

  <gamecontrol>
  <![CDATA[
    var crateIds =      [10, 13, 11, 14, 15, 12];
    var platformIds =   [ 1,  1,  1,  1,  1,  1];
    var deactivateIds = [23, 21, 24, 25, 22, -1];
    var activateIds =   [20, 23, 21, 24, 25, 22];
    var currentObjective = 0;

    function initObjective() {
        addBringCrateToPlatformObjective(
            crateIds[currentObjective], platformIds[currentObjective]);
    }
    
    function init() {
        initObjective();
    }
    
    function onCratePickup() {
        var id = deactivateIds[currentObjective];
        if (id != -1) {
            gameControl.getObjectById(id).deactivate();
        }
    }
    
    function onObjectiveReached() {
        gameControl.getObjectById(activateIds[currentObjective]).activate();
        if (++currentObjective < 6) {
            initObjective();
        } else {
            gameControl.finish();
        }
    }
  ]]>
  </gamecontrol>

Remember that the onCratePickup() function is called whenever a crate is picked up. What happens here is that the laser barrier to the next crate is deactivated. As soon as the crate is brought home, the laser barrier that protected this crate is activated again and the next objective is created until all crates are brought home.

The important points of this script are:

  • You may define any helper-functions you want.
  • You may define any global variables you want to store some kind of state between the different function calls from the game engine.
  • Using arrays might help you organizing all the object id s for a better overview.

Further tutorials worth looking to

taxi.xml
This is a "space taxi" level, where you have to bring crates to particular platforms one by one. The interesting part of the level script is the randomization of the order the crates show up and the selection of the target platform for each crate, which is also random. Further, the script will show pop-up messages during the game flow to notify the player about the target platform of the just picked up crate.
platforms.xml
This is also a "space taxi" level as taxi.xml . But additionally the level script takes care of moving the crate platforms around the playfield. This way, the level becomes much more difficult and challenging.
boss.xml
In this level scripting is used to model a boss out of several tiles and three turrets. When the third turret is destroyed, a timer is used to let the tiles explode in sequence resulting in a nice-looking firework.

Debugging and Logging

There's no easy way to debug a level script similar to debugging Java code. However you can add logging statements to your level script using the global variable log in the same syntax as you would do in Java using the log4j Logger interface.

    function onObjectiveReached() {
        log.trace("onObjectiveReached(): currentObjective = " + currentObjective);
        gameControl.getObjectById(activateIds[currentObjective]).activate();
        if (++currentObjective < 6) {
            log.debug("onObjectiveReached(): initializing next objective");
            initObjective();
        } else {
            log.info("onObjectiveReached(): finishing level");
            gameControl.finish();
        }
    }

The only thing to keep in mind is that the SCRIPTING logger must be configured properly. The log4j.properties file already contains a commented out line for this.