Categories

Category Tutorials

Tutorial #2 – Tilox

Tilox

Okay, now we know how to make a basic game using Bloxley. But, the game that we made… isn’t very impressive looking. Our workers are animated, but they just slide from place to place, with the same static image. That’s not cool. So for our second tutorial, we’re going to work on polish.

Since our focus is on improving the animations, we’re going to implement a pretty simple game. Simple, but still interesting. I’ve seen it called “Hock”, and “Tilox”, and has at least two implementations on the web: Tilox by Lightforce Games, and Hock by Lutz Tautenhahn. It seems Hock is the original name, but I first saw it as Tilox, so that’s what we’ll call it here.

In Tilox, you have player characters that can either (a) move from one square to an adjacent square, or (b) jump over an adjacent square to land on the next square over. When a character leaves a square, that square disappears. When there’s only the square you’re standing on left, you win–but if you jump off into nothingness, you lose.

You can see our implementation below. The arrow keys will move the character, and holding down Shift while hitting the arrow keys will cause the character to jump. Like with Sokoban, Backspace will undo and Shift+Backspace will reset the level.

Let’s break this tutorial up into three parts:

  1. Setting up the game structure.
  2. Implementing the game.
  3. Improving the graphics.

Implementing Tilox

Step 1: Setting up the game structure

We’ll start by creating a folder for this project, and inside create another folders called tilox. Inside the flash folder, create a new flash file called Tilox.fla. You can download the graphics we’ll use here: Tilox Graphics. In the final result above, I set the flash dimensions to 256px x 256px, and the frame rate up to 30fps, but neither are strictly necessary.

Make sure the Classpath for this project contains $LOCALDATA and the directory that contains Bloxley.

So now we need to figure out what classes we’re going to create. Put all of these files in the tilox directory. First, we know we’ll need a Game controller, so in the tilox directory create TiloxGame.as and insert the following code:

package tilox {

    import flash.display.Stage;

    import bloxley.controller.game.*;

    public class TiloxGame extends BXGame {

        public function TiloxGame(stage:Stage) {
            super(stage);

            controllers({ Patch: TiloxPatchController, Play: TiloxPlayController, Player: TiloxPlayerController });
        }

    }

}

We know we’ll need a patch controller, so create TiloxPatchController.as and insert the following code:

package tilox {

    import bloxley.model.game.*;
    import bloxley.controller.game.*;
    import bloxley.controller.event.*;
    import bloxley.view.sprite.*;
    import bloxley.view.animation.BXFreeAnimation;

    public class TiloxPatchController extends BXPatchController {

        public function TiloxPatchController(name:String, game:BXGame) {
            super(name, game);

            tiles({ Floor: ".@", Pit: "#" });
        }

    }

}

We’ll also need an actor controller–only one in this case, since there’s only one kind of object moving around. Create TiloxPlayerController.as and insert the following code:

package tilox {

    import bloxley.model.game.BXActor;
    import bloxley.controller.game.BXActorController;
    import bloxley.controller.event.*;
    import bloxley.view.sprite.*;

    public class TiloxPlayerController extends BXActorController {

        public function TiloxPlayerController(name, game) {
            super(name, game);

            setBoardString("@");
        }

        override public function key(options = null):String {
            return "Player";
        }

        override public function canBePlayer(actor:BXActor):Boolean {
            return true;
        }

    }

}

And finally we’ll need a play controller. Create TiloxPlayController.as and insert the following code:

package tilox {

    import bloxley.controller.game.*;
    import bloxley.controller.pen.*;
    import bloxley.controller.event.BXAction;
    import bloxley.base.BXSystem;
    import bloxley.view.gui.BXImage;

    public class TiloxPlayController extends BXPlayController {

        public function TiloxPlayController(name: String, game:BXGame) {
            super(name, game);
        }

    }
}

To go along with this, place the following code into the Frame Actions in tilox.fla:

import tilox.*;

var game = new TiloxGame(stage);

game.loadLevel([
    ".#######",
    "....####",
    ".###.@.#",
    ".#######",
    ".#...###",
    "###.####",
    "....####",
    "########",
]);

game.setCurrentGameController("Play");

And that should be enough to get us started. If you run the flash file, you should get something that looks like this:

First pass at Tilox

If it doesn’t, then run through the steps again, making sure you didn’t miss anything. Setting the classpath for both the actionscript code, and the bloxley library, is especially important, and easy to forget.

When running the game, you’ll notice that it’s similar to Sokoban before we implemented any behavior. You can move the player around, and he’ll animate its movements, but it will treat all patches the same.

But it’s a start.

Step 2: Implementing Tilox

So in this step, there’s really four things we need to implement. However, they’ll take us into parts of Bloxley that we haven’t discussed yet.

  1. Floor patches break when you step off of them.
  2. Jumping.
  3. What happens if you end up in a pit.
  4. Win and Lose conditions.

Let’s start with breaking floor tiles. As you can see in TiloxPatchController.as, we defined two kinds of patches–floors, which players can stand on, and pits, which players can’t. So we want to change the code so that when a player exists a floor, it becomes a pit.

Luckily, there’s an event for that. Similar to the enter event we used in Sokoban, there’s an exit event that gets called when an actor moves off of a patch. Specifically, if an actor with key ActorKey exits a patch with key PatchKey, the first method in the following list that is defined gets called on the patch controller:

  1. can<ActorKey>Exit<PatchKey>()
  2. canExit<PatchKey>()
  3. can<ActorKey>Exit()
  4. canExit() — this method is defined in the base class BXPatchController, so if it gets to this level, this method is always called (and does allows the event to succeed).

This technique–called Method Cascading–allows us to define generic methods for common behavior, and override that with custom behavior defined in specific methods. Bloxley frequently uses it to allow flexibility in customizing behavior. In Sokoban, we used it to take canWorkerEnterWall(), a method that prevented workers from stepping on walls, and made it more generic by changing the name to canEnterWall(), so that blocks couldn’t be pushed onto walls, either.

In Tilox’s case, we don’t really need to worry about the actor key, since we have only one kind of actor. So simply defining canExitFloor() will allow us to define the behavior we want. Like with the enter actions, these event methods will all be given three arguments: the action that caused the event, the source actor, and the target patch. So inside the TiloxPatchController class, define the following method:

public function canExitFloor(action:BXMoveAction, source:BXActor, target:BXPatch) {
    action.causes( new BXPatchChangeAction(target, "Pit") );
}

BXPatchChange, not too surprisingly, is an action that changes a patch from one type to another. If you re-run the code now, you’ll see that when the player steps off of a floor, it disappears. Not very exciting, but we can change that later. Nothing happens when the player steps off of a pit, which it shouldn’t yet. So far, so good.

Next we want to handle jumping. When you hit one of the arrow keys, the player moves one step in whatever direction was hit. What we’d like is that, when you hold down Shift and hit an arrow key, the player jumps two steps in that direction. To implement that, we first need to look into what happens when you hit the arrow key.

Pens

In any Bloxley game, we have the User who is playing the game, and the Controller which handles the game logic. However, the user communicates with the game through button presses and mouse movements–and the controller communicates through moving the actors, and changing the patches. So there needs to be some intermediary that can speak to both users and controllers.

Pen Diagram

Bloxley calls this intermediary a Pen. Pens take in user interactions like key presses, and tell the controller what that key press means. All pens are a subclass of BXPen, although there’s a special subclass called BXPlayPen designed for pens used in gameplay. The only behavior that BXPen directly implements is Delete for undo, and Shift+Delete for resetting the level. BXPlayPen, however, also includes using the arrow keys to move the currently selected actor, Space to change the currently selected actor, and some mouse control as well.

In Tilox, we want to change what the arrow keys do. So we’ll create our own special subclass of BXPlayPen, called TiloxPlayPen. In the tilox directory, create the file TiloxPlayPen.as and insert the following code:

package tilox {

    import bloxley.controller.game.BXPlayController;
    import bloxley.controller.pen.BXPlayPen;
    import bloxley.model.data.BXDirection;

    public class TiloxPlayPen extends BXPlayPen {

        public function TiloxPlayPen(controller:BXPlayController) {
            super(controller);
        }

        override public function arrow(direction:BXDirection, shift:Boolean, alt:Boolean, ctrl:Boolean) {
            controller.respondTo("moveCharacter", [ direction, shift ? 2 : 1 ]);
        }

    }
}

As you can see, there’s a method called arrow() which gets called when an arrow key is hit, and the direction of the arrow key is passed in, as well as the state of the modifier keys (Shift, Alt, and Control).

What we’re doing when the arrow key gets hit is calling moveCharacter() on the controller, which will be our TloxPlayController. Rather than calling it directly, we’re using the indirect “respondTo” method, because that (a) allows hooks around when moveCharacter() gets called, and (b) allows the controller to easily pass the call onto another object, in case the controller doesn’t handle moveCharacter() directly. (In this case it does, but we still should always have the pens speak to the controller through respondTo()).

The second argument to respondTo() is the array of parameters, so when moveCharacter() gets called, the direction and the number of steps will get passed in (2 if shift is being held down, 1 otherwise). moveCharacter() knows how to handle both of those arguments, so we don’t need to redefine it–but we do need to tell the play controller about our new pen.

So open up tilox/TiloxPlayController.as and insert the following method:

override public function createPens() {
    var pen = new TiloxPlayPen(this);
    pen.setName("Play");

    var pen1 = new BXGameOverPen(this);
    pen1.setName("GameOver");
}

createPens() is just a convenient place to define the creation of pens. As you can see, we’re creating two pens: one is our new TiloxPlayPen, and the other is a BXGameOverPen–that handles the interaction when the game has been won (or lost). We won’t talk too much about it this time.

Now if you run Tilox, you’ll be able to make the player jump around by holding down Shift. The only visual difference will be that the cell jumped over won’t turn into a pit.

Next thing we want to tackle is falling into a pit. In Sokoban you can beat a level, but not lose. In Tilox, we want to be able to lose as well as win. So, we want to make the player “go away” when they jump into a pit. In Bloxley, this is called disabling the actor. It makes the actor’s sprite disappear, and they can’t be selected as the active actor any more. (In Tilox, this is a bad thing–but in other games, disabling an actor is useful for when they exit the level).

So, open up TiloxPatchController, and insert the following method:

public function canEnterPit(action:BXMoveAction, source:BXActor, target:BXPatch) {
    action.causes( new BXDisableAction(source) );
}

This is pretty easy to understand–it gets called when an actor enters a Pit patch, and causes the actor to become disabled. If you run the flash app, and jump off into nothingness, you’ll see the actor fade away. Now we just need to hook that into ending the game.

As we discussed in the last tutorial, Bloxley’s default game flow includes a pair of methods didBeatLevel() and didLoseLevel() where you can determine if the level was beaten or not. Bloxley checks to see if a level was lost, before it checks whether a level was beaten–so if both conditions are reached at the same time, the level will be lost. (I’m mentioning this because it’s relevant for Tilox.)

So, first: winning. In standard Tilox, you win when there is only one floor patch left–the patch you’re standing on. In the TiloxPlayController class, insert the following method:

override public function didBeatLevel():Boolean {
    return board().allPatches().ofType("Floor").areExactly(1);
}        

I think this method is pretty readable. It takes the board, looks at all of the patches, gets only the patches that are Floors, and counts how many of those there are. If there is exactly 1, then the level has been beaten.

Next, losing. The user loses a game of Tilox if the player has jumped into a pit and been disabled. In the TiloxPlayController class, insert the method:

override public function didLoseLevel():Boolean {
    return board().allActors().theFirst().isDisabled();
}

This method looks at the first (and only) actor on the board, and returns true if it has been disabled.

Since we want to be able to display when the game is won and lost, also stick the following method into TiloxPlayController:

override public function createInterface() {
    super.createInterface();

    var screen = BXSystem.screenDimensions();

    setBank("Beat Level");
        var image = new BXImage(this, "BeatLevel", { centered: true, depth: 1 });
        image.goto([ screen[0] * 0.5, screen[1] * 0.5 ]);
        register( image );

    setBank("Lost Level");
        var image2 = new BXImage(this, "LostLevel", { centered: true, depth: 2 });
        image2.goto([ screen[0] * 0.5, screen[1] * 0.5 ]);
        register( image2 );
}

Make sure that the linkage class for your winning banner is set to game.BeatLevel, and the linkage class for your losing banner is set to game.LostLevel. It’s already set in the provided graphics, but if you decide to change them, you’ll have to make sure that you do this.

Once again, run Tilox. Now when you beat the level, you’ll get a nice message saying so:

Beat the Level

And when you lose the level, you’ll get a message saying you lost:

Lost the Level

Step 3: Improving the Graphics

So now we’re about where we were at the end of the Sokoban tutorial–we have a playable game with correct end game conditions, but it’s not very good looking. So let’s set about improving that. I can think of four places where the game could use some polish:

  1. The player sprite should be a little fancier than just a static image.
  2. When the player moves around, jumping instead of sliding would look good
  3. And while we’re at it, making it face the direction of motion would be good, too.
  4. Having the floor disappear instead of just changing.

So let’s get started.

First, we want the player sprite to look better. That means not just having a single static image; let’s put a slight shadow under the sprite. That’ll definitely look good when we have the actor jump. Open up tilox/BXTiloxPlayerController.as, and insert the following methods:

override public function initializeSprite(actor:BXActor, sprite:BXSprite) {
    var comp:BXCompositeSprite = sprite as BXCompositeSprite;

    comp.layer(0).goto([0, 0]);

    comp.addSpriteLayer("Shadow", { depth: 1 });
    comp.swapLayers(0, 1);
}

So, here we’re defining initializeSprite(). This method gets called when an actor’s sprite is first created. It allows you to set up the sprite in whatever way you want it. In our case, we’re using it to add another image (a shadow) into the sprite, and then calling swapLayers() to place it below the main sprite image (which defaults to depth 0). Run Tilox and take a look; it’s subtle but you can see it when you compare this screenshot with the last:

Player with shadow

Now we want to make the player jump around a bit. In the same class, insert the following methods:

override public function defaultSpeed():Number {
    return 5.0;
}

override public function animateMove(actor:BXActor, action:BXMoveAction) {
    var sprite = spriteForActor(actor);
    var body = sprite.layer(1);

    return [
        sprite.goto(action.newPosition, { speed: defaultSpeed() }),
        body.shift([0, -8.0 * action.steps()], { seconds: action.steps() / defaultSpeed(), blend: "bounce" })
    ];
}

Now what are we doing here? The defaultSpeed() method is just a convenient way to change the animation speed. Fine. But what is this animateMove() method?

Well, to explain, let’s talk about actions and animations for a second. As we said in the first tutorial, every game state change should be performed by an action. For moving actors around, that action, as we’ve seen, is BXMoveAction. However, every action also needs to provide an animation–that way the animations for each action can be properly placed in time sequence.

However, we don’t want to have to subclass BXMoveAction every time we want to change its animation; that seems excessive. And it gets even worse when different actors get animated differently. Instead, we’ll notice that while every action need to provide its animation, it doesn’t need to produce it. So, most actions just pass the request off to a controller–actor, patch, or game–since that will be subclassed anyway, and it is the kind of thing that the control layer should do. So, BXMoveAction calls animateMove() on the controller of the actor that is moving, and that method generates the animations necessary.

In our case, we’re returning an array of two separate animations. When this action gets animated, both of the animations will occur at the same time. What are our two animations?

sprite.goto(action.newPosition, { speed: defaultSpeed() }): This is the usual default move animation. It moves the sprite from its current location, to its new location, at a speed of defaultSpeed() (in patch lengths per second).

body.shift([0, -8.0 * action.steps()], { seconds: action.steps() / defaultSpeed(), blend: "bounce" }): This is more interesting.

  • First of all, it just animates the yellow body of the sprite, and not the entire thing.
  • Secondly, shift() moves the sprite, but as an adjustment to its real location. The sprite remembers where it really is, but displays itself at a slightly shifted location. The adjustment we have to provide in pixels rather than patch lengths because it is a component of the sprite, rather that the entire sprite. (This is a limitation of Bloxley that will get fixed).
  • Passing in a seconds option allows us to say how long the animation should take.
  • And finally, blend: "bounce" tells Bloxley that, rather than adjusting the shift from the starting value (0) to the final value (-8 or -16), it should start at 0, go up to that final value, and then back down to 0. So you can kinda picture how this is a jump.

Now if you run Tilox, you’ll see how the player jumps around the screen instead of sliding. Already, a lot more interesting! Notice how the shadow stays on the ground while the player’s body jumps:

Player jumping

Now, we want to have the player face the direction it moves in. We could get really complicated with this, but I think just moving the eyes will be pretty effective. If you look at the frames of the ‘Player’ movie clip, you’ll see that there are frames named “North”, “South”, “East”, and “West”. We’ll use the “West” frame to show when the player is moving “West”, etc.

Luckily, since we’re already defining our own move animation, we can just modify it to change the sprite’s frame when we move. In the array of animations that animateMove() returns, insert the following line:

body.frame(action.direction().toString(), { wait: true })

As you can see, we added another animation to the array; this one is a call to frame(), which animates changing frames. Normally, instantaneous animations (ones with no speed or seconds option) are performed as soon as they are created. Mostly, that’s what you want, but not in animation methods. The wait: true option we pass in tells Bloxley to hold off on it, it will be started by some other object. Leaving this out will cause strange graphical glitches which can be tricky to track down.

So, run Tilox again, and you’ll see the player sprite looking where it is going. Excellent!

Look where you're going!

Our final step is having the floor disappear. Phew!

Rather than having the floor simply disappear, we’ll instead have it (a) shrink down to a dot, and (b) fade away to nothingness. To accomplish that, we first need to have the two pieces of the floor (the black background, and the blue tile) be controlled by Bloxley, rather than Flash. So open up TiloxPatchController and put these methods inside:

override public function frameName(patch:BXPatch):String {
    return "Pit";
}

override public function initializeSprite(patch:BXPatch, sprite:BXSprite) {
    if (patch.isA("Floor")) {
        var comp:BXCompositeSprite = sprite as BXCompositeSprite;

        var floor = comp.addSpriteLayer("WeakFloor", { depth: 1, centered: true });
        floor.goto([ 16.0, 16.0 ]);
    }
}

The first method, frameName(), tells Bloxley to always use the “Pit” frame. That gets the “Floor” frame’s floor tile out of the way. Next, initializeSprite() is used identically to initializeSprite() in TiloxPlayerController–to set up what the sprite will look like. In this case, we’re adding a “WeakFloor” image to the center of the patch sprite. To make it easier to resize, centered: true tells Bloxley that its registration point is in the center of the image.

Now if you run Tilox, it will look the same as before, but the floor tiles will no longer disappear when you walk over them. This is because they’re all already on the “Pit” frame, so the default animation doesn’t change anything. We’ll have to fix that. Add the following method to TiloxPatchController:

override public function animatePatchChange(patch:BXPatch, action:BXPatchChangeAction) {
    var layer = (spriteForPatch(patch) as BXCompositeSprite).layer(1);

    return [
        layer.hide({ seconds: 0.5, blend: "snap" }),
        layer.resize([0.0, 0.0], { seconds: 0.5, blend: "accel" })
    ];
}

Having defined animateMove() for the player controller, we’re in a much better position to understand what we’re doing here. Following the code, we’re taking the layer at depth 1 (which we created in initializeSprite() to be the floor tile), and simultaneously (a) hiding it, and (b) resizing it to nothing. We’re using two new blends here–”snap” starts changing quickly and slows down, and “accel” starts slowly and speeds up.

Run Tilox one more time, and you’ll see it working! Excellent, it looks much nicer than before.

Floor falling away

However, you’ll soon find one more problem: undo. Yes, when you undo a step, the floor tile won’t reappear. That’s because we’re still using the default undo animation. We need to replace that. Insert the following method into TiloxPatchController:

override public function animateUndoPatchChange(patch:BXPatch, action:BXPatchChangeAction) {
    var layer = (spriteForPatch(patch) as BXCompositeSprite).layer(1);

    return [
        layer.show(),
        layer.resize([28.0, 28.0])
    ];
}

It’s pretty clear that this is a reverse of what we’re doing in animatePatchChange(), but you’ll notice that we’re no longer specifying a seconds option. Like we discussed above, when we don’t specify speed, seconds, or wait, the animation takes place instantly–which is fine for undo animations.

So, run Tilox one last time, and you should see everything working together. If you want, you can make any of the animations even more complicated and interesting–hopefully this tutorial showed you how it’s done!

Next Time: Threesome

In our next tutorial, we’ll develop a game called Threesome. Rather than focusing on implementing the gameplay, we’ll talk about how to write a level editor, as well as implement saving and loading from a server.

Bloxley Tutorial #1 — Sokoban

Bloxley

Bloxley is a game framework designed for creating 2D, grid-based puzzle games like Sokoban, Tetris, and Bejewelled. By allowing you to describe the game at a high level, you can focus on what makes your game unique. Bloxley is an MVC framework, meaning that the code is broken into three main layers–Model classes describe the state of the game, View classes display the game, and Controller classes handle the game logic.

Bloxley is designed to make it so advanced features like undo and animation come free (or mostly free), and make it easier to create level editors, and loading from and saving to XML on servers. Currently, Bloxley is only implemented in ActionScript 3, although an older version exists in ActionScript 2, and hopefully I’ll eventually create an Objective-C port.

Parts of this tutorial will seem like hand-waving–many of the complexities are handled by the framework. How all of the pieces work, and how to take advantage of them, will be covered in more detail in later tutorials. Until then, just sit back and relax; it’ll all make sense eventually.

The Game

The game we’re implementing for this tutorial is Sokoban, the classic game of pushing around boxes in a warehouse. For those unfamiliar with the game, you can check out our stopping point below. Click on the game to begin:

The basic rules to Sokoban are as follows:

  1. You can move workers around a grid, one square at a time.
  2. Only one worker moves at a time.
  3. Workers can push one block.
  4. The goal is to get all of the blocks onto special target squares.

Games in Bloxley

Before we get started, let’s go over the basic structure of Bloxley games. You’ll notice that every class in Bloxley is prefixed with BX. Bloxley games all take place on a 2-dimensional grid, which has 2 separate layers, patches and actors:

Patches

The bottom layer is made up of patches. Every grid square corresponds to single patch, which stays fixed. Patches can be in different states corresponding to what kind of square it is. In our Sokoban example, the three possible states are:

  1. Floor, for empty floor squares that can be walked upon.
  2. Wall, for squares that cannot be walked upon.
  3. Target, they behave like Floors and are the squares that you are trying to push boxes onto.

In the framework, each patch is an instance of BXPatch, and the behavior for patches is contained in a subclass of BXPatchController which you will need to implement.

Actors

The top layer of the grid is made up of actors. Actors are the parts of the game that moves around the board during play. Actors can be any shape or size, and can overlap as well. In Sokoban, there are two different kinds of actors:

  1. Worker, which can move around and push blocks
  2. Block, which can’t move on its own, and only get pushed by Workers.

In Bloxley, each actor is an instance of BXActor, but since their behavior can be much more complicated, each kind of actor must have its own subclass of BXActorController to contain its logic.

Controllers

In addition to the actor and patch controllers, you will have to define a game controller–which handles top level information about the game, and possibly one or more interaction controllers–which handle the game flow logic and how the user interacts with the game. The game controller will be a subclass of BXGame, and the interaction controllers will be subclasses of BXController.

Keys

The last major concept to understand are keys. A key is a string that represents what type an object or action is. For instance, every patch has a key that indicates what kind of patch it is–we’ll use “Floor” for floor patches, “Wall” for wall patches, and “Target” for target patches. Actors also have keys, as do other kinds of objects that we’ll discuss in later tutorials.

Implementing Sokoban

Okay, so now we have a basic idea of the pieces, let’s get started. Our plan of attack is as follows:

  1. Setting up the project
  2. Get the board to appear.
  3. Get the workers to appear.
  4. Getting workers to move around.
  5. Get the blocks to appear, and get pushed around.
  6. Implementing the winning condition.

Step 1: Setting up the project

Let’s get started. Start by downloading Bloxley from github here: Bloxley’s page on GitHub, or from the downloads page, and download the Sokoban graphics we’ll use here: Sokoban Graphics.

Next in Flash create a new ActionScript 3 project called Sokoban, and add $LOCALDATA and the location of your bloxley directory to the classpath. Go to the Publish Settings window Setting the classpath

Then copy the resources from the library provided into your project library.

Also, adjust the size of the .swf file so that it’s 608px x 352px, and the frame rate so that it’s 30 fps. Adjusting the flash size

Finally, we need a package to contain all our custom code, so in the same directory where you saved your Sokoban.fla file, create a new directory called sokoban.

Now that the project is set up, let’s begin coding.

Step 2: Creating the board

So now we need a game controller. In the sokoban directory, create a new file called SokobanGame.as, and insert the following code:

package sokoban {

    import flash.display.Stage;

    import bloxley.controller.game.*;

    public class SokobanGame extends BXGame {

        public function SokobanGame(stage:Stage) {
            super(stage);
        }

    }

}

That seems simple enough. Our game controller is a subclass of BXGame, and that requires the flash Stage object (which is the root of all movie clips in a flash program).

Next we need to define a patch controller, which will know how to create and display patches as they are needed. So in your sokoban directory, create a new file called SokobanPatchController.as and insert the following code:

package sokoban {

    import bloxley.controller.game.*;
    import bloxley.model.game.*;

    public class SokobanPatchController extends BXPatchController {

        public function SokobanPatchController(name:String, game:BXGame) {
            super(name, game);

            tiles({ Floor: " @$", Wall: "#", Target: ".+*" });
        }

    }

}

This does something a little more interesting–it defines several tiles. In Bloxley, a tile is a way to map the kind of patch (its key) to the characters that represent it in a level file. So this means, when loading a level a ‘ ‘, ‘@’, or ‘$’ will all create a Floor patch. (The ‘@’ and ‘$’ will represent a floor patch with a worker, and floor patch with a block, respectively).

Next we need to make sure that the Flash graphics will properly link to the code. This has 2 parts:

  1. In the Linkage Properties panel for the movie clip named “Patch”, ensure that the Class is set to game.Patch Setting the clip class

  2. In the Patch movie clip itself, make sure that there is a frame called “Floor”, a frame called “Wall”, and a frame called “Target” Setting the frame name to match with the Patch keys

Bloxley knows to look for a movie clip with class game.Patch when creating movie clips for Patches, and it looks for the frame with the same name as the patch clip. By setting those up, Bloxley can properly render the Patches. This will be set in the provided graphics pack, but will need to be set if you use your own graphics.

Now that we’ve created a controller, we need to tell the Sokoban game about it. Insert the following line into the BXGame constructor at the end:

controllers({ Patch: SokobanPatchController });

This tells Bloxley that we want a patch controller which is an instance of SokobanPatchController.

So now we need to instantiate a Sokoban game, and give it a sample level to render. Insert the following code into the Frame Actions for Sokoban.fla

import sokoban.*;

var game = new SokobanGame(stage);

game.loadLevel([
  "    #####          ",
  "    #   #          ",
  "    #$  #          ",
  "  ###  $##         ",
  "  #  $ $ #         ",
  "### # ## #   ######",
  "#   # ## #####  ..#",
  "# $  $          ..#",
  "##### ### #@##  ..#",
  "    #     #########",
  "    #######        "
]);

game.showBank("Main");

This tells Flash that we’re creating a new instance of SokobanGame, our top level game controller, and telling that game to load the level we provide. The level is given as an array of strings–each string represents one row of the level, and each character in the string represents one patch (and possibly some actors). The call to showBank() is because we don’t have an interaction controller yet.

When you run the Flash file, assuming everything has been entered correctly, you should get something that looks as follows: The game board with patches

Excellent! Our board now shows up.

Step 3: Workers

Now we want to get workers to show up. The level we’ve created includes a single worker, although multiple workers are possible.

Like we had to for the patches, we need to define a new controller–but in this case, we’re defining a subclass of BXActorController. Create a new file in the sokoban directory called SokobanWorkerController.as and include the following code:

package sokoban {

    import bloxley.controller.game.BXActorController;
    import bloxley.model.game.BXActor;

    public class SokobanWorkerController extends BXActorController {

        public function SokobanWorkerController(name, game) {
            super(name, game);

            setBoardString("@+");
        }

        override public function key(options = null):String {
            return "Worker";
        }

    }

}

So what does this code do? Well, there are three things to notice.

First, we’re calling setBoardString(). This function works similar to tiles()–it defines what characters on the level will generate a worker. In this case (as we discussed above), ‘@’ and ‘+’ characters will generate workers.

Next, we define the key():String method. This determines what key will be used for workers. It can take a hash of options, but we don’t need that right now.

Now we need to tell the game controller about SokobanWorkerController; change the relevant line in SokobanGameController to:

controllers({ Patch: SokobanPatchController, Worker: SokobanWorkerController });

Similar to what we did with the patches, ensure the Linkage Class of the ‘Worker’ movie clip is set to game.Worker. Now when we run our flash file, we see:

The game with a worker

Step 4: Making Workers Move

So now we have a worker. However, it just sits there, not doing anything. Now we need to turn it into an actual game.

To make a game out of our code, we need to allow user interaction–that requires an interaction controller. Bloxley includes BXPlayController, which is designed to be the starting point for implementing game play. Right now it’s sufficient for our purposes. So first, let’s tell our game controller about it–change the controllers() line in SokobanGameController to:

controllers({ Patch: SokobanPatchController, Worker: SokobanWorkerController, Play: BXPlayController });

Now that we have a game controller, we need to enable it. After the controller line, insert:

setCurrentGameController("Play");

And since we don’t need it any longer, remove the game.showBank() line from the Frame Actions in Flash.

This will enable us to move units around, but we need to tell Bloxley what units can be moved. So in SokobanWorkerController, add the following method:

override public function canBePlayer(actor:BXActor):Boolean {
    return true;
}

This determines whether an actor can be selected to be directly controlled by the user.

Now when we run Sokoban, we can move our worker around using the arrow keys! Notice that its movement is fully animated. For another neat trick, if you hit Backspace, then you’ll undo your last move. And if you hit Shift+Backspace, you’ll restart the board! Like animation, undo is something that is very easy to do with Bloxley.

However, we have a slight problem. Our worker can walk straight through walls. This is really not what we want.

To get around this, we need to tell the patch controller about movement through walls. When a Worker tries to walk into a patch of type Wall, the function canWorkerEnterWall() is called on the patch controller. This method can be implemented to define what should happen. Insert the following method into the SokobanPatchController class:

public function canWorkerEnterWall(action, source:BXActor, target:BXPatch) {
    action.fail();
}

In this method, action is the worker’s attempt to walk onto a wall. By telling Bloxley that that attempt should fail, we prevent workers from walking on walls. Re-run the flash file and have the worker move around. You’ll notice that it cannot step onto the wall patches.

Step 5: Blocks

In this step, we’re introducing another type of actor–blocks. Since the process is similar to what we did for workers, I’ll go through the steps more quickly:

  1. Create a file in the sokoban directory called SokobanBlockController.as and insert the following code:

    package sokoban {

    import bloxley.controller.game.; import bloxley.model.game.; import bloxley.controller.event.BXMoveAction;

    public class SokobanBlockController extends BXActorController {

    public function SokobanBlockController(name, game) {
        super(name, game);
    
        setBoardString("$*");
    }
    
    override public function key(options = null):String {
        return "Block";
    }
    

    }

    }

  2. Make sure that the movie clip in the flash library named “Block” has its Linkage Class set to game.Block.

  3. Update the controllers() line in the SokobanGameController to read:

    controllers({ Patch: SokobanPatchController, Worker: SokobanWorkerController, Play: BXPlayController, Block: SokobanBlockController });

Now when you run the flash file, it should look like this:

Board with Blocks

And by playing around with it, you’ll notice the next problem: our worker can’t interact with the blocks in any way. Luckily, this is easy to remedy. When a worker tries to step onto a block, the method canBeSteppedOnByWorker() gets called on the block controller. By implementing this method, we can tell Bloxley what should happen in this situation. Insert the following code into SokobanBlockController:

public function canBeSteppedOnByWorker(action:BXMoveAction, block:BXActor, player:BXActor) {
  action.causes(new BXMoveAction(block, action.direction()));
}

Now when we re-run the flash file, our worker can move around and push blocks–but only 1 block at a time! Excellent!

In Bloxley, all changes to the state of the game are handled through actions–subclasses of BXAction. By telling Bloxley one action is caused by another, then they succeed or fail together. So stepping onto a block causes that block to move in the same direction–in other words, the worker pushes the block. If that push is impossible (like trying to push onto a wall or another block), then the worker’s move fails as well. We’ll get into a lot more depth on actions in later tutorials.

Once more a problem presents itself. Our worker can push the blocks too well–the blocks can be pushed right onto the walls! There’s two ways to remedy this situation. We could define a method named canBlockEnterWall() in the SokobanPatchController, like we did for the workers. However, there is an easier way–instead of defining a second method, we can make our existing method more general. By changing the name of the method from canWorkerEnterWall() to canEnterWall() (leaving out the key of the object trying to enter the wall), this method will handle any actor trying to step onto a wall.

Now re-run the flash file. Play around with it–you’ll see that you can move the worker around, and have him push the blocks. Notice that when you undo an action (Backspace or Shift+Backspace), it properly replaces the blocks as well! Even if you move everything around, completely changing the board, one quick tap of Shift+Backspace will completely restart the board to its initial position.

Step 6: Completing a Level

Now that we can play the game, we need to tell Bloxley how the game is won. First, look back on the code that’s in place. While we’ve had a long discussion to get here, we really didn’t need to implement very much code–but we got a lot of behavior out of it. Our last step is: we need bloxley to recognize when we’ve won.

Luckily for us, this won’t really require more work that anything else we’ve done has, but I have to admit, it does involve a slight bit of cheating. See, Bloxley can handle pretty much any grid-based game, but its primary focus is games like Sokoban–games that involve player characters moving around the grid and interacting with the patches and actors. So while the game flow can be changed to handle real-time games like Tetris, and games with no clear player characters like Bejeweled, you get Sokoban-like behavior for free out of the box!

So how does this work? There’s a series of 7 methods that get called on the play controller, to walk the game through the gameplay. Here’s a list:

  1. startGame()–this gets called when the game begins.
  2. validUserActions()–this returns a list of methods that indicate that the player has made a move.
  3. heartbeat()–this gets called after the player makes a move.
  4. didBeatLevel()–this is used to test whether the player has beat the level.
  5. didLoseLevel()–this is used to test whether the player has lost the level.
  6. beatLevel()–this get called when the player does beat the level.
  7. lostLevel()–this get called when the player does lose the level.

That’s a lot of methods! However, we don’t have to implement all of them–most have built-in behavior that will be good enough for us, and some games (like Sokoban!) can’t be explicitly lost. So let’s get started!

First, since we’re going to be defining custom behavior, we need a place to define it. That means just using an instance of BXPlayController isn’t good enough anymore–we need our own subclass. Create a file in the sokoban directory called SokobanPlayController, and insert the following code:

package sokoban {

    import bloxley.controller.game.*;

    public class SokobanPlayController extends BXPlayController {

        public function SokobanPlayController(name: String, game:BXGame) {
            super(name, game);
        }

        override public function didBeatLevel():Boolean {
            return board().allActors().ofType("Block").areAllGood();
        }

    }
}

Yes, we really only did need to implement one of the methods listed above. Phew! Let’s run through the list and discuss why we skipped them:

  1. startGame()–the default behavior is to select the first actor that can be a player, which is what we want.
  2. validUserActions()–the default behavior is to return just “moveCharacter”, which is the only kind of user action that we care about.
  3. heartbeat()–by default this does nothing, which is okay.
  4. didBeatLevel()–by default this just return false, which is why we needed to override it.
  5. didLoseLevel()–this also defaults to returning false. Since Sokoban can’t be “lost”, we never need it to return anything else.
  6. beatLevel()–this performs a game over animation, which we’ll discuss below. Good enough.
  7. lostLevel()–since Sokoban can’t be lost, this will never get called.

We also need to tell our game controller about our new class. So in SokobanGame.as replace the call to controllers() with:

controllers({ Patch: SokobanPatchController, Worker: SokobanWorkerController, Play: SokobanPlayController,
  Block: SokobanBlockController });

Now let’s look a little closer at the implementation of the didBeatLevel() method; it should be readable as to what it’s doing. First, it gets the gameboard(). Then it gets all of the actors on the board of type Block. Finally, it checks to see if they’re all “good”, whatever that is.

What is “good”, you ask? Really, “good” can be anything. It’s just a placeholder that bloxley allows, to make it easier for actors to indicate when the think the level is over. Right now, it doesn’t mean anything, since we haven’t told Bloxley anything yet. So let’s talk about what it should mean.

What does it mean to beat a level in Sokoban? The goal is to get all of the blocks onto target squares. So it would make sense to implement “good” for a block to mean that it is standing on a target patch. Since it is part of the game logic, it needs to be placed in a controller; the actor controller SokobanBlockController seems like the obvious place it belongs. Open up SokobanBlockController and insert the following method into the class:

override public function isGood(actor:BXActor):Boolean {
    return actor.amIStandingOn("Target");
}

As you can see, the isGood() method takes in a single parameter–the actor in question–and returns true or false. As we discussed above, a block is “good” if it is on top of a target patch, and amIStandingOn() takes care of that.

Well, that was pretty easy. Sure, it involves a shortcut–but there’s a huge list of games that this basic gameflow will cover.

One final thing to discuss–what happens when we win the game? As I said above, the method beatLevel() gets called, which shows a “You Won” image. While (as always) this behavior can be overridden, it’s good enough for now.

Now to actually generate the “You Won” image, insert the following method into SokobanPlayController:

override public function createInterface() {
    super.createInterface();

    var screen = BXSystem.screenDimensions();

    setBank("Beat Level");
        var image = new BXImage(this, "BeatLevel", { centered: true, depth: 1 });
        image.goto([ screen[0] * 0.5, screen[1] * 0.5 ]);
        register( image );

}

We’ll hold off on discussing the details of this method until a later tutorial, but if you read it, you should be able to tell that it generates an image, and places it into the center of the screen. A bank (“Beat Level” in this case) is just a group of interface elements like buttons, images, and the game grid itself. Make sure that the Linkage Class for your winning banner is set to game.BeatLevel. Again, it’s already set in the provided graphics, but if you decide to change them, you’ll have to make sure that this is set.

Now run our game. When you get all of the blocks onto the target squares (remove some of the blocks from the level if you want to make things easier), you should get a banner to appear that looks like this:

You beat the level!

Congrats! You have now programmed a complete game! It has animation, unlimited undo, and lets you know when the game has been won. Not too bad for a day’s work.

What’s Next?

While our implementation of Sokoban is playable, it’s pretty bare-bones. The player is a single, static image. In our next tutorial, we’ll tackle a different game, called Tilox–and we’ll talk about how to polish the graphics so it looks more professional. We’ll also talk about some other parts of the Bloxley framework.