Displaying the map contents

Right now, the game scene is dependent on another module that hasn't been created, the world module. This module is needed to create a visible representation of the abstract Map data, from the format created by the sok.load function.

Getting ready

The world module is used on the following line in game.lua:

self.World = require "world" (30, 20, lawnParty, statics, lawnParty, movables)

This tells us that the world module needs to return a function. That function takes two numbers, the number of columns of tiles and rows of tiles that the map object can display at once, and several graphical arguments: an image sheet containing the tiles to use for walls, goals, and empty spaces, a table explaining which to use for each purpose, another image sheet (or in this case, the same one) containing the images to use for movable elements, and a table containing the sprite descriptions for each movable item. The function also returns some sort of object or value representing the world.

The resulting object is immediately used on two other lines:

  group:insert(self.World)
  self:addEventListener('Game', self.World)

We also know that the returned object has to be a display object (since it can be inserted into a group), and that it will receive Game events (although it is not required to act on them).

Finally, the object is used once in the game's enterScene method:

  self.World:Load(self.Map)

So, the returned object will also need a Load method that takes the Map data as an argument. This method will arrange the elements of the world component to represent the specified map.

To get started, create a new world.lua file in your SuperCargo project directory and open it.

Getting on with it

We established during preparation that the module creates a function that takes certain arguments, so we'll enter the skeleton for that into the new file as follows:

return function(columns, rows, tileSheet, tiles, spriteSheet, sprites)
end

We said that the function needs to return a display object. Since it will contain many other tiles and sprites, this object should be a group:

return function(columns, rows, tileSheet, tiles, spriteSheet, sprites)
  local self = display.newGroup()
  return self
end

The returned group needs a method called Load that takes a map structure:

  local self = display.newGroup()
  function self:Load(map)
  end
  return self

Adding the content layers

We've gone on repeatedly about how the Map component is separated into two layers, so it makes sense to create two visual layers to parallel them:

  local self = display.newGroup()
  self.Tiles = display.newImageGroup(tileSheet)
  self:insert(self.Tiles)
  self.Mobs = display.newImageGroup(spriteSheet)
  self:insert(self.Mobs)
  function self:Load(map)

Tip

Optimized image groups

Since we're depending on the fact that all tiles come from a single image sheet, and all sprites also come from a single image sheet (also called a texture atlas), we can take advantage of Corona's image groups, which allow for higher performance when all the contents of a group can be guaranteed to use the same texture raster as their source data. While Sokoban is not a performance-critical game, we can experiment here with using this functionality.

Since there will be a tile in each space of the world, but we won't know which one until the map is loaded, we represent each tile with a sprite that has one sequence for each thing that tile can contain. To display the fixed part of a map, the world can just switch each tile to the sequence for the contents at that point. So that we can find each tile, we'll also store references to all of them in another two-dimensional array.

  self:insert(self.Tiles)
  self.Tiles.Row = {}
  for row = 1, rows do
    self.Tiles.Row[row] = {}
    for column = 1, columns do
      local tile = display.newSprite(self.Tiles, tileSheet, tiles)
      tile:setReferencePoint( display.BottomRightReferencePoint )
      tile.x, tile.y = column * tile.width, row * tile.height
      self.Tiles.Row[row][column] = tile
    end
  end
  self.Mobs = display.newGroup()

For each row in the specified size, we create a row in the array, and enough tiles to cover every column in the row.

For the Mobs layer (mob in this case is a term from the MUD community that means mobile object), we just create an array to track the positions of later sprites:

  self:insert(self.Mobs)
  self.Mobs.Row = {}
  for i = 1, #self.Tiles.Row do
    table.insert(self.Mobs.Row, {})
  end
  function self:Load(map)

Loading the world with a map

Now the blank map is created, we need to define the Load function so that it actually copies the map content into the display. Perform the following steps to load the world with a map:

  1. The first step is to make sure the Mobs layer is empty, in case the map layer was in use previously:
      function self:Load(map)
        for i = self.Mobs.numChildren, 1, -1 do
          self.Mobs:remove(i)
        end
      end
  2. Next, we consider each tile of the world, setting the tile sprite's sequence to match the fixed layer of the map (or a blank default if the supplied map doesn't cover the whole area):
          self.Mobs:remove(i)
        end
        for y, row in ipairs(self.Tiles.Row) do
          for x, tile in ipairs(row) do
            tile:setSequence(map.Fixed[y] and map.Fixed[y][x] or ' ')
            local mob = map.Moving[y] and map.Moving[y][x]
          end
        end
      end
      return self
  3. At the same time, we need to create sprites that represent moving objects at those locations:
            local mob = map.Moving[y] and map.Moving[y][x]
            if mob then
              local new = display.newSprite(self.Mobs, spriteSheet, sprites[mob])
              new:setReferencePoint( display.BottomRightReferencePoint)
              new.x, new.y = x * new.width, y * new.height
              self.Mobs.Row[y][x] = new
            end
          end

Tip

Tiling from the bottom-right corner

We set objects to be placed by their bottom-right corners, because in a system where ranges typically start at one rather than zero, this allows that corner to be placed at the index times the size of each tile and still land in the right place. For example, an object located at the tile position (3, 5) with a tile size of 16 will end up with its bottom-right corner placed at (48, 80) and its top-left at (32, 64). This means that it covers the third column and fifth row of 16 x 16 blocks starting at (0, 0).

What did we do?

Using an existing, defined data format, we created a generalized way to display the contents of a map compatible with that format.

What else do I need to know?

At this point the project should be ready to run without errors, although it will just display a single level on the screen like this and doesn't allow the user to play at all. However, one of the common strategies for programming is to develop one component, test it, and make sure it works before adding new features. Now we know we have a working map and can focus on adding gameplay.

If your project doesn't work, particularly check that you modified the main.lua file to launch the game scene rather than scenetemplate, and that you're passing it the File and Level fields in its params table.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.137.243