Custom map loading technique [Solved]

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
bobbymcbobface
Citizen
Posts: 78
Joined: Tue Jun 04, 2019 8:31 pm

Custom map loading technique [Solved]

Post by bobbymcbobface »

Hay i was wondering if it's possible to create a custom map loading system
how this would work:
it would load a map styled like this

#############
#############
#############
#############

red = on screen
black = off screen

if the player moves to the left 1 tile would remove behind him and one would load ahead of him like this

~############+
~############+
~############+
~############+

~ = deleted
+ = added

and if he moves back vice versa is there any way to do this and store the data so if the player goes back the same tiles will load back in

EDIT: also is it possible to make it so if the player reaches the border of the top of the map the players co-ordinates change to the bottom of the map

-thankyou :)
Last edited by bobbymcbobface on Sun Dec 01, 2019 10:54 am, edited 1 time in total.
I make games that run on potatoes :P
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Custom map loading technique

Post by raidho36 »

It's possible to create literally anything - that's what programming is for.

Unless your map takes several gigabytes of memory, you should keep the entire thing loaded. Frustum culling takes care of the "not rendering offscreen stuff" problem, and the same technique allows checking logic against specific objects on the map based on coordinates rather than against every single one of them.

To answer your questions, you can do the rolling map update by simply checking if an offscreen row/column is far enough to be removed, and in such case do so (and create a row/column on the opposite side at the same time, because both operations are triggered by the same condition). And to implement wrapping, you simply check if a coordinate exceeds certain limits, and then set it to such value that the entity will appear on the other side - usually by adding/subtracting map width or height as appropriate.
sphyrth
Party member
Posts: 260
Joined: Mon Jul 07, 2014 11:04 am
Contact:

Re: Custom map loading technique

Post by sphyrth »

Tutorial: Efficient Tile-based Scrolling seems to cover that one up.

I don't really remember if it "deletes" tiles outside of the screen (because learning about Spritebatch was enough for me), but I assume that it also does that.
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Custom map loading technique

Post by zorg »

Do note that frustum culling doesn't include your own logic things being applied to things outside the current viewport, as in, the part of your map that's currently visible in the window, so that's another thing you might want to implement, or not, depending on what your goals are.

That said, most games i know of don't partition the map into singular tiles, rather into larger clusters of them called chunks (e.g. minecraft), or into "complete" areas that need to be transitioned from and to, (e.g. earlier pokemon games), since it's faster to load the data in that way, and you don't need to play "lets killkeep the user's ssd/harddrive busy" by continuously loading in rows/columns of tiles as the player moves around the world.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
bobbymcbobface
Citizen
Posts: 78
Joined: Tue Jun 04, 2019 8:31 pm

Re: Custom map loading technique

Post by bobbymcbobface »

Ok here's what I've got so far:
tileset.png
tileset.png (114.42 KiB) Viewed 9598 times

Code: Select all

local map -- stores tiledata
local mapWidth, mapHeight -- width and height in tiles

local mapX, mapY -- view x,y in tiles. can be a fractional value like 3.25.

local tilesDisplayWidth, tilesDisplayHeight -- number of tiles to show
local zoomX, zoomY

local tilesetImage
local tileSize -- size of tiles in pixels
local tileQuads = {} -- parts of the tileset used for different tiles
local tilesetSprite

function love.load()
  setupMap()
  setupMapView()
  setupTileset()
end

function setupMap()
  mapWidth = 2000  ------------------------------------------------------------
  mapHeight = 2000 -----------------------------------------------------------
  
  map = {}
  for x=0,mapWidth do
    map[x] = {}
    for y=0,mapHeight do
      map[x][y] = love.math.random(0,3)
    end
  end
end

function setupMapView()
  mapX = 1
  mapY = 1
  tilesDisplayWidth = 26
  tilesDisplayHeight = 20
 
  zoomX = 1
  zoomY = 1
end

function setupTileset()
  tilesetImage = love.graphics.newImage( "tileset.png" )
  tilesetImage:setFilter("nearest", "linear") -- this "linear filter" removes some artifacts if we were to scale the tiles
  tileSize = 32
 
  -- grass
  tileQuads[0] = love.graphics.newQuad(2 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
  -- kitchen floor tile
  tileQuads[1] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
  -- parquet flooring
  tileQuads[2] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
  -- middle of red carpet
  tileQuads[3] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
 
  tilesetBatch = love.graphics.newSpriteBatch(tilesetImage, tilesDisplayWidth * tilesDisplayHeight)
 
  updateTilesetBatch()
end

function updateTilesetBatch()
  tilesetBatch:clear()
  for x=0, tilesDisplayWidth-1 do
    for y=0, tilesDisplayHeight-1 do
      tilesetBatch:add(tileQuads[map[x+math.floor(mapX)][y+math.floor(mapY)]],
        x*tileSize, y*tileSize)
    end
  end
  tilesetBatch:flush()
  
  if mapHeight == 0 or mapHeight == 2000 then
    mapHeight = 0
  end
end

-- central function for moving the map
function moveMap(dx, dy)
  oldMapX = mapX
  oldMapY = mapY
  mapX = math.max(math.min(mapX + dx, mapWidth - tilesDisplayWidth), 1)
  mapY = math.max(math.min(mapY + dy, mapHeight - tilesDisplayHeight), 1)
  -- only update if we actually moved
  if math.floor(mapX) ~= math.floor(oldMapX) or math.floor(mapY) ~= math.floor(oldMapY) then
    updateTilesetBatch()
  end
end

function love.update(dt)
  if love.keyboard.isDown("up")  then
    moveMap(0, -0.2 * tileSize * dt)
  end
  if love.keyboard.isDown("down")  then
    moveMap(0, 0.2 * tileSize * dt)
  end
  if love.keyboard.isDown("left")  then
    moveMap(-0.2 * tileSize * dt, 0)
  end
  if love.keyboard.isDown("right")  then
    moveMap(0.2 * tileSize * dt, 0)
  end
end

function love.draw()
  love.graphics.draw(tilesetBatch,
    math.floor(-zoomX*(mapX%1)*tileSize), math.floor(-zoomY*(mapY%1)*tileSize),
    0, zoomX, zoomY)
  love.graphics.print("FPS: "..love.timer.getFPS(), 10, 20)
end
2 questions though
1. how can i remove the weird black square moving across the screen
2. can anyone edit my code so it delete's any tiles left behind and adds one ahead (so basically my previous example) because i'm stuck on how i would achieve this

btw thank you for replying so quick this forum is a lot better than stack overflow

edit: as well as that would i need to store previous tile or hide them for if the player wants to go back and since there's a lot of trees would the physics handling cause lag?

editX2 (sorry for so many edits i'm just eager to get the right result): basing of zorg's comment how can i load in chunks of a map i.e has anyone got an example code they could show me

also for anyone who wants to know what my goals are:

i need to create a random map that the player can roam around and structures such as tombs will appear with weapons inside and these will be used to fight off enemies that are generated across the map at random and if the player exceeds the boarder he will loop back to the opposite side making the map seem "infinite"

thankyou for your help!
I make games that run on potatoes :P
sphyrth
Party member
Posts: 260
Joined: Mon Jul 07, 2014 11:04 am
Contact:

Re: Custom map loading technique

Post by sphyrth »

Okay, I think I have to review the tutorial to know what you are talking about (Weird Moving Square).

When it comes to what zorg is talking about, it's a technique used by a lot of Adventure RPGs. I'm not sure how many games have been made, but they are in the Games and Creations section.

In your particular case, I'm currently trying to clone this game. It's similar to what you're trying to accomplish except for the Random Generating part. Getting the maths done is still rattling my brain. Maybe I'll try to share them when it's basically done.
sphyrth
Party member
Posts: 260
Joined: Mon Jul 07, 2014 11:04 am
Contact:

Re: Custom map loading technique

Post by sphyrth »

Okay, try this code. I stripped it down as much as I could to leave out the unimportant bits. I personally don't recommend depending on it as it's for discussion purposes (meaning that it's very rough and therefore inefficient for serious game prototyping).

I assume this is how you want it to work.

Code: Select all

local tilesetImage
local tileSize = 32 -- size of tiles in pixels
local tileQuads = {} -- parts of the tileset used for different tiles
local tilesetSprite

local map -- stores tiledata
local mapX, mapY -- view x,y in tiles. can be a fractional value like 3.25.
local mapWidth, mapHeight -- width and height in tiles

function love.load()
  setupMap()
  setupTileset()
  updateTilesetBatch()
end

function setupMap()
  -- We only need a tiny map for this example
  mapWidth = 15
  mapHeight = 15
  
  --Leaving a space on the right and topsides so we know if the tiles are being "deleted"
  mapX = 32
  mapY = 32
  
  map = {}
  for x=0, mapWidth do
    map[x] = {}
    for y=0, mapHeight do
      map[x][y] = { --Let's turn this into a table that stores 3 things:
        tile = love.math.random(0,3),
        x = x * tileSize,
        y = y * tileSize
      }
    end
  end
end

function setupTileset()
  tilesetImage = love.graphics.newImage( "tileset.png" )
  tilesetImage:setFilter("nearest", "linear")
 
  -- Tree
  tileQuads[0] = love.graphics.newQuad(2 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
  -- Grass
  tileQuads[1] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
  -- Grass with Stone
  tileQuads[2] = love.graphics.newQuad(1 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
  -- Grass (Yup the same Grass as 1
  tileQuads[3] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
    tilesetImage:getWidth(), tilesetImage:getHeight())
    
  tilesetBatch = love.graphics.newSpriteBatch(tilesetImage, mapWidth * mapHeight)
end

function updateTilesetBatch()
  tilesetBatch:clear()
  for x=1, mapWidth do
    for y=1, mapHeight do
      tilesetBatch:add(tileQuads[map[x][y].tile], map[x][y].x, map[x][y].y)
    end
  end
  tilesetBatch:flush()
end

-- central function for moving the map
function moveMap(dx, dy)
  for x = 1, mapWidth do
    for y = 1, mapHeight do
      map[x][y].x = map[x][y].x + dx
      map[x][y].y = map[x][y].y + dy
      
      -- Okay so I'm not "deleting" the tiles, but transferring their x and y coordinates
      
      --X
      if map[x][y].x < mapX - (tileSize / 2) then
         map[x][y].x = map[x][y].x + (tileSize * mapWidth)
      end
      if map[x][y].x > mapX + (mapWidth * tileSize) - (tileSize / 2) then
         map[x][y].x = map[x][y].x - (tileSize * mapWidth)
      end
      
      --Y
      if map[x][y].y < mapY - (tileSize / 2) then
         map[x][y].y = map[x][y].y + (tileSize * mapHeight)
      end
      if map[x][y].y > mapY + (mapHeight * tileSize) - (tileSize / 2) then
         map[x][y].y = map[x][y].y - (tileSize * mapHeight)
      end
    end
  end
   updateTilesetBatch()
end

function love.update(dt)
  if love.keyboard.isDown("up")  then
    moveMap(0, - 50 * dt)
  end
  if love.keyboard.isDown("down")  then
    moveMap(0, 50 * dt)
  end
  if love.keyboard.isDown("left")  then
    moveMap(-50 * dt, 0)
  end
  if love.keyboard.isDown("right")  then
    moveMap(50 * dt, 0)
  end
end

function love.draw()
  love.graphics.draw(tilesetBatch)
  love.graphics.print("FPS: "..love.timer.getFPS(), 10, 20)
end
User avatar
bobbymcbobface
Citizen
Posts: 78
Joined: Tue Jun 04, 2019 8:31 pm

Re: Custom map loading technique

Post by bobbymcbobface »

thank you sphyrth although the code is a bit laggy it has boosted me a whole lot closer to my goal thank you :)
I will still leave this topic unsolved due to the fact i still want to ask some questions on this topic later on and resolve some more problems but thank you for all your help so far and I'll probably be back asking for help soon :)

edit (yeh i'm back already :P ) : how can i fix the lines going across the screen when travelling up?
I make games that run on potatoes :P
sphyrth
Party member
Posts: 260
Joined: Mon Jul 07, 2014 11:04 am
Contact:

Re: Custom map loading technique

Post by sphyrth »

I'm not really sure. I also saw that effect and I assumed that it's because of the code's quirkiness. Maybe moving the image up and down behaves differently with moving it left and right. But then again, the original Tutorial doesn't have that problem. Anyway, that's my limit. I might try to solve that later on.
User avatar
bobbymcbobface
Citizen
Posts: 78
Joined: Tue Jun 04, 2019 8:31 pm

Re: Custom map loading technique

Post by bobbymcbobface »

sphyrth wrote: Mon Jul 08, 2019 10:24 pm Maybe moving the image up and down behaves differently with moving it left and right. But then again, the original Tutorial doesn't have that problem.
It might have been due to my graphics card sometimes it has bad days or it could be a pixels difference between the tilset images height and width - I'll get back to you when I solve it
I make games that run on potatoes :P
Post Reply

Who is online

Users browsing this forum: No registered users and 10 guests