Activity: Tiles

||scene:Tile maps|| allow for an easy way to design and structure maps that the player can explore.

||scene:Wall collision|| events and ||scene:Tiles|| allow for further control over how developers can interact with the individual ||scene:tiles|| that make up the ||scene:tile map||.

Concept: On Hit Tile Events

The ||scene:scene.onHitTile|| event occurs when a sprite of the given ||sprites:Kind|| collides with a given ||scene:Tile|| that is a ||scene:Wall||.

scene.onHitTile(0, 0, null);

Example #1: Rock Collector

Animation of tile collision

  1. Review the code below
  2. Identify how the ||scene:scene.onTileHit|| event is used to make the sprite interact with the scene
  3. How does the ||variables:tile|| parameter in the ||scene:on hit tile|| event correspond to the type of tile that is used?
let mySprite: Sprite = sprites.create(sprites.castle.heroWalkFront1, SpriteKind.Player);
controller.moveSprite(mySprite, 100, 100);

scene.setTileMap(img`
    7 7 7 7 7 7 7 7 7 7 
    7 7 7 7 7 7 7 7 7 7 
    7 7 f 7 7 7 7 7 7 7 
    7 7 7 7 7 7 7 7 7 7 
    7 7 7 7 7 7 7 7 f 7 
    7 7 7 f 7 7 7 f 7 7 
    7 7 7 7 7 7 7 f 7 7 
    7 7 7 7 7 7 7 7 7 7 
`);

scene.setBackgroundColor(6);
scene.setTile(7, sprites.castle.tileDarkGrass1);
scene.setTile(15, sprites.castle.rock0, true);

scene.onHitTile(SpriteKind.Player, 15, function (sprite: Sprite) {
    sprite.say("Ooh! A rock!", 1000);
});

Student Task #1: Collect More

  1. Start with the code from example #1
  2. Create at least one more tile that is not a wall, and add it to the ||scene:tile map||
  3. Create at least one more type of ||scene:wall||, and add it to the ||scene:tile map||. Make the image be of something that looks like a “portal”
  4. Add an ||scene:on hit tile|| event that occurs when ||variables:mySprite|| hits the new type of ||scene:wall||
  5. In the new event, set ||variables:mySprite|| to a random new ||sprites:x|| and ||sprites:y|| position

Concept: Tiles

A ||scene:tile map|| is made up of tiles of type ||scene:tiles.Tile||. This type is defined in the ||scene:tiles|| namespace.

The ||scene:scene.getTile|| and ||scene:scene.setTileAt|| functions can be used to get and modify the individual ||scene:Tiles|| in the ||scene:tile map||.

scene.getTile(0, 0);
scene.setTileAt(null, 0);

Example #2: Draw a Red Line

  1. Review the code below
  2. Identify how it gets a specific tile and stores it in variable
  3. Identify how a specific tile is set to be a different color
scene.setTileMap(img`
    f f f f f f f f f f
    f f f f f f f f f f
    f f f f f f f f f f
    f f f f f f f f f f
    f f f f f f f f f f
    f f f f f f f f f f
    f f f f f f f f f f
    f f f f f f f f f f
`);
for (let i = 0; i < 8; i++) {
    let currentTile: tiles.Tile = scene.getTile(i, i);
    scene.setTileAt(currentTile, 2);
    pause(250);
}

Student Task #2: Random Tiles

  1. Create a 10 x 8 ||scene:tile map||
  2. Create an ||game:on update interval|| event that runs every 2000 ms
  3. Inside the event, get a random tile by selecting a random row (between 0 and 7) and column (between 0 and 9)
  4. Set this tile to be the color blue (8)

Concept: Placing Sprites

The ||scene:place|| function can be used to set a ||sprites:sprite|| to be centered on a given ||scene:tile||. This makes it easy to place ||sprites:sprites|| in different locations around the map.

Example #3: Place Some Flowers

  1. Review the code below
  2. Identify how and where the ||variables:flower|| sprite is ||scene:placed|| on ||variables:myTile||
  3. Identify where the ||variables:player|| can be placed on the ||scene:tile map||. Can it be placed in between two tiles?
namespace SpriteKind {
    export const Flower = SpriteKind.create();
}
scene.setTileMap(img`
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
`);

let player: Sprite = sprites.create(sprites.castle.heroWalkFront1, SpriteKind.Player);
let flower: Sprite = sprites.create(sprites.castle.tileDarkGrass2, SpriteKind.Flower);

let myTile: tiles.Tile = scene.getTile(1, 1);
myTile.place(flower);

controller.anyButton.onEvent(ControllerButtonEvent.Pressed, function () {
    myTile = scene.getTile(Math.randomRange(0, 9), Math.randomRange(0, 7));
    myTile.place(player);
});

Student Task #3: Place a House

  1. Start with the code from example #3
  2. Create a type of tile that is the color orange (4), and add it somewhere on the ||scene:tile map||
  3. Create a new sprite, ||variables:house||, that represents a house. Use the image of a blue house (||sprites:sprites.castle.houseBlue||)
  4. Use ||scene:place|| to place ||variables:house|| on top of the ||scene:tile|| that was orange in the ||scene:tile map||

Concept: Tiles by Type

Placing ||sprites:sprites|| on top of ||scene:tiles|| may not be extremely exciting to start, but becomes more useful when ||scene:tiles|| are created in other ways.

The function ||scene:scene.getTilesByType|| returns an array of all of the tiles in the ||scene:tile map|| of the color specified. This can be very useful to help set up levels, by placing characters and items in specific locations on the screen.

scene.getTilesByType(0);

Example #4: Flower Town

  1. Review the code below
  2. Identify how it is different from example #3
  3. Identify how the ||scene:Tile|| ||arrays:array|| is created
  4. Identify how the ||scene:Tile|| ||arrays:array|| is used
namespace SpriteKind {
    export const Flower = SpriteKind.create();
}

scene.setTileMap(img`
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
    7 6 7 6 7 6 7 6 7 6 
    6 7 6 7 6 7 6 7 6 7 
`);

let flowerTiles: tiles.Tile[] = scene.getTilesByType(6);
for (let i = 0; i < flowerTiles.length; i++) {
    let flower: Sprite = sprites.create(sprites.castle.tileDarkGrass2, SpriteKind.Flower);
    flower.setFlag(SpriteFlag.Ghost, true);
    flowerTiles[i].place(flower);
}

let player: Sprite = sprites.create(sprites.castle.heroWalkFront1, SpriteKind.Player);
controller.moveSprite(player, 100, 100);
scene.cameraFollowSprite(player);

Student Task #4: Fill in the Neighborhood

Animation of player moving around map with house

  1. Start with the code from example #4
  2. Expand the ||scene:tile map|| to be 16 x 16, and add orange (4) tiles on the map
  3. Use ||scene:scene.getTilesByType|| to obtain an array of all orange ||scene:Tiles||
  4. Use a loop to create a ||sprites:sprite|| representing houses for every orange ||scene:Tile||. ||scene:Place|| the houses on top of the orange ||scene:Tiles||.

What did we learn?

  1. What does a ||scene:on hit tile|| event allow you to do?
  2. What is the relationship between ||scene:Tiles|| and ||scene:tile maps||?
  3. How can ||scene:scene.getTilesByType|| allow ||sprites:Sprites|| to be placed in different locations on the screen more easily?

Before moving on to the next lesson, it is recommended that you check out the selected problems for this section to review the material and practice the concepts introduced in this section.

Case Study

Recharge Rate Up

Add a new type of PowerUp, which makes the ships energy recharge faster.

In the current game, you have the energy recharge based off an ||game:on game update interval|| event, which occurs every 750ms. It may seem like this is an easy change, by just using a ||variables:variable|| for the interval instead of a specific time, but this doesn’t quite work. The ||game:game.onUpdateInterval|| function takes in an interval in milliseconds and an event handler function, and causes the event handler to occur on the interval

To fix this, you will need to change the ||game:on game update interval|| event in the ship namespace to an ||game:on game update|| event, and keep track of the time yourself. The ||game:game.runtime|| function is useful for this, as it gives the time since the game game was started, in milliseconds.

First, create two new variables in the ship namespace: ||variables:rechargeDelay|| and ||variables:lastRecharge||. ||variables:rechargeDelay|| should keep track of the current delay between recharges, and should start at 750ms (like the previous ||game:on game update interval||). ||variables:lastRecharge|| should keep track of the last time that the ship’s currentCharge was incremented.

In the ||game:on game update|| event, get the current time, and check if the time that has passed since the ||variables:lastRecharge|| is greater than or equal to the ||variables:rechargeDelay||. If it is, update ||variables:lastRecharge|| to the current time, and increment ||variables:currentCharge|| if it is less than ||variables:maxCharge||.

Finally, add the new RechargeRateUp PowerUp to the game. In the ||sprites:overlap event|| between Player and PowerUp, decrement the ||variables:ship.rechargeDelay|| by 20 if the PowerUp is of type RechargeRateUp. Set the response for this PowerUp to “Faster Charge!”.

Solution

enum PowerUpType {
    Health,
    Score,
    EnergyUp,
    RechargeRateUp
}

namespace ship {
    export let rechargeDelay = 750;
    let lastRecharge = 0;

    game.onUpdate(function () {
        let currentTime = game.runtime();
        if (currentTime - lastRecharge >= rechargeDelay) {
            lastRecharge = currentTime;
            if (currentCharge < maxCharge) {
                currentCharge++;
            }
        }
    });
}

namespace powerups {
    let availablePowerUps = [
        PowerUpType.Health,
        PowerUpType.Score,
        PowerUpType.EnergyUp,
        PowerUpType.RechargeRateUp
    ];

    export let responses: string[] = [];
    responses[PowerUpType.Health] = "Got health!";
    responses[PowerUpType.Score] = "Score!";
    responses[PowerUpType.EnergyUp] = "More Energy!";
    responses[PowerUpType.RechargeRateUp] = "Faster Charge!";
}

namespace overlapevents {
    // When a player hits a powerup, apply the bonus for that powerup
    sprites.onOverlap(SpriteKind.Player, SpriteKind.PowerUp, function (sprite: Sprite, otherSprite: Sprite) {
        let powerUp: number = powerups.getType(otherSprite);
        otherSprite.destroy();
        sprite.say(powerups.responses[powerUp], 500);
        if (powerUp == PowerUpType.Health) {
            info.changeLifeBy(1);
        } else if (powerUp == PowerUpType.Score) {
            info.changeScoreBy(15);
        } else if (powerUp == PowerUpType.EnergyUp) {
            ship.maxCharge++;
        } else if (powerUp == PowerUpType.RechargeRateUp) {
            ship.rechargeDelay -= 20;
        }
    });
}