Case study 3: Lady Bugsy

From Open Surge Wiki
Jump to: navigation, search

Introduction

In this tutorial, we'll develop Lady Bugsy, an enemy with advanced behavior. As this is an advanced tutorial, we assume that the reader has already got some familiarity with the basic commands of the scripting engine. Lady Bugsy makes heavy use of the variables system. Although it's possible to create an equivalent version of this enemy without it, the variables greatly simplify the work, reducing the number of required states.

We highly recommend that the reader opens the API Reference in a new window before proceeding.

We can model Lady Bugsy as a state machine by considering the following behavior:

  • It will walk around for a while. For how long? Define it as $RELOAD_TIMER seconds.
  • After that, it will prepare to shoot a bullet. Lady Bugsy will load its weapon, and this operation takes $VOLLEY_GAP seconds.
  • Once the weapon gets loaded, Lady Bugsy will shoot a bullet.
  • After shooting a bullet, Lady Bugsy needs to reload its weapon ($VOLLEY_GAP) in order to shoot again.
  • Lady Bugsy can shoot up to $SHOTS_PER_ROUND bullets consecutively. After that, it will walk around for a while again, and everything starts over.

Of course, Lady Bugsy is affected by gravity. The bullet damages the player.

As a curiosity, Lady Bugsy had been conceived and programmed directly into the engine by Celdecea (a community member) much before the scripting engine was created. Adding features to the engine requires programming expertise. The scripting system was developed to enable users to create their own content in a much easier way. Nowadays there are no built-in enemies in the engine - they are all made via scripting.

Lady Bugsy already comes with Open Surge, so you don't need to add it yourself.

Making the sprites

We create two sprites: SD_LADYBUGSY and SD_SLIMEBULLET. The .png and the .spr files are below:

Lady bugsy.png

Note that Lady Bugsy appears in two directions: left and right. That's different of a simple horizontal flip in the image because of the shine. This means we won't be able to use a decorator like look_at_walking_direction as we did in Case Study 1. We'll need to control the direction via scripting.

// Enemy: Lady Bugsy
sprite SD_LADYBUGSY
{
    source_file     images/lady_bugsy.png
    source_rect     0 0 312 90
    frame_size      52 30
    hot_spot        26 29

    // idle (left by default)
    animation 0
    {
        repeat      TRUE
        fps         4
        data        0
    }

    // rolling left
    animation 1
    {
        repeat      TRUE
        fps         4
        data        0 1 2 3
    }

    // rolling right
    animation 2
    {
        repeat      TRUE
        fps         4
        data        9 10 11 12
    }

    // shooting left
    animation 3
    {
        repeat      FALSE
        fps         20
        data        0 4 5 6 7 8 0
    }

    // shooting right
    animation 4
    {
        repeat      FALSE
        fps         20
        data        9 13 14 15 16 17 9
    }
}

// Slime Bullet
sprite SD_SLIMEBULLET
{
    source_file     images/lady_bugsy.png
    source_rect     0 100 96 7
    frame_size      16 7
    hot_spot        3 3

    // bullet - left
    animation 0
    {
        repeat      TRUE
        fps         4
        data        0
    }

    // bullet - right
    animation 1
    {
        repeat      FALSE
        fps         4
        data        3
    }

    // slimy bullet - left
    animation 2
    {
        repeat      TRUE
        fps         20
        data        1 2
    }

    // slimy bullet - right
    animation 3
    {
        repeat      TRUE
        fps         20
        data        4 5
    }
}

Scripting: making the Slime Bullet

Lady Bugsy shoots a bullet, and a bullet is an object. As it is disposable, we'll set the property destroy_if_far_from_play_area. We'll also make it as a hidden object - that means, its name will start with a "." - because we don't want that to be available in the level editor.

The code should be self-explanatory. The bullet damages the player. It starts with a blank animation and after a while, it expands to a slime animation. Once the bullet gets created, there will be two global variables $_bullet_sx and $_bullet_sy that will provide the speed, in pixels per second, that it should assume. These variables are published by Lady Bugsy immediately before it spawns this bullet.

Please open the API Reference in a new window if you haven't done it already.

The following goes into the .obj file:

object ".Lady Bugsy - Slime Bullet"
{
    requires 0.2.0
    destroy_if_far_from_play_area

    state "main"
    {
        // my constants
        let "$ANIM_BLANK_LEFT = 0"
        let "$ANIM_BLANK_RIGHT = 1"
        let "$ANIM_LEFT = 2"
        let "$ANIM_RIGHT = 3"
        let "$LEFT = 0"
        let "$RIGHT = 1"
        let "$SLIME_DELAY = 0.45"

        // initializing...
        let "$sx = $_bullet_sx" // bullet horizontal speed
        let "$sy = $_bullet_sy" // bullet vertical speed
        let "$direction = cond($sx <= 0, $LEFT, $RIGHT)" // direction

        // ready... go!
        change_state blank
    }

    state "blank"
    {
        set_animation "SD_SLIMEBULLET" "cond($direction==$LEFT, $ANIM_BLANK_LEFT, $ANIM_BLANK_RIGHT)"
        move $sx $sy
        on_brick_collision dead
        on_player_collision hit

        on_timeout $SLIME_DELAY slime
    }

    state "slime"
    {
        set_animation "SD_SLIMEBULLET" "cond($direction==$LEFT, $ANIM_LEFT, $ANIM_RIGHT)"
        move $sx $sy
        on_brick_collision dead
        on_player_collision hit

        set_zindex 0.6
    }

    state "hit"
    {
        hit_player
        change_state dead
    }

    state "dead"
    {
        destroy
    }
}

Scripting: Lady Bugsy

You already know how Lady Bugsy behaves. The API Reference tells you what commands do what. Now, let's jump to the code! The following goes into the same .obj file:

object "Lady Bugsy"
{
    requires 0.2.0

    state "main"
    {
        // my constants
        let "$ANIM_IDLE = 0"
        let "$ANIM_MOVING_LEFT = 1"
        let "$ANIM_MOVING_RIGHT = 2"
        let "$ANIM_SHOOTING_LEFT = 3"
        let "$ANIM_SHOOTING_RIGHT = 4"
        let "$LEFT = 0"
        let "$RIGHT = 1"
        let "$SHOTS_PER_ROUND = 3"
        let "$RELOAD_TIME = 5.0"
        let "$VOLLEY_GAP = 1.0"
        let "$WALK_SPEED = 50"
        let "$SCORE = 100"

        // internal data
        let "$direction = $LEFT" // current direction
        let "$shots_fired = 0" // number of shots fired
        let "$oldx = xpos()" // previous x position

        // ready... go!
        change_state go
    }

    state "go"
    {
        // default behavior
        set_animation "SD_LADYBUGSY" "cond($direction==$LEFT, $ANIM_MOVING_LEFT, $ANIM_MOVING_RIGHT)"
        walk $WALK_SPEED
        enemy $SCORE
        gravity

        // detect direction
        let "$direction = cond(xpos() == $oldx, $direction, cond(xpos() > $oldx, $RIGHT, $LEFT))"
        let "$oldx = xpos()"

        // enemy weapon is loaded
        on_timeout $RELOAD_TIME weapon_is_loaded
    }

    state "weapon_is_loaded"
    {
        // default behavior
        enemy $SCORE
        gravity

        // detect direction
        let "$direction = cond(xpos() == $oldx, $direction, cond(xpos() > $oldx, $RIGHT, $LEFT))"
        let "$oldx = xpos()"

        // ready to fire!
        on_animation_finished ready_to_fire
        on_timeout $VOLLEY_GAP ready_to_fire

        // finished shooting
        if "$shots_fired >= $SHOTS_PER_ROUND" finished_shooting        
    }

    state "finished_shooting"
    {
        let "$shots_fired = 0"
        change_state go
    }

    state "ready_to_fire"
    {
        // creating the bullet
        let "$_bullet_sx = 200 * cond($direction == $LEFT, 0-1, 1)" // bullet horizontal speed
        let "$_bullet_sy = 0" // bullet vertical speed
        create_child ".Lady Bugsy - Slime Bullet" 0 -15
        //play_sample "samples/shot.wav" // not mandatory

        // will shoot again later
        let "$shots_fired += 1"
        set_animation SD_LADYBUGSY "cond($direction == $LEFT, $ANIM_SHOOTING_LEFT, $ANIM_SHOOTING_RIGHT)"
        set_animation_frame 0
        change_state weapon_is_loaded
    }
}

Conclusion

This advanced tutorial shows how you can create lots of complex behaviors using the Open Surge Scripting Engine. Mixing states and variables gives the designer a lot of power. If you're excited, we tell you: this is just the tip of the iceberg! With some creativity you can create lots of interesting objects: from T-bar zipline objects to powerful bosses (see case study 4 for more details) and much more! A simple Artificial Intelligence agent can also be created with ease.

We encourage our users: go for it! Study the engine! Make your own experiments! And, most importantly: have fun!!! ;)

Charge vs Lady Bugsy.png