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.
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.
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
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)
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)
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:
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
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
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
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).
Using an existing, defined data format, we created a generalized way to display the contents of a map compatible with that format.
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.
18.118.137.243