Flappybird style endless ground

Hey everyone!

Trying to make the endless scrolling ground mechanic of flappybird, simply by using 3 ground tiles each one starting at the end of the other and when one gets outside the screen it’s position resets to the right of the screen so it could re-enter and so on, set up is as follows:

Code:

go.property("moveSpeed", -600)
function init(self)
	self.groundObjects = {
		ground1 = "/Ground1",
		ground2 = "/Ground2",
		ground3 = "/Ground3"
	}
	
	local screenWidth = tonumber(sys.get_config_int("display.width"))
	self.groundSpriteWidth = go.get(self.groundObjects.ground1 .. "#sprite", "size.x")
	self.groundStartXPosition = screenWidth * 0.5 + self.groundSpriteWidth * 0.5
	self.groundEndXPosition = -self.groundStartXPosition
end

function update(self, dt)
	for k, v in pairs(self.groundObjects) do 
		local currentPosition = go.get_position(v)
		local moveVector = vmath.vector3(self.moveSpeed * dt, 0, 0);
		
		go.set_position(currentPosition + moveVector, v)
		
		if currentPosition.x <= self.groundEndXPosition then
			go.set(v, "position.x", self.groundStartXPosition + moveVector.x)
		end
	end
end

And there a bug happening where a gap would appear between the moving tiles like this:

So the question is, why is this happening?

I know i’m doing something dumb here, but i’m very new to defold and to posting on a forum :slight_smile:

Not sure but it might be that the ground’s sprite is not perfectly centered in the x axis…Because of that when it reaches the groundEndXPosition it moves like that creating the gap

1 Like

Hello,

Humble beginner here too.

Infinite scrolling is one of the first things i tried like you.
I remember having met some friction points.

Some details are missing for us to help you, so i’m going to try to guess.
Maybe I can spot few things that could help you.

How did you manually place the objects in your main collection, did you place the sprites components instead of placing GO’s ?

Why are you using 3 sprites ? I mean, it is a viable option, but the common way to do it is to use 2 sprites each the width of the game width resolution. If sprite is too short, then repeat it to cover at least a width of 2 game’s width, and divide it in 2 game objects. That’s perfectly doable with your 3 sprites approach, but that would simplify the maths and the thinking process.

That is leading me to the gap, You chose startX at 1/2 display + 1/2 sprite’s width. I don’t know where you placed your sprites origin, neither the sprite’s width, nore the display’s width you’re testing with, but half the lenght of your 3 sprites must be at least equal to your display width.

From the picture you’ve provided, that seems to be a position problem, either in your startx / endx, or in the way you’ve placed your sprites in their own GOs. I remember myself having problems with 1 pixel gaps occuring here and there, and that don’t seem to be your problem. This 1 pixel gap i am talking about was solved by moving the “root” of the scroll instead of the sprites, then when reaching the X limits, repositioning the “root” object and switching the sprites position relative to the root at the same time.

Rereading this, i can see that i’m not so helpful, but i hope i could give you some hints.
Do not hesitate to share a minimal project here, as i see it often requested for “quality” help.

Edit:
Ok, i was not happy about the answer i gave you, so i tried to reproduce your project, and better understand your concerns.
Following code is working fine with the “mimic” project i tried to make.

I think that is the behaviour you wished, and you can see the 1 pixel gap i was talking about.
That is assuming your collection, camera and scroll objects are all Origin aligned, and that your game width do not exceed the lenght of 2 sprites.

go.property("moveSpeed", -100)

function init(self)
	self.groundObjects = {
		ground1 = "/Ground1",
		ground2 = "/Ground2",
		ground3 = "/Ground3"
	}

	local screenWidth = tonumber(sys.get_config_int("display.width"))
	
	self.groundSpriteWidth = go.get(self.groundObjects.ground1 .. "#sprite", "size.x")
	self.groundStartXPosition = 2 * self.groundSpriteWidth
	self.groundEndXPosition = -self.groundSpriteWidth
	
end

function update(self, dt)
	local moveVector = vmath.vector3(self.moveSpeed * dt, 0, 0)
	
	for k, v in pairs(self.groundObjects) do

		local pos = go.get_position(v)
		if pos.x <= self.groundEndXPosition then
			pos.x = self.groundStartXPosition
			go.set_position(pos, v)
		else
			go.set_position(pos + moveVector, v)
		end
		
	end
end
1 Like

Thank you for your answer!

Yeah it seems to be it somehow, it turns out i changed the original sprite’s width (originally 336pixels) to approximately half (180pixels), not exactly half, which seems to have missed with the origin point.. although i’m not really to convinced but it seems to be the case :slight_smile:

thank you so much for the taking the time to thoroughly answer :smile:

Yeah you made me realize it isn’t a logic problem, i actually tried different approaches one being using 2 sprites where each one covers the whole width of the screen.. but the problem persisted, and as you noted, given that all the objects are origin aligned, this should work.. turned out to be a problem with the origin, details are in the first reply.

Thanks again, i really appreciate it.

We do have an infinite runner project tutorial here which covered repeating ground graphics: Endless runner tutorial

1 Like

that’s great i’ll check it out, thanks :smile: