How to structure a display object comprised of multiple graphical elements

I’m hoping someone can point me in the right direction.

In the project I’m working/learning on, I have a game map made up of a 10x10 grid. Each square has a couple of elements that need to effect its graphical representation.

For instance, the background color of every square will be determined by it’s ownership. Either, white, no one owns it, or blue, red, green and yellow depending on which player owns it.

In addition, each squares may have certain structures built on them by the controlling player.

Further, any given square may have a defensive value. A value of zero will have no graphical element, but a value of 1-3 will put up a little black shield in the upper left corner with the number 1-3 inside it.

Each square will be selectable, so that the player may upgrade the square, and the entire map is scrollable because it’s larger then the display area.

I have so far been working with a tilemap, and that may still prove to be the best solution, but it’s getting to be a biggish file. 15 tile types for each tile color (5) is 75 tiles, but that doesn’t address the defensive value of the square, so if I had to provide a tile with the black shield and without the black shield, that bumps me up to 150 tiles in my tile map, and that doesn’t address the value displayed on the black shield, which I can probably do with text, but it will have to scroll in sync with the map.

I’m wondering if anyone can tell me the best way to structure this.
Is it possible to do a color swap for instance, so that I wouldn’t need an entire set of tiles for every player color, but just one set that is adjusted on the fly based on the tile ownership value?

Is it possible to have a display object with multiple layers of graphics, one layer for the color, one for the structure, one for defensive value, and maybe one for player unit, that I’m leaving out of this question for now?

A large game map might be as big as 400 squares, also, if that effects any suggestions people may have.

Any input or direction towards a specific tutorial that may give me insight on my question will be most appreciated.
[import]uid: 170004 topic_id: 34899 reply_id: 334899[/import]

What you need is this wonderful thing called a display group (see the api: display.newGroup(). It is a container of other display objects. From your description, you really only need 5 tiles (one for each color … and could probably get away with just one and use the :setFillColor() method to color them), a graphic for your shield logo (mostly transparent) and then use a display.newText() API to draw the numbers. Then you could do something like this:

local grid = {}  
for row = 1, 50 do -- assume 50 rows  
 grid[row] = {}  
 for col = 1, 50 do -- assume 50 columns  
 grid[row][col] = display.newGroup()  
 grid[row][col].tile = display.newImageRect("tile.png",32,32)  
 grid[row][col]:insert(grid[row][col].tile  
 grid[row][col].tile:setFillColor(255,255,255)  
 grid[row][col].color = "white" -- for your reference  
 grid[row][col].count = 0  
 grid[row][col].shield = display.newImageRect("shield.png", 32, 32)  
 grid[row][col]:insert(grid[row][col].shield)  
 grid[row][col].shield.isVisible = false -- don't show it until count its greater than 0  
 grid[row][col].countText = display.newText("", 0, 0, "Helvetica", 12)  
 grid[row][col]:insert(countText)  
 grid[row][col].countText.isVisible = false -- hide it too for now  
 --   
 -- at this point you should have a complete group that you can position as necessary  
 -- and insert into your scrollView  
 end  
end  

First, I just wrote that off the top of my head, so it could be full of errors…

Secondly, it’s using a technique where you can add any attribute you want to an object and that attribute follows the object around. This might have been easier to understand if we used different tables to track the group, the tile, the shield and the count separately, but this packs everything into one table which each cell is the group. Objects like that are just big Lua tables and you can freely add other members to it, so in this case, it is the group, but it also is a pointer to the tile, a pointer to the shield, etc. as well as various attributes to track.

I’m assuming you would want some touch/tap handler, then you would just add that to the group, when the handler receives the tap, the event.target variable will be the group and all the groups components are nicely packaged together for you (event.target.tile, event.target.countText, etc.) and you could set your fill color, toggle the visibility of the shield/text, etc.
[import]uid: 199310 topic_id: 34899 reply_id: 138697[/import]

Hi Rob
Thank you for the awesome supper fast response.
This really helps me get my head around my problem, and expands my understanding of what you can do with display groups.

I’m sure I can use that structure and modify it for me needs, like making the tiles bigger and the shield graphic in the upper left corner of the tile, etc…

I have a number of structure types that will have a symbol displayed on the lower right corner of the tile as well. Would you set up a 14 branch if statement that checks the structure value to apply the correct image file to that tile, or is there a better way?

[import]uid: 170004 topic_id: 34899 reply_id: 138704[/import]

You would just make a reference table instead.

local tileSymbols = {} tileSymbols[1] = "castle.png" tileSymbols[2] = "hut.png" tileSymbols[3] = "chamberPot.png" -- etc...

Using that, you can loop through all the tiles in a for loop (as Rob showed you) and either create images using those references or destroy the old and create a new one. The basic advantage here is that the reference table allows you to use variables, so, say you give your tile a symbol id…

tile = display.newImage("cooltile.png") tile.id = 17

Then you could later on find the symbol image to set by using

symbolImage = display.newImage( tileSymbols[tile.id] )

Basically, tileSymbols is a table, and tile.id is a variable representing a number, so it works the same way as a number. It’s a bit harder to read but it’s much easier to maintain than doing huge if/else code. [import]uid: 41884 topic_id: 34899 reply_id: 138709[/import]

thanks Richard [import]uid: 170004 topic_id: 34899 reply_id: 138718[/import]

  1. Okay, first up, when you post code, please encapsulate in the html tags: < code> < / code> (minus the spaces inside the tags) That way it looks like code and it’s easier for people here to help. :slight_smile:

  2. You can use a reference table for your color coding as well. Here’s how it works:

[code] – Put the table outside of the loop, maybe at the beginning of the lua file somewhere.
local playerColor = {}
playerColor[1] = {255,255,255}
playerColor[2] = {157,173,249}
playerColor[3] = {255,115,115}
playerColor[4] = {122,217,114}
playerColor[5] = {252,254,117}

– Then, later, when you’re in the loop
grid[row][col].tile:setFillColor( unpack( playerColor[mapTable[row][col][2] ] ) )
– “unpack()” converts {x,y,z} into x,y,z
[/code]

  1. At a glance, it looks like your positioning method puts the centerpoint of tile 1,1 at x0,y0, but it couldn’t hurt to manually set x/y for the structure during creation.

grid[row][col].structure = display.newImageRect( tileType[typeId], 96, 96 ) grid[row][col]:insert(grid[row][col].structure) grid[row][col].structure.x = grid[row][col].tile.x grid[row][col].structure.y = grid[row][col].tile.y

That just makes sure their internal x/y match up. From there you can change reference points as needed to tweak it… [import]uid: 41884 topic_id: 34899 reply_id: 138998[/import]

Thanks again Richard.

I will use < code> < / code> from now on, just didn’t know how, but now I do.

Also, that solved my issue with alignment. I will be using your suggestion for using a reference table for player color also. [import]uid: 170004 topic_id: 34899 reply_id: 139069[/import]

I was wondering if someone could show me the best method for making my individual tiles touchable “selectable” so that I can then create ways for the user to make changes to the specific tile.

The player will have options specific to the tiletype, and if they control the tile they touch.

I’ve read a bit about the takefocus() but the only example I’ve found about a touchable display object inside a scrollview was for a button widget, and not a more generic display object such as my tiles.

[import]uid: 170004 topic_id: 34899 reply_id: 139167[/import]

okay, so I have gotten a start on this, and have gotten it to create a grid of tiles in the scrollview using data from my map file i created, but I’m running into some issues.

my problem is that the structure icons assigned to the tiles are not lined up with my tiles correctly. They seem to be about about 50 pixels off both x and y

here is my code

 -- Create a new ScrollView widget:  
 local scrollView = widget.newScrollView{  
 width = 420,  
 height = 320,  
 scrollWidth = 1000,  
 scrollHeight = 1000,  
 friction = .75,  
 maskFile="mask-420x320.png",  
 hideBackground = true,  
 hideScrollBar = true,  
 listener = scrollViewListener  
 }  
  
 -- populate tiletype  
 local tileType = {}  
 tileType[1] = "blank.png"  
 tileType[2] = "agriculture.png"  
 tileType[3] = "farm.png"  
 tileType[4] = "industry.png"  
 tileType[5] = "factory.png"  
 tileType[6] = "energy.png"  
 tileType[7] = "powerplant.png"  
 tileType[8] = "commerce.png"  
 tileType[9] = "bank.png"  
 tileType[10] = "lab.png"  
 tileType[11] = "market.png"  
 tileType[12] = "barrack.png"  
 tileType[13] = "tavern.png"  
 tileType[14] = "monument.png"  
 tileType[15] = "kapital.png"  
  
 -- populate playerColor  
 local playerColor = {}  
 playerColor[1] = {255,255,255}  
 playerColor[2] = {157,173,249}  
 playerColor[3] = {255,115,115}  
 playerColor[4] = {122,217,114}  
 playerColor[5] = {252,254,117}  
  
 local typeId = 0  
 local defense = 0  
  
 -- populate scrollview  
 local grid = {}  
 for row = 1, 10 do -- 10 rows  
 grid[row] = {}  
 for col = 1, 10 do -- 10 columns  
 grid[row][col] = display.newGroup()  
  
 -- Create tile and insert it into grid  
 grid[row][col].tile = display.newRoundedRect(0,0,95,95,7)  
 grid[row][col].tile:setReferencePoint(display.TopLeftReferencePoint)  
 grid[row][col]:insert(grid[row][col].tile)  
  
 if mapTable[row][col][1] == 0 then  
 grid[row][col].tile.isVisible = false  
 end  
  
 -- Fill tile with correct player color  
 grid[row][col].tile:setFillColor( unpack( playerColor[mapTable[row][col][2] ] ) )  
 -- "unpack()" converts {x,y,z} into x,y,z  
  
 -- Paint a stoke line  
 grid[row][col].tile:setStrokeColor(140, 140, 140)  
 grid[row][col].tile.strokeWidth = 2  
  
 -- Insert Defense value into grid array  
 grid[row][col].defense = mapTable[row][col][3]  
  
 -- Insert Shield icon into grid array and hide it  
 grid[row][col].shield = display.newImageRect("shield.png", 95, 95)  
 grid[row][col].shield:setReferencePoint(display.TopLeftReferencePoint)  
 grid[row][col]:insert(grid[row][col].shield)  
 if mapTable[row][col][3] \<= 0 then  
 grid[row][col].shield.isVisible = false  
 end  
  
 grid[row][col].shield.x = grid[row][col].tile.x  
 grid[row][col].shield.y = grid[row][col].tile.y  
  
 -- Insert defenseText and hide it  
 defense = mapTable[row][col][3]  
 grid[row][col].defenseText = display.newText(defense, 12, 8, "Helvetica", 24)  
 grid[row][col]:insert(grid[row][col].defenseText)  
 if mapTable[row][col][3] \<= 0 then  
 grid[row][col].defenseText.isVisible = false   
 end  
  
 -- Insert structure icons into grid array  
 typeId = mapTable[row][col][1]  
 if typeId \>= 1 then  
 grid[row][col].structure = display.newImageRect( tileType[typeId], 95, 95 )  
 grid[row][col].structure:setReferencePoint(display.TopLeftReferencePoint)  
 grid[row][col]:insert(grid[row][col].structure)  
  
 grid[row][col].structure.x = grid[row][col].tile.x  
 grid[row][col].structure.y = grid[row][col].tile.y  
 end   
 -- Position tile  
 grid[row][col].x = (col - 1) \* 100  
 grid[row][col].y = (row - 1) \* 100   
  
 -- Print confirmation  
 print("Y = " .. row .. " X = " .. col .. " and Tile Type = " .. typeId)  
  
  
 -- Insert grid into scrollView  
 scrollView:insert( grid[row][col] )  

I edited this code to reflect changes I’ve made, and put it inside the correct tags [import]uid: 170004 topic_id: 34899 reply_id: 138899[/import]

The basic gist is that scrollView is swallows certain parts of the touch listener in order to function (namely, the “ended” phase). You use :takeFocus() to temporarily snatch the event away. Usually, the code looks a little bit like this:

[code]function tile.touch(self, event)
if event.phase == “began” then
self:takeFocus( event ) – now the only thing the player can influence is the tile
self.isFocus = true
– This next line ensures you had focus before you do anything.
– Otherwise it’s possible to trigger this line by touching something, dragging your finger over the
– button, and then lifting your finger
elseif event.phase == “ended” and self.isFocus then
display.getCurrentStage():setFocus(nil) –
self.isFocus = false
doSomething() – whatever action you want to happen
end
return true
end

tile:addEventListener(“touch”)[/code]

Now as I said, I think scrollView swallows “ended” but this is still a good general approach to ensure that only the thing you touched in the first place is affected.
[import]uid: 41884 topic_id: 34899 reply_id: 139181[/import]

Hi Richard
For some reason I couldn’t get your setup to work for me.

I finally got this working

-- function to handle touch event tile selections  
 local function onTileTap( event )  
 local s = event.target -- reference to onTileTouch object  
  
 -- r and c are references I added to tile so that I could have a direct way to access mapTable with   
 print( "Tile taped located at row " .. s.r .. " and col " .. s.c)  
 event.target:setStrokeColor(250, 250, 250)  
-- TouchListener  
 grid[row][col].tile:addEventListener("tap", onTileTap)  

That gives me info about the target that was tapped and changes the stroke color as an indicator of selection. Scrolling is working great, too.

Trouble is, I need to change the strokecolor back when a new tile is tapped.
If I add a .isSelected to grid[row][col].tile, is there any way for me access and loop through the display objects in search of one with .isSelected with a value of true so that I can then make it false and set the stoke color to normal before applying the selection change to the new selection?
[import]uid: 170004 topic_id: 34899 reply_id: 139209[/import]

Tap is a really troublesome beast to use because it only detects a single state (“did you press?”). You could convert the touch code to work mostly the same as what you have there.

But yeah, you absolutely can search through. It gets slow if you have too many tiles but from what I vaguely remember of your scope it’s probably not a big deal.

I would write a search function and then call it from within the tap listener.

[code]local function searchforActive()
for r = 1, #grid do
for c = 1, grid[r].numChildren do – # doesn’t work for displayGroups
if grid[r][c].tile.isSelected then
grid[r][c].tile.isSelected = false
– change stroke color here
end
end
end
end

– So then in your touch code
searchforActive()
event.target.isSelected = true
event.target:setStrokeColor(250,250,250) [/code]

Something like that. If you are sure there is only one .isSelected to remove you could add break after the = false line to break out of the loop and stop looking further. [import]uid: 41884 topic_id: 34899 reply_id: 139215[/import]

What you need is this wonderful thing called a display group (see the api: display.newGroup(). It is a container of other display objects. From your description, you really only need 5 tiles (one for each color … and could probably get away with just one and use the :setFillColor() method to color them), a graphic for your shield logo (mostly transparent) and then use a display.newText() API to draw the numbers. Then you could do something like this:

local grid = {}  
for row = 1, 50 do -- assume 50 rows  
 grid[row] = {}  
 for col = 1, 50 do -- assume 50 columns  
 grid[row][col] = display.newGroup()  
 grid[row][col].tile = display.newImageRect("tile.png",32,32)  
 grid[row][col]:insert(grid[row][col].tile  
 grid[row][col].tile:setFillColor(255,255,255)  
 grid[row][col].color = "white" -- for your reference  
 grid[row][col].count = 0  
 grid[row][col].shield = display.newImageRect("shield.png", 32, 32)  
 grid[row][col]:insert(grid[row][col].shield)  
 grid[row][col].shield.isVisible = false -- don't show it until count its greater than 0  
 grid[row][col].countText = display.newText("", 0, 0, "Helvetica", 12)  
 grid[row][col]:insert(countText)  
 grid[row][col].countText.isVisible = false -- hide it too for now  
 --   
 -- at this point you should have a complete group that you can position as necessary  
 -- and insert into your scrollView  
 end  
end  

First, I just wrote that off the top of my head, so it could be full of errors…

Secondly, it’s using a technique where you can add any attribute you want to an object and that attribute follows the object around. This might have been easier to understand if we used different tables to track the group, the tile, the shield and the count separately, but this packs everything into one table which each cell is the group. Objects like that are just big Lua tables and you can freely add other members to it, so in this case, it is the group, but it also is a pointer to the tile, a pointer to the shield, etc. as well as various attributes to track.

I’m assuming you would want some touch/tap handler, then you would just add that to the group, when the handler receives the tap, the event.target variable will be the group and all the groups components are nicely packaged together for you (event.target.tile, event.target.countText, etc.) and you could set your fill color, toggle the visibility of the shield/text, etc.
[import]uid: 199310 topic_id: 34899 reply_id: 138697[/import]

Hi Rob
Thank you for the awesome supper fast response.
This really helps me get my head around my problem, and expands my understanding of what you can do with display groups.

I’m sure I can use that structure and modify it for me needs, like making the tiles bigger and the shield graphic in the upper left corner of the tile, etc…

I have a number of structure types that will have a symbol displayed on the lower right corner of the tile as well. Would you set up a 14 branch if statement that checks the structure value to apply the correct image file to that tile, or is there a better way?

[import]uid: 170004 topic_id: 34899 reply_id: 138704[/import]

You would just make a reference table instead.

local tileSymbols = {} tileSymbols[1] = "castle.png" tileSymbols[2] = "hut.png" tileSymbols[3] = "chamberPot.png" -- etc...

Using that, you can loop through all the tiles in a for loop (as Rob showed you) and either create images using those references or destroy the old and create a new one. The basic advantage here is that the reference table allows you to use variables, so, say you give your tile a symbol id…

tile = display.newImage("cooltile.png") tile.id = 17

Then you could later on find the symbol image to set by using

symbolImage = display.newImage( tileSymbols[tile.id] )

Basically, tileSymbols is a table, and tile.id is a variable representing a number, so it works the same way as a number. It’s a bit harder to read but it’s much easier to maintain than doing huge if/else code. [import]uid: 41884 topic_id: 34899 reply_id: 138709[/import]

Maybe some day I’ll know enough that I can aid you in some way Richard :wink:

I had to make grid a global to get that to work, I had tried to make something pretty similar work without success, but it’s working now.

Should I be worried about the overhead involved in grid and mapTable both being globals? I have a 100 tiles, but a large map my have 400 tiles.

If you think it’ll be to much of a memory hog, I can probably make mapTable a local and load the info when needed, but I don’t know which is worse on performance, repetitive loading of a json file into mapTable or the extra group of globals. [import]uid: 170004 topic_id: 34899 reply_id: 139235[/import]

thanks Richard [import]uid: 170004 topic_id: 34899 reply_id: 138718[/import]

You probably didn’t have to make it a global; you just need to follow the rules of scope in Lua:

  1. You can only address what is behind you

[code]local y = 2

local function findX()
print("x = ", x) – nil
print("y = ", y) – 2
end

local x = 1
findX()[/code]

  1. Use forward addressing to get past this in many cases

[code]local x – predeclare so the variable exists!

local function findX() – the function doesn’t care what the value is until it’s executed
print("x = ", x) – 1
end

x = 1
findX() – executed here, AFTER x is given a value[/code]

  1. If you absolutely need to use globals, try to localize them.

[code]_G.bigTable = {} – lots of table stuff

local function manipulateTable()
local bigTable = bigTable – now you can access it inside the function locally
end[/code]

All of this being said in a slower pace game I’m not to worried about the overhead unless you go 1000+ tiles or need to constantly scroll the screen at a high speed. But localizing can often pay off for performance. [import]uid: 41884 topic_id: 34899 reply_id: 139246[/import]

  1. Okay, first up, when you post code, please encapsulate in the html tags: < code> < / code> (minus the spaces inside the tags) That way it looks like code and it’s easier for people here to help. :slight_smile:

  2. You can use a reference table for your color coding as well. Here’s how it works:

[code] – Put the table outside of the loop, maybe at the beginning of the lua file somewhere.
local playerColor = {}
playerColor[1] = {255,255,255}
playerColor[2] = {157,173,249}
playerColor[3] = {255,115,115}
playerColor[4] = {122,217,114}
playerColor[5] = {252,254,117}

– Then, later, when you’re in the loop
grid[row][col].tile:setFillColor( unpack( playerColor[mapTable[row][col][2] ] ) )
– “unpack()” converts {x,y,z} into x,y,z
[/code]

  1. At a glance, it looks like your positioning method puts the centerpoint of tile 1,1 at x0,y0, but it couldn’t hurt to manually set x/y for the structure during creation.

grid[row][col].structure = display.newImageRect( tileType[typeId], 96, 96 ) grid[row][col]:insert(grid[row][col].structure) grid[row][col].structure.x = grid[row][col].tile.x grid[row][col].structure.y = grid[row][col].tile.y

That just makes sure their internal x/y match up. From there you can change reference points as needed to tweak it… [import]uid: 41884 topic_id: 34899 reply_id: 138998[/import]

Thanks again Richard.

I will use < code> < / code> from now on, just didn’t know how, but now I do.

Also, that solved my issue with alignment. I will be using your suggestion for using a reference table for player color also. [import]uid: 170004 topic_id: 34899 reply_id: 139069[/import]