Scrolling Tilemap Woes

Monkey Forums/Monkey Programming/Scrolling Tilemap Woes

zoqfotpik(Posted 2013) [#1]
A smoothly scrolling tilemap has been causing me issues for some while now.

Essentially, the following code produces jerky movement and I cannot for the life of me see why. The array map1 is an array 32 by 1000 in size. Annotations are in the code below:

	Method DrawMap:Void()
		Local mapsquare:int
		For Local i%=0 To 31
			For Local j%=0 To 24
' we begin at the bottom of the 1000-long array and as the map scrolls
' upward the y (j) portion of the array index decreases.  Ticks/10 is
' simply ticks divided by a magic number to make the scrolling slow enough.

				mapsquare = map1[i][1000-(j-24+ticks/10)]  


				Select mapsquare
					Case 0
						SetColor 75,75,75
					Case 1
						SetColor 100,100,100
				End Select	

' the smooth-scrolling offset value is in here.  I'm not sure if
' the problem lies here or in the line above where mapsquare is assigned.
				DrawRect i*32,768-(j*32-ticks Mod 32) -32,31,31 			
                Next
		Next
		SetColor 255,0,0
	End Method


Any help is appreciated.

The specific symptom appears to be that every time a tilemap border is crossed (eg. by the bottom of the screen) there is a momentary jerk upward to the previous tile that lasts for one tick. This leads me to think that there may be something wrong with the mod statement, but I can't for the life of me figure it out. Very annoying because I've written tilemap scrollers that worked perfectly the first time numerous times in the past.


Dima(Posted 2013) [#2]
kinda hard to tell - seems somewhat unconventional. Aside from the fact that it's pretty much impossible to have butter-smooth scrolling like in dedicated hardware, there are usually many things that get in the way.

Before getting into generalization of what it takes to achieve smooth scrolling, lets make sure there are no discrepancies. I'm not going to pretend I fully understood the code, but assuming ticks variable is your vertical offset in pixels, there may be a few potential pitfalls.

First thing that kinda pops is when ticks is 0 and j is 0, wouldn't 1000-(0-24+0) = 1024, which is out of bound?

The other thing that I don't understand is if you scale ticks with division when figuring out which array element to select, shouldn't you scale ticks also when performing drawing?

Please forgive me if I misunderstood things - had a few beers at work and about to go home, so I'm blurbing this out without concrete understanding.


Generally, smooth scrolling depends on a few factors; specifically consistent frame timing and precise time measurement. There are a few other mechanics like sub-pixel drawing that make the experience better, but when timing is inconsistent there is pretty much nothing that can be done.

Pretty much all operating systems we develop for have other tasks running in parallel, so hick-ups are almost unavoidable, and even without hick-ups you can only expect consistent frame times when running with vsync and making sure every frame takes less time to render than screen refresh.

Without vsync, discrepancies in times it takes to process frames might be huge. Combine that with very low precision timing and you'll find yourself in a lot of pain. 1 millisecond might sound like a lot to work with, but when you start trying to figure out how to control remainders and seeing either 0ms or 1ms in delta, you realize there is little control to be had. Higher precision timing would make things better where we could measure difference in frames < 1ms.

Last thing I want to mention is sub-pixel rendering (drawing in between pixels with floating point), which is achieved only with texture filtering as hardware in not capable of drawing polygons in between pixels. DrawRect will always jump from pixel to pixel with nothing in between, but DrawImage can interpolate texture between pixels even though the quad itself only draws at integer coordinates. If you're having problems with moving things smoothly between two pixels - that can only be done with textured polygons.

Cheers,
and Good luck.
hrdnutz


zoqfotpik(Posted 2013) [#3]
Not talking about framerate hitches, what I'm seeing here is very weird, almost like an off-by-one error. The scrolling itself is acceptably smooth but the square indexed will jump back by one every once in a while, for about 1/32 of a second...

I've just been pounding at this for so long that my brain is mush, it's a short function, maybe I will scrap it and start over.


Jesse(Posted 2013) [#4]

mapsquare = map1[i][1000-(j-24+ticks/32)




32




zoqfotpik(Posted 2013) [#5]
Nah :)

I am just going to rewrite it from scratch tomorrow, there have to be a hundred easier ways.


Jesse(Posted 2013) [#6]
haha. yes, there are! Definitely!


zoqfotpik(Posted 2013) [#7]
I had thought of defining horizontal strips of tiles as particles and shooting them down the screen. As stupid as it sounds if I had done that it would have unquestionably been easier than all the hair tearing I've done over this... oy vey.


Jesse(Posted 2013) [#8]
well it works here except for the out of bounds:
Strict
Import mojo

Function Main:Int()
	New Game
	Return 1
End Function

Class Game extends App

	Field map1:Int[][]
	Field ticks:Int

	Method OnCreate:int()
		map1 = MakeArray<Int>.TwoD(50,1000)
		For Local i := 0 Until map1.Length()
			For Local j := 0 Until map1[i].Length()
				map1[i][j] = Rnd(2.0)
			Next
		Next
		SetUpdateRate 30
		Return 1
	End Method
	
	Method OnRender:Int()
		Cls()
		ticks = Millisecs()/10
		Local mapsquare:Int
		For Local i%=0 To 31
			Local x:Int =i * 32
			For Local j%=0 To 24

' we begin at the bottom of the 1000-long array and as the map scrolls
' upward the y (j) portion of the array index decreases.  Ticks/10 is
' simply ticks divided by a magic number to make the scrolling slow enough.
				Local l:Int = (j-24+ticks/32)
				mapsquare = map1[i][800-l]  


				Select mapsquare
					Case 0
						SetColor 75,75,75
					Case 1
						SetColor 100,100,100
				End Select	

' the smooth-scrolling offset value is in here.  I'm not sure if
' the problem lies here or in the line above where mapsquare is assigned.
				DrawRect x,768-(j*32-ticks Mod 32) -32,31,31 			
                Next
		Next
		SetColor 255,0,0
		Return 1
	End Method
	
	
End Class
	
	
Class MakeArray<T>

	Function TwoD:T[][](i:Int,j:Int)
		Local arr:T[][] = New T[i][]
		For Local ind:Int = 0 Until i
			arr[ind] = New T[j]
	    Next
	    Return arr		
	End Function

End Class	
	




zoqfotpik(Posted 2013) [#9]
Thank you! Binding the scroll to millisecs() solved the problem. I'm just going to leave it like this. I very much appreciate your help, if there's anything I can do for you in the future please ask.

Programming is sort of unique in that it can always make you feel like an idiot. This isn't even a complicated or difficult algorithm either... or shouldn't be.