[SOLVED] Help with map scrolling, jittery

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
manyshotsshort
Prole
Posts: 4
Joined: Mon May 27, 2013 10:20 pm

[SOLVED] Help with map scrolling, jittery

Post by manyshotsshort »

There are tons of things wrong with my code, but here is where I'm at.
I started with one of the smooth tile scrolling tutorials, which obviously scrolled awesomely. I tried to mod it such that instead of a fixed map size, I would have endless scrolling (and world generation) and that's where things went downhill fast.

User stuff is broken, just in there as placeholders. What I cant figure out is 2 things:
Why does the map jitter when moving left and right fairly quickly?
Why does the map offset so bad that you see black on the left? (Right is messed too, dont know why yet, and I think solving the first question will probably fix this too!)

If anyone can spot the obvious I would be very grateful!

Code: Select all


local player = {}
--hold player level objects
local layer
local tileQuads = {}
local screen = {}
--holds the info on what tiled objects the world has
local knownMap = {}
--spriteBatch for the level up to ground level and buildings.
local tileSet


--hack for now to turn white into a transparent color
function loadTransparent(imagePath, transR, transG, transB)
	 imageData = love.image.newImageData( imagePath )
	 function mapFunction(x, y, r, g, b, a)
			if r == transR and g == transG and b == transB then a = 0 end
			return r,g,b,a
	 end
	 imageData:mapPixel( mapFunction )
	 return love.graphics.newImage( imageData )
end

function getQuad(obj, x, y, tilesetImage)
	 obj.tile = love.graphics.newQuad(x * screen.tileSize, y * screen.tileSize, 
		screen.tileSize, screen.tileSize, tilesetImage:getWidth(), tilesetImage:getHeight())
end

--rounds method
function round(num) 
  if num >= 0 then
		return math.floor(num+.5) 
  else
		return math.ceil(num-.5) 
	end
end

function love.load()
	screen.width = 900
	screen.height = 650
	screen.tileSize = 32
	screen.tileDisplayWidth = math.floor(screen.width/screen.tileSize)
	screen.tileDisplayHeight = math.floor(screen.height/screen.tileSize)
	screen.posX = 0
	screen.posY = 0
	screen.lastMovedX = 0
	screen.lastMovedY = 0
	screen.tileOffsetX = 0
	screen.tileOffsetY = 0
	
	--set screen res
	love.window.setMode(screen.width,screen.height,{fullscreen=false,resizable=false})

	--setup map max tiles
	--map.width = 60
	--map.height = 60
	
	--load tileset
	tilesetImage = loadTransparent( "tileset.png",255,255,255 )
	tilesetImage:setFilter("nearest", "linear") -- this "linear filter" removes some artifacts if we were to scale the tiles
 
	for x = 1, 6 do
		 tileQuads[x] = {}
		 tileQuads[x].obstacle = false
		 tileQuads[x].removeable = true
	end
	
		-- grass
	getQuad(tileQuads[1],5,18,tilesetImage)
	--  grass flower
	getQuad(tileQuads[2],6,18,tilesetImage)
	-- grass rock
	getQuad(tileQuads[3],4,18,tilesetImage)
	tileQuads[2].obstacle = true
	-- gras pebble
	getQuad(tileQuads[4],5,19,tilesetImage)
	-- manhole -currently our 'player'
	getQuad(tileQuads[5],10,26,tilesetImage)
	--dirt
	getQuad(tileQuads[6],5,15,tilesetImage)

	--setup spritebatch
	tileSet = love.graphics.newSpriteBatch(tilesetImage,screen.tileDisplayWidth*screen.tileDisplayHeight,"dynamic");
	
	--setup initial layer, can support 1000 objects
	layer = love.graphics.newSpriteBatch(tilesetImage,1000,"dynamic");
	
	--setup player initial position
	player.x = math.floor(screen.tileDisplayWidth/2)
	player.y = math.floor(screen.tileDisplayHeight/2)
	player.rate = 0.2
	
	updateLayer()
	updateMap()
end

--retuns obj indicating tileQuad number, depth (0 is ground, -1 is under, 1 is over)
function getTile(x,y)
	--print("getTile for: "..x.." "..y)
	-- x doesnt exist, create
	if not knownMap[x] then
		knownMap[x] = {}
	end
	
	-- y doesnt exist, create
	if not knownMap[x][y] then
		knownMap[x][y] = {}
		knownMap[x][y].tile = math.random(1,3)
		knownMap[x][y].depth = 0
		--print("new Tile at: "..x.." "..y)
	end
	return knownMap[x][y]
end

function updateMap()
	print("update map")
	tileSet:clear()
	for x = 0, screen.tileDisplayWidth + 1 do
		for y = 0, screen.tileDisplayHeight + 1 do
				--if x == 0 then
					--print(x.." "..screen.posX.." "..round(x+screen.posX).." "..x*screen.tileSize)
				--end			--print(x+math.floor(screen.posX))
			tileSet:add(tileQuads[getTile(round(x+screen.posX),math.floor(screen.posY)+y).tile].tile,
				x*screen.tileSize,y*screen.tileSize)
		end
	end
end

function updateLayer()
	layer:clear()
	layer:add(tileQuads[5].tile,player.x*screen.tileSize,player.y*screen.tileSize)
end

--so generally the player will be in the middle of the screen
--we can calculate when to move the map vs the screen.
function move(dx,dy)
	--calc new position of player
	x = player.x + dx
	y = player.y + dy

	-- TODO: collision detection goes here

	player.x = x
	player.y = y
	moveMap(dx,dy)
end

function moveMap(dx,dy)
	oldX = screen.posX
	oldY = screen.posY
	screen.posX = screen.posX + dx
	screen.posY = screen.posY + dy
	
	--print(screen.posX..screen.posY)
	screen.tileOffsetX =  round((1-screen.posX%1)*screen.tileSize)
	print(screen.posX.." "..(1-screen.posX%1).." "..(screen.tileSize*(1-screen.posX%1)))
	print("tileSet will render x starting at: "..screen.tileOffsetX)
	print("moveMap: "..screen.posX.." "..screen.lastMovedX.." "..screen.posX-screen.lastMovedX.." "..math.abs(screen.posX-screen.lastMovedX))
	
	--update tileSet only if we moved x or y by a whole unit from lastMoved
	if math.abs(screen.posX - screen.lastMovedX) >= 1 then
		screen.lastMovedX = round(screen.posX)
		--screen.tileOffsetX = 0
		updateMap()
	elseif math.abs(screen.posY-screen.lastMovedY) >= 1 then
		screen.lastMovedY = round(screen.posY)
		updateMap()
	end
end


function love.update(dt)
	if love.keyboard.isDown("up")  then
		move(0, -player.rate * screen.tileSize * dt)
	end
	if love.keyboard.isDown("down")  then
		move(0, player.rate * screen.tileSize * dt)
	end
	if love.keyboard.isDown("left")  then
		move(-player.rate * screen.tileSize * dt, 0)
	end
	if love.keyboard.isDown("right")  then
		move(player.rate * screen.tileSize * dt, 0)
	end
end

function love.draw()
	love.graphics.draw(tileSet,screen.tileOffsetX,round(-(screen.posY%1)*screen.tileSize),0,1,1)

	--love.graphics.draw(layer,(screen.posX%1)*screen.tileSize,(screen.posY%1)*screen.tileSize,0, 1,1)
	love.graphics.draw(layer,(screen.posX%1),(screen.posY%1),0, 1,1)

	love.graphics.print("FPS: "..love.timer.getFPS(), 10, 20)
	love.graphics.print("Player: "..player.x.." "..player.y, 10, 30)
	love.graphics.print("SPOS: "..screen.posX.." "..screen.posY, 10, 40)
	love.graphics.print("offset: "..screen.tileOffsetX.." "..round(-(screen.posY%1)*screen.tileSize),10,50)
	love.graphics.print("Mod: "..screen.posX%1, 10, 60)
end

function love.keypressed(key)
	print(key)
	 if key == 'escape' then
		 love.event.push("quit")
	 end
end
Last edited by manyshotsshort on Mon Jan 06, 2014 2:13 am, edited 1 time in total.
User avatar
CRxTRDude
Prole
Posts: 41
Joined: Sun Dec 15, 2013 3:03 am
Location: Some island in the archipelago

Re: Help with map scrolling, jittery

Post by CRxTRDude »

If only you shared a .love file, that would be very helpful. (You got files declared in your snippet that I don't think would run without certain files [eg. Tileset])
~ CRxTRDude || Will be out of touch for a wee longer than expected. Will keep in touch with soon enough. Sorry bout that.
Merlin_ua
Prole
Posts: 4
Joined: Fri Jun 21, 2013 7:34 am

Re: Help with map scrolling, jittery

Post by Merlin_ua »

First of all, +1 to what CRxTRDude said. I had to put a random tile set in in order to make it run.

Second thing: the strange vertical artifact with half-full rightmost column is not related to anything else, it is just that your "tileSet" is too small. You initialize it for tileDisplayWidth * tileDisplayHeight elements, but then you put (tileDisplayWidth + 2) * (tileDisplayHeight + 2) elements in (updateMap function).

Lastly, your math was off in a few places. I guess, you were trying to fix things by changing it here and there - now it is inconsistent and incorrect in many places. I really suggest figuring everything out on a peace of paper and then rewriting the whole thing. I will give a few steps below, that I did to fix scrolling in horizontal direction only. I hope it will help you to figure out the situation:

1. Your heuristic for doing updateMap only if we moved far enough is wrong. Consider the following situation: posX was 0.1 and then it became -0.1 Two values are only 0.2 apart, but we still need to updateMap because a new column of sprites showed up. The easiest (not the most elegant) fix is:

Code: Select all

if math.abs(math.floor(screen.posX) - screen.lastMovedX) >= 1 then
      screen.lastMovedX = math.floor(screen.posX)
2. In updateMap the tile you get for a given position should always round down, because your position represents top left corner, not the sprite middle. I suggest:

Code: Select all

tileSet:add(tileQuads[getTile(math.floor(x+screen.posX)
3. Your tileOffsetX calculation is incorrect. Note that because you do "1 - screen.posX % 1" it will always be positive. In reality, you want it almost always to be negative, because first column of sprites needs to start at the "invisible" part of the screen to the left. If you have positive tileOffsetX - you will have a stripe of blackness to the left, which is exactly what you observe. I suggest:

Code: Select all

screen.tileOffsetX =  round((-(screen.posX%1))*screen.tileSize)
Hope this helps :-)
manyshotsshort
Prole
Posts: 4
Joined: Mon May 27, 2013 10:20 pm

Re: Help with map scrolling, jittery

Post by manyshotsshort »

Thank you all for the replies! Yes next time I will definitely paste a love file, as soon as I figure out how to make one. :)

Merlin, your spot on about everything. I had just about fixed things myself by getting out the graph paper and really trying to go step by step through this. I cant seem do to what looks like simple math in my head.

SpriteBatch is fixed, +2 now like you said makes everything fit just right. Can you believe thats what I was still stuck on? :D

For the updateMap checking I did something a little different:

Code: Select all

	screen.lastTileOffsetX = screen.tileOffsetX
	screen.tileOffsetX =  round(-1*(screen.posX%1)*screen.tileSize)
	screen.lastTileOffsetY = screen.tileOffsetY
	screen.tileOffsetY =  round(-1*(screen.posY%1)*screen.tileSize)
	
	--update tileSet only if our offset shifted enough to want to render a new tile
	if math.abs(math.abs(screen.lastTileOffsetX) - math.abs(screen.tileOffsetX)) > (screen.tileSize/2) then
		screen.lastMovedX = round(screen.posX)
		updateMap()
	elseif math.abs(math.abs(screen.lastTileOffsetY) - math.abs(screen.tileOffsetY)) > (screen.tileSize/2) then
		screen.lastMovedY = round(screen.posY)
		updateMap()
	end
I like yours a lot better so I am going to try that out now.

Did exactly like you said inside updateMap, works much better. I dont even know why I used round anymore.

And as you can see above, I did figure out that it was always positive when I did not want that. I think I got confused because the example I was starting from was all positive based and I think the math assumed as such.


Thanks again, this is turning out pretty fun!
User avatar
bdjnk
Citizen
Posts: 81
Joined: Wed Jul 03, 2013 11:44 pm

Re: Help with map scrolling, jittery

Post by bdjnk »

manyshotsshort wrote:Yes next time I will definitely paste a love file, as soon as I figure out how to make one. :)
See https://www.love2d.org/wiki/Game_Distribution for how to make a love file. There's also a small how-to as the first post in this section (Support and Development).
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 4 guests