Setup scripts

From Open Surge Wiki
Jump to: navigation, search

Introduction

Setup scripts are scripts that run when levels are loaded. They are used to configure level objects: set the length of a bridge, configure the color of a door, make a on/off switch do different things when pressed, and much more. Different levels may be linked to different setup scripts.

Setup scripts are written in SurgeScript, the scripting language that powers Open Surge. Since setup scripts follow a more or less fixed template, it turns out that you don't need to know much about scripting to modify them. All you need to do is use basic common sense and modify the template that is given. Try different things; see what works and what doesn't.

Hacking setup scripts is an excellent way to get started with SurgeScript: you can do very fun things without too much knowledge initially. Once you're ready, you can proceed to learn SurgeScript, which will give you unlimited power to unleash your full creativity!

Hacking

Setup scripts files are .ss files located in the scripts/setup/ folder. A setup script defines a setup object (see below). Setup objects are used to set up properties and events in your level.

Properties

Entities are level objects like: items, enemies, gimmicks, and so on. Different entities may exhibit different properties: a bridge may have configurable length, a door may have configurable color, a message box may have configurable text, and so on. You can see which properties are supported by which entities by inspecting them in the level editor.

Events

Entities may also trigger events. Events are a way to create cause and effect relationships. Example: an on/off switch may trigger an event when pressed. That event is configurable in the setup script: you may set it to raise the water level, open a door, teleport the player, and so on.

Linking

Since there may be multiple setup scripts, you need to tell the engine which script is associated to which level. You do it by linking the name of the setup object to the setup field in the .lev file. Open the .lev file with a text editor and add the name of the your setup object to the setup field of the level. Example: if the name of your setup object is "Example Setup", you may set the setup field to:

setup "Default Setup" "Example Setup"

The "Default Setup" is a setup object that handles basic things: pause the game, quit the game, display the HUD (heads-up display), and so on. You should keep it in most cases.

For more information on the structure of the .lev files, take a look at the Level specification.

Templates

Complete template

A setup script includes a setup object, usually named (Something) Setup. Inside that object, there's a configuration area in which you can modify things. There's also some additional code at the end. That additional code should be kept as it is in most cases.

The template below is an example setup script. If you copy it, make sure to rename the object from "Example Setup" to something else, like "My Level Setup". You may have a more up-to-date version of that script in your download, but the version below has been placed in this page for clarity. When modifying the template, pay close attention to the syntax: the placement of the commas, brackets, quotes, and so on.

Note: if you don't know much about SurgeScript yet, don't worry! Simply read the examples and look to understand. Remember: you just need basic common sense!

// -----------------------------------------------------------------------------
// File: example_setup.ss
// Description: example setup script
// Author: Alexandre Martins <http://opensurge2d.org>
// License: MIT
// -----------------------------------------------------------------------------
using SurgeEngine.Lang;
using SurgeEngine.Level;
using SurgeEngine.Player;
using SurgeEngine.Vector2;
using SurgeEngine.Audio.Music;
using SurgeEngine.Audio.Sound;
using SurgeEngine.Events.EventList;
using SurgeEngine.Events.EventChain;
using SurgeEngine.Events.EntityEvent;
using SurgeEngine.Events.DelayedEvent;
using SurgeEngine.Events.FunctionEvent;

/*
                      SETUP SCRIPT

The setup script is used to set up entities and data in a level.
Below you'll find a configuration object partitioned into zones.

Each zone is likely to contain multiple entities. Entities have
a name (like "Bridge", "Elevator", and so on) and an ID (a hex
code like "2bbc752e0454d031"). While multiple entities may have
the same name (e.g., there may be multiple bridges on a level),
each entity is uniquely identified by its ID.

Entities may be referenced in the configuration object using
their names or their unique IDs. Once you reference an entity,
you may change its properties following the template below.
The exact properties of an entity can be found by inspecting
it in the level editor.

To associate a setup script with a particular level, open the
.lev file with a text editor and add the name of the setup
object (e.g., "Example Setup") to the setup field. Example:

    setup "Default Setup" "Example Setup"

Happy hacking! ;)

Tip: explore folder scripts/functions/ to know about many
     functions you can use with events.
*/

object "Example Setup"
{
    //
    // This is the configuration area.
    // You'll configure your entities here.
    //
    config = {

        //
        // all zones / acts
        //
        "*": {
            // the configuration below is just an example
            // (modify at will, but backup this file first!)

            // Example: configuring all Bridges. Setting their
            // length and their animation number.
            "Bridge": {
                "length": 12, // length 12 is the default
                "anim": 1     // animation number (the sprite is "Bridge")
            },

            // Example: configuring all Elevators.
            "Elevator": {
                "anim": 2 // animation number
            }
        },

        //
        // zone 1 only
        //
        "1": {
            // Example: configuring the Background Exchanger for zone 1.
            "Background Exchanger": {
                "background": "themes/template.bg"
            },

            // Example: making a specific Door become red.
            // (suppose its ID is 532ab77cef1252ae)
            "532ab77cef1252ae": {
                "color": "red"
            },

            // Example: configuring an event. An event is something that
            // may be triggered during gameplay. This will print a message
            // as soon as the player touches an Event Trigger 1 entity.
            "Event Trigger 1": {
                "onTrigger": FunctionEvent("Print").withArgument("Hello! Give me pizza!")
            },

            // Example: lock the camera as soon as the player touches an
            // Event Trigger 4 entity, ideally placed near the goal flag.
            "Event Trigger 4": {
                "onTrigger": FunctionEvent("Lock Camera").withArgument(2048) // give a maximum space of 2048 pixels to the right
            },

            // Example: collapse a specific Bridge (ID: 481c9ccb42a38268)
            // when activating a specific Switch (ID: 142f0aa6855b991a).
            "142f0aa6855b991a": {
                "onActivate": EntityEvent("481c9ccb42a38268").willCall("collapse")
            },

            // Example: open a specific Door (ID: 2bbc752e0454d031) ONLY IF
            // a specific red Switch (ID: 66b9b90da2a5a5fa) is being pressed.
            "66b9b90da2a5a5fa": {
                "color": "red",
                "sticky": false,
                "onActivate": EntityEvent("2bbc752e0454d031").willCall("open"),
                "onDeactivate": EntityEvent("2bbc752e0454d031").willCall("close")
            }
        },

        //
        // zone 2 only
        //
        "2": {
            // Example: make all Switches blue.
            // Make them trigger multiple events at the same time!
            "Switch": {
                "color": "blue",
                "onActivate": EventList([
                    FunctionEvent("Print").withArgument("Oh my! Look at the water!"),
                    FunctionEvent("Change Water Level").withArgument(128) // raise water to ypos = 128
                ])
            },

            // Example: tell a story. Trigger one event at a time!
            "Event Trigger 2": {
                "onTrigger": EventList([
                    FunctionEvent("Print").withArgument("Oh my! Something is about to happen!"),
                    DelayedEvent(
                        FunctionEvent("Print").withArgument("I can feel... water...")
                    ).willWait(3.0), // wait 3 seconds before triggering this
                    DelayedEvent(
                        FunctionEvent("Print").withArgument("WATCH OUT!")
                    ).willWait(6.0), // wait 6 seconds before triggering this
                    DelayedEvent(
                        FunctionEvent("Change Water Level").withArgument(128)
                    ).willWait(7.0) // wait 7 seconds before triggering this
                ])
            },

            // Example: trigger different events when pressing a
            // Switch multiple times. The events will be triggered
            // sequentially, like the links of a chain.
            "e58aecd5740a0547": {
                "sticky": false,
                "onActivate": EventChain([
                    FunctionEvent("Print").withArgument("First time you pressed it"),
                    FunctionEvent("Print").withArgument("Second time you pressed it"),
                    FunctionEvent("Print").withArgument("Third time you pressed it"),
                    FunctionEvent("Print").withArgument("Don't you get tired?"),
                    FunctionEvent("Print").withArgument("Enough!")
                ])
            },

            // Example: trigger alternating events. Raise the water
            // level and then restore it back to its original level,
            // again and again, whenever the blue Switch is pressed.
            "ab5264eabc2564cc": {
                "color": "blue",
                "sticky": false,
                "onActivate": EventChain([
                    FunctionEvent("Change Water Level").withArgument(128),
                    FunctionEvent("Change Water Level").withArgument(Level.waterlevel)
                ]).willLoop()
            }
        },

        //
        // zone 3 only
        //
        "3": {
            // Example: start a boss fight!
            "Event Trigger 4": {
                "onTrigger": EventList([
                    FunctionEvent("Play Boss Music"),
                    FunctionEvent("Lock Camera").withArgument(800) // lock camera with a space of 800 pixels to the right
                ])
            },

            // Example: extend the locked region after a boss fight.
            // "My Boss" is a hypothetical entity that triggers onDefeat.
            "My Boss": {
                "onDefeat": EventList([
                    FunctionEvent("Stop Boss Music"),
                    FunctionEvent("Lock Camera").withArgument(512) // give more 512 pixels of space
                ])
            },

            // Example: unlock the camera after a mini-boss fight,
            // so the player can keep moving throughout the level.
            "My Mini Boss": {
                "onDefeat": EventList([
                    FunctionEvent("Stop Boss Music"),
                    FunctionEvent("Unlock Camera")
                ])
            },

            // Example: when touching Event Trigger 3, give the player
            // a bonus of 50 collectibles to prepare for the boss fight!
            "Event Trigger 3": {
                "onTrigger": FunctionEvent("Give Lucky Bonus").withArgument(50)
            }
        }

    };

    // -------------------------------------------------------------------------

    //
    // Below you'll find the setup code.
    // You should keep it as it is in most cases.
    //
    state "main"
    {
    }

    // setup the entities
    fun constructor()
    {
        zone = String(Level.act);
        Level.setup(config["*"] || { });
        Level.setup(config[zone] || { });
    }
}

Short template

The code below shows a simplified setup object, without all the comments and explanations. You may use it to create a new setup object with a fresh start.

//
// Setup script
//
using SurgeEngine.Lang;
using SurgeEngine.Level;
using SurgeEngine.Player;
using SurgeEngine.Vector2;
using SurgeEngine.Audio.Music;
using SurgeEngine.Audio.Sound;
using SurgeEngine.Events.EventList;
using SurgeEngine.Events.EventChain;
using SurgeEngine.Events.EntityEvent;
using SurgeEngine.Events.DelayedEvent;
using SurgeEngine.Events.FunctionEvent;

object "Simple Setup"
{
    //
    // This is the configuration area.
    // You'll configure your entities here.
    //
    config = {

        //
        // all zones / acts
        //
        "*": {
            // configuring the Elevators
            "Elevator": {
                "anim": 2
            }
        },

        //
        // zone 1 only
        //
        "1": {
            // give the player 50 collectibles when pressing any Switch
            "Switch": {
                "onActivate": FunctionEvent("Give Lucky Bonus").withArgument(50)
            }
        },

        //
        // zone 2 only
        //
        "2": {
        },

        //
        // zone 3 only
        //
        "3": {
        }

    };

    // -------------------------------------------------------------------------

    //
    // Below you'll find the setup code.
    // You should keep it as it is in most cases.
    //
    state "main"
    {
    }

    // setup the entities
    fun constructor()
    {
        zone = String(Level.act);
        Level.setup(config["*"] || { });
        Level.setup(config[zone] || { });
    }
}

Using Events

Function Events

Folder scripts/functions/ offers plenty of choices you can use with events. One such choice is called "Hit Player". In the example below, we'll make all Switch objects trigger "Hit Player" when pressed (all names are case sensitive):

// the active player will get hit when any Switch is activated
"Switch": {
    "onActivate": FunctionEvent("Hit Player")
}

You are highly encouraged to explore the scripts/functions/ folder to get a glimpse of the many possibilities can you use with events. Each possibility is represented by a .ss file, which you can open with a text editor (open to see how to use). In SurgeScript, we call those possibilities function objects: they run some code when called. A FunctionEvent is used to call a function object.

Even though there are many function objects readily available for you to play with, if you learn SurgeScript you can create new possibilities of your own, and thus broaden your horizons even further!

Combining Events

Events can be combined in many ways to create highly interactive experiences. There are special types of events that trigger other events. One such special event is called EventList: it will trigger a list of events. In the example below, we'll make all Switch objects trigger both "Hit Player" and Print" when pressed:

// multiple events will be triggered when pressing any Switch
"Switch": {
    "onActivate": EventList([
        FunctionEvent("Hit Player"),
        FunctionEvent("Print").withArgument("OUCH!") // this will display message OUCH!
    ])
}

Other special events include: EventChain and DelayedEvent - they have been shown in the complete example of this page.