Log In  


I'm trying to make a thexder demake for pico8, and this is an mock up image.

I'm trying to use 3x3 tiles for the map with thexder being 9X12 and enemies being 6x6, instead of 8x8 tiles as they are too big for thexder's gameplay where the player can transform as a jet to get into narrow 3 block tunnels.

Since pico8's map can only support 8x8 I need to make my own mapping to use smaller 3x3 grids.

tilex = {}
tiley = {}
for i=1,252 do
add(tilex,1)
end
for i=1,126 do
add(tiley,1)
end

I tried making two arrays of each x and y variables, and the tiles are suppossed to be drawn at the position where both tilex[?] and tiley[?] equals 1.

for i=1,42 do
for i2=1,42 do
local xck=(player.x-20+i)+1
local yck=(player.y-20+i2)+1
if xck>0 and yck>0 and xck<=#tilex and yck<=#tiley and tilex[xck]==1 and tiley[yck]==1 then
sspr(0,24,3,3,-2+i3,-2+i23)
end
end
end

the entire screen has space for 42X42=1764 tiles, but displaying all those tiles with sspr like adove code makes a bad result. it already uses half of the cpu that I imagine it will be really unplayable when players, enemies, and other systems are all thrown in.

I really have no idea efficiently utilizing 3x3 tile mapping, can anyone give me some hint to make it as efficient as using 8x8 tiles in pico8's default mapping ?



you need a better data structure for your map:

  • why not fully mimicking the map data structure? eg a flat list of bytes?
  • if too complex, a table of table will do
  • you need a structure that can directly address a map starting point (both 1d array and flat list will do)

The key aspect is finding the top/left corner of the map:

— assumes x/y are in map units
— ensure mx/my are within bounds
mx=mid(player.x-20,0,map_width-20)\1
my=mid(player.y-20,0,map_height-40)\1

next find corner (using a 1d table):

local offset=mx+my*map_width
for j=0,39 do
 for j=0,39
 local tile=tiles[offset]
 sspr(tx,ty,3,3,i*3,j*3)
 — next tile
 offset+=1
end
— next line
offset+=40
end

note: this will be slower than regular map() call as this is calling sspr much more

note: cannot provide a working example right now!


Would using flat list of bytes be something like this ?

Tiles[?]= "XXXYYYB"

First 3 letters for x coord, next 3 for y, and last for checking blocked or empty.


you can create a table in a table

field={}
for y=1,10 do
  field[y]={}
  for x=1,10 do
    field[y][x]=0 -- empty
  end
end
field[1][2] = 20 -- set column(y) 1 row(x) 2 to value 20

maybe this can be useful for you:

gamefield=[[
#############
#  #  #  #  #
# ### # #1# #
#  #  #  #  #
#############
]]

field={}
for l in all(split(gamefield,"\n")) do
  add(field,split(l,1))
end

height=#field
width=#field[1] -- when all lines have the equal length!

print(width .. " x " ..height)
print(field[3][10])

see discord ;)

and GPI answer is also good (except I find 0-based tables easier to work with in such case)


Now I understood how to properly store tiles in array theoretically, drawing optimization is the next issue as calling over 1000 sspr per frame is too slow.

I'll try just using spr to see if it's faster, and If I can't find any good ways I'll just use chonky 9x9 tiles to make both mapping and drawing much simpler as calling 196 sspr or spr will be a lot lighter.

Thexder gameplay can still work in 9x9 tiles,
even through the levels will look much blockier.


@hijongpark, there is another way.

Back on the Apple ][ when Ultima ][ made the platform, I was intrigued to see how they could manipulate their maps so quickly. It was machine-language yes but it seemed to run a lot faster than other mapped games.

Turns out after closely examining what they did - is not redraw every single tile every single frame like Ultima 1. But keep track of every single tile in a 2nd array. So for instance if you had something like this. Let's say the dark gray is grass and the light is forest:

[24x24]

And you wanted this:

[24x24]

You would compare the two arrays and makes changes ONLY the tiles that changed like so:

[24x24]

Therefore only redrawing =2= tiles instead of =9=

This would also work for a scrolling map, for instance if you have just a few tiles changing in a 9x9 field, like forest, you would only update where they were and where they are going whereas the other tiles visually do not change, like grass, so therefore - you need not redraw them.


function _init()
tiles={}
player={
x=0,
y=0
}
for i=1,31752 do
add(tiles,rnd({1,0}))
end

end

function _update()

if btnp(0) then
player.x-=1
end
if btnp(1) then
player.x+=1
end
if btnp(2) then
player.y-=1
end
if btnp(3) then
player.y+=1
end

end

function _draw()
cls()

local ty=player.y-20

for i=0,41 do
local tx=player.x-20

for j=0,41 do
if tx>=1 and tx<=252 and ty>=1 and ty<=126 and tiles[tx+ty*252]==1 then
spr(0,1+j*3,1+i*3)
end
tx+=1
end

ty+=1

end

end

This is the 3x3 tile map example I have managed to implement. uses 3x3 tile drawn in 0 sprite number, and use arrows to scroll.

solving the 50% CPU usage for calling spr() 1764 times per frame is still a mystery.

dw817 // That looks good, but I can't grasp my head to make that, because I think you eventually need to check every 31752 arrays to compare the difference per frame...


1

Hi @hijongpark.

Well here now you also shouldn't draw tiles that aren't there. For instance your entire map may be 31752 tiles but how many tiles will be on the screen at a time ?

If you like I can write an example of the speedy tile-plotter that uses the method I talked about earlier. It's not that tricky, it's just a matter of doubling the memory used to keep track of what moved on the screen.


@dw817

The code I wrote displays 1764 tiles max, 42x42.

I'm not sure how will your method decrease the amount of spr calls for tiles, as it just tracks differences and don't drastically change the amount of tiles needed to draw.


you have multiple option but first is really to remove all maths from this core loop
all indices can be precomputed

second, you are drawing too many pixels, spr can take a fractional argument (3/8) to indicate sprite size

if not enough (agree that 41x41 spr calls is a lot), dw advice is valid - eg draw only needed tiles.
First frame:

  • draw map & copy to 0x8000

Runtime:

  • calculate offset with previous frame position (eg how much camera moved)
  • memcpy map to screen (shifting by y offset)
  • rebase gfx to screen
  • shift whole screeen with spr(0,-offsetx,0,16,16)
  • draw ‘missing’ tiles (eg screen borders)
  • copy screen to 0x8000

Tbh not sure this is going to cost less than 50% cpu 😜


Then I think the best solution will be :

  1. Squash thexder into 8x12 and 8x8 space somehow and use standard 8x8 tiles, althrough that means It can't shoot laser in exact center of the tile.

  2. Use 9x9 tile and only draw 196 tiles per screen.

Problem solved !!! It was actually very easy to put thexder in 8X12 and 8X8 sprites, so I can use standard 8x8 tiles now,

for laser firing positions I'll fire laser from two positions per frame when the jet is facing upward, downward, and diagonals as that will look cooler...


Hi @hijongpark.

Cart #map_3x3-3 | 2022-11-17 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

TO LOAD THIS CART in immediate mode, type: load #map_3x3

Here is code example I wrote demonstrating to write to your screen of 42x42 tiles in addition to only drawing the parts that need redrawing.

When running, press CTRL+P. Notice when there is no map activity that it runs at 17%. At maximum it runs at 19%.

Try working your way towards the center of the map and you will notice something. Suddenly the clicking representing the map changes stop. Why ? Because at this point you are moving without changing any tiles.

Same thing if you enter the water. Eventually it realizes no tiles are changing so none are drawn and the CPU usage goes down.

Press 🅾️ to swap between the normal view you have now and movement mode which shows only what tiles are changed by a red square.


thanks for the effort @dw817, I hope this will be helpful to my possible future projects or others.


UBetcha. Glad to help, @hijongpark. :)



[Please log in to post a comment]