Glimmr Canvas-Based Drawing

version 2/101030 by Erik Temple

  • Home page
  • Beginning
  • Previous
  • Next



  • Example: ** Roguelike-like - This example uses image-maps and some sprites to create something that looks a lot like a roguelike dungeon-crawler. None of the actual game mechanics one would want in such a game are included, of course, but the basic techniques for display are provided.

    We use an image-map to display the outline of whatever room we are in. Some of the tiles are graphlinked, so that clicking on the floor of the room will move us one step in the direction clicked, and so that clicking on the exit to a room will take us out of the room. Only the latter takes any time: moving around within a room has no effect on actual gameplay in this example.

    We wouldn't have a roguelike, though, if all we had was a map. We also need characters that wander over the map. One way we could do this would be to layer multiple image-maps over one another--one map containing the characters, say, and another of the same size representing the base map. An easier method, the one this example employs, is to use sprites for the characters.

    The tiles used in this example come from a set of tiles known as LoFi Roguelike, by TIGSource user oryx. These are very nice, low-resolution tiles. Each of the tiles is 8 x 8 pixels, but we display them here at 24 by 24. Link to LoFi Roguelike: http://forums.tigsource.com/index.php?topic=8970.0

        "Roguelike-like"

        Include Glimmr Canvas-Based Drawing by Erik Temple.
        Include Glimmr Graphic Hyperlinks by Erik Temple.
        Include Glimmr Simple Graphics Window by Erik Temple.

    First we define the tileset. It's often more convenient to put a tileset into its own extension, but we don't have that luxury here, and doing it inline works too.

    Note the linked command entries in the tileset's translation table. These allow us to specify commands for individual tiles; when we click on these tiles on screen, the associated command will fire. Note that we include 5 versions of the floor tile, one for each of the four directions, and one that is linked with a hyphen. The directional versions are placed at the edge of a room's map, so that clicking on them will move us to the next room. The hyphen link is not issued as a command. We will instead intercept the hyphen link with a special graphlink processing rule (see Glimmr Graphic Hyperlinks) so that we can supply our own behavior later on.

        Section - The Tileset

        LoFi Roguelike is a tileset. The translation-table is the Table of Roguelike Tiles. The tile-width is 24. The tile-height is 24.

        Figure of Bowman is the file "Bowman.png".
        Figure of Floor is the file "Floor.png".
        Figure of Manticore is the file "Manticore.png".
        Figure of Stairs Up is the file "Stairs Up.png".
        Figure of Stairs Down is the file "Stairs Down.png".
        Figure of Table is the file "Table.png".
        Figure of Torch Wall is the file "Wall with Torch.png".
        Figure of Wall is the file "Wall.png".
        Figure of Vertical Wall is the file "Vertical Wall.png".


        Table of Roguelike Tiles
      Char  Tile  Linked Command  
      number  figure-name  text  
      1  Figure of Wall  ""  
      2  Figure of Floor  "-"  
      3  Figure of Torch Wall  ""  
      4  Figure of Vertical Wall  ""  
      5  Figure of Stairs Up  "up"  
      6  Figure of Stairs Down  "down"  
      7  Figure of Floor  "north"  
      8  Figure of Floor  "east"  
      9  Figure of Floor  "south"  
      10  Figure of Floor  "west"  
        

        Section - The Graphics Window

        The measurement of the graphics-window is 336.
        The canvas-width of the graphics-canvas is 336. The canvas-height of the graphics-canvas is 336.

        When play begins:
            follow the update dungeon map rule;
            open up the graphics-window.
            
        [After printing the banner text:
            say "[line break]This imitation of a roguelike game is an example for the Glimmr Canvas-Based Drawing extension. It features an image-map element that displays the map base for each room, using regular graphic tiles from a tileset known as LoFi Roguelike, by TIGSource user oryx (http://forums.tigsource.com/index.php?topic=8970.0). The character icons are borrowed from the the same tileset, and are implemented in Glimmr as sprite objects.[paragraph break]Click on the floor of the room to move toward the location clicked. Click on the furthest floor tile of a map exit to move to the next room. Click on the player's avatar (the archer) to take inventory, or click on an enemy to attack it.[paragraph break]"]

    Sprites are displayed using the coordinate system of the canvas, but the characters we are using them to display will need to move according to the internal grid of our image-map. We handle this by giving the sprite a second set of coordinates that we will use for calculating the character's position on the map. We convert this coordinate to canvas coordinates (based on the current position of the image-map on the canvas) before scaling and drawing a sprite. This is the "convert origin coordinate" rule below, and it utilizes one of the phrases that GCBD provides for converting between canvas, screen, and image-map coordinate systems; see the section on image-maps in the documentation above for more.

    We want each room to have its own independent map, but there is no reason to create a different image-map element for each room. Instead, we have just one image-map object, the Dungeon Map. We assign each room a tile-array, and change the tile-array of the Dungeon Map to reflect the tile-array of the location.

    At the same time, we also ensure that the appropriate characters are visible. Each character in the game (there are only two, the player and the beast at the end of his quest) has an associated sprite. Whenever we need to redraw the map--as usual, we do this in the looking rules--we activate the characters he can see and deactivate the ones he can't.

        
        Section - Element display and updating

        Element scaling rule for a character-sprite (called the character) (this is the convert origin coordinate rule):
            now the origin of the character is the canvas coordinate equivalent of the grid-coordinate of the character in the coordinates of the the dungeon map;
            continue.

        The convert origin coordinate rule is listed before the element origin scaling rule in the element scaling rules.

        A room has a list of lists of numbers called the tile-array. A room has a list of numbers called the initial grid coordinate.

        Carry out looking:
            follow the update dungeon map rule;
            follow the window-drawing rules for the graphics-window.
        
        This is the update dungeon map rule:
            now the tile-array of the Dungeon Map is the tile-array of the location;
            construct graphic hyperlinks array for the the dungeon map;
            now the grid-coordinate of the character of the player is the initial grid coordinate of the location;
            repeat with P running through people:
                if P can be seen by the player:
                    mark the character of P for display;
                otherwise:
                    deactivate the character of P.
        

        Section - Image-map and Sprites

        The dungeon map is a tileset image-map. The associated tileset is LoFi Roguelike. The origin is {24, 24}. The tiled graphlink status of the dungeon map is g-active.

        A character-sprite is a kind of sprite. A character-sprite has a list of numbers called the grid-coordinate. The display-layer of a character-sprite is 3. The graphlink status of a character-sprite is g-active. The display status of a character-sprite is g-inactive.

        A person has a character-sprite called the character.

        Some character-sprites are defined by the Table of Characters.

        Table of Characters
      character-sprite  image-ID  grid-coordinate  linked replacement-command  
      Bowman-sprite  Figure of Bowman  {4, 4}  "inventory"  
      Manticore-sprite  Figure of Manticore  {5, 7}  "attack manticore"  
        

    The next section contains the scenario and is fairly simple. We assign the character-sprites to our characters, and we also set the player's sprite to be active from the start.

    We then define the rooms and other behavior. Note that each room has an image-map, and that we can use line breaks within the list to make it more or less human-readable.

        Section - Scenario

        The player carries a bow and an arrow. The character of the player is the Bowman-sprite. The display status of the Bowman-sprite is g-active.

        The Entrance is a room. "A dry and dusty room. A passage leads east, and crumbling stairs lead back to the cave above." The tile-array is {
            { 0, 4, 1, 3, 1, 1, 3, 1, 4, 0, 0, 0 },
            { 0, 4, 2, 2, 2, 2, 2, 2, 4, 0, 0, 0 },
            { 0, 4, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0 },
            { 0, 4, 5, 2, 2, 2, 2, 2, 2, 2, 8, 0 },
            { 0, 4, 2, 2, 2, 2, 2, 2, 4, 1, 1, 0 },
            { 0, 4, 2, 2, 2, 2, 2, 2, 4, 0, 0, 0 },
            { 0, 4, 2, 2, 2, 2, 2, 2, 4, 0, 0, 0 },
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }
        }. The initial grid coordinate is {6, 4}.

        Instead of going up in the Entrance:
            say "But your adventure has only just begun, you rogue!".

        The Passage is east of the Entrance. "A narrow passage that turns toward the south, where it widens." The tile-array is {
            { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 },
            { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 },
            { 00, 01, 01, 01, 01, 01, 01, 01, 01, 04, 00, 00 },
            { 00, 10, 02, 02, 02, 02, 02, 02, 02, 04, 00, 00 },
            { 00, 01, 01, 01, 01, 01, 01, 04, 02, 02, 04, 00 },
            { 00, 00, 00, 00, 00, 00, 00, 04, 02, 02, 04, 00 },
            { 00, 00, 00, 00, 00, 00, 00, 04, 02, 02, 04, 00 },
            { 00, 00, 00, 00, 00, 00, 00, 04, 02, 02, 04, 00 },
            { 00, 00, 00, 00, 00, 00, 00, 04, 09, 09, 04, 00 }
        }. The initial grid coordinate is {9, 4}.

        The Den of the Beast is south of the Passage. "A large room that nevertheless feels close and fetid. The great Manticore lurks here." The tile-array is {
            { 0, 0, 0, 0, 0, 0, 0, 4, 7, 7, 4, 0 },
            { 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 4, 0 },
            { 4, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 4 },
            { 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4 },
            { 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1 },
            { 0, 0, 4, 2, 2, 2, 2, 2, 2, 2, 4, 0 },
            { 0, 0, 4, 2, 2, 2, 2, 2, 2, 2, 4, 0 },
            { 0, 0, 4, 2, 2, 2, 2, 2, 2, 2, 4, 0 },
            { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }
        }. The initial grid coordinate is {9, 3}.

        Check going north from the Den:
            say "If you flee now, you will never be able to face yourself again." instead.

        Every turn while the player is in the Den:
            say "[one of]The Manticore roars[or]A snort echoes through the chamber. The Manticor shakes its mane[or]The Manticore stares at you. You feel that your impudence is truly epic[or]The spiked pad at the end of the Manticore's tail sways lazily over the beast's back[purely at random]."
        
        The Manticore is an animal in the Den. "The Manticore stirs. His eyes lock on yours." The character is the Manticore-sprite. The description is "More fearsome than the villagers said. A massive catlike creature. Its tail is studded with large, deadly spikes."

        Understand "shoot [something]" as attacking.
        
        Instead of attacking the Manticore:
            say "As you nock one of your spindly arrows, you realize just how foolish you have been. Before you can draw back the bowstring, the Manticore's tail stiffens and a shower of deadly spikes arcs toward you.";
            end the story finally.
        
        Every turn when the player is in the Den:
            if the player has been in the Den for three turns and the current action is not the action of attacking the manticore:
                say "The Manticore has evidently tired of your dithering. He crouches low and, before you can fit your arrow, he flings a barrage of spikes from his tail.";
                end the story finally.


    Finally, we have a couple of things we want to do with mouse input. Since all of our mouse input is game-related--we move the player, take inventory, attack monsters, etc.--we don't want to continue issuing commands if the player is dead. To do this, we make the first graphlink processing rule a check of whether the story is still active. The odd phrasing of the condition reflects a weakness in the Standard Rules' phraseology.

    More interesting is the handle movement rule. This rule moves the player's avatar in response to mouse clicks on the map. If the player has clicked on the Dungeon Map, we check to see what command was triggered by the click. If that command was "-", we know that the player has clicked on a floor tile. We then convert the coordinates of the mouse input from screen coordinates to the coordinates of the image-map. We compare those coordinates with the coordinates of the player, and move the player one step horizontally and/or one step vertically toward the clicked coordinate.

    You will notice that this rule doesn't do any collision checking--what happens if the player moves over a wall, or on top of the Manticore? This example game is set up so that this is impossible. The player will die before he reaches the Manticore, and because the walls are always at the edge of the map and are not themselves graphlinked, the player can never reach them by clicking. In a real game, we'd need a full system of collision detection, but this slight of hand suffices for a short example.

        Section - Mouse movement

        First graphlink processing rule when not the story has not ended:
            rule fails.
        
        A graphlink processing rule for the Dungeon Map when the candidate replacement command is "-" (this is the handle movement rule):
            now the candidate replacement command is "";
            let L be a list of numbers;
            let L be {0, 0};
            now entry 1 of L is current graphlink x;
            now entry 2 of L is current graphlink y;
            let L be the equivalent of screen coordinate L in the coordinates of the Dungeon Map;
            if entry 1 of L is greater than entry 1 of the grid-coordinate of the Bowman-sprite:
                increase entry 1 of the grid-coordinate of the Bowman-sprite by 1;
            otherwise if entry 1 of L is less than entry 1 of the grid-coordinate of the Bowman-sprite:
                decrease entry 1 of the grid-coordinate of the Bowman-sprite by 1;
            if entry 2 of L is greater than entry 2 of the grid-coordinate of the Bowman-sprite:
                increase entry 2 of the grid-coordinate of the Bowman-sprite by 1;
            otherwise if entry 2 of L is less than entry 2 of the grid-coordinate of the Bowman-sprite:
                decrease entry 2 of the grid-coordinate of the Bowman-sprite by 1;
            follow the window-drawing rules for the graphics-window;
            rule succeeds.