Object get stuck at gaps

I am playing with the Sticker-Knight-Platformer project.

My purpose is: making my main character moving with constant speed(something like “3 pixels per frame”), regardless of its mass, so I use
setLinearVelocity in enterFrame event.
I tried different speed values, and found sometimes my character(using Rectangular Bodies) will be stuck between my “floor objects”, just like there is a wall on the gap.


(x and y on the box, all “floor objects” is 256px wide)

If I change the speed to some value it work fine, some doesn’t.

Is that cause by float numbers or something? Should I use something to “round up” these positions?

Hello @gate120
I’m looking at your hybrid physics view and trying to understand what the different physics bodies are. I see two physics bodies within the blue box but neither matches the blue box (which I assume is your main character). Can you provide a video with with the physics drawmode set hybrid?

Also, if you can provide your code that would be helpful to. You can use the toolbar to format the code so it is easy for everyone to read (and more likely to get you good responses to your questions)

Oh my fault. the blue box is just sprite and the long one box is a sensor, only the square one is body.
here is the video. As you can see, my character move fine, until a jump to the wall, then stuck when move left with this gap.


the “floor” and the “wall” both are simple static body without any setting.
here is my enterFrame code and addBody

    instance = display.newSprite(parent, myImageSheet, sequenceData)
    instance.x, instance.y = x, y -- x is 127 as default, I dont know why in the simulator is 126.999999 after addBody)
    instance:setSequence("idle")
    instance.anchorY = 0.6

    physics.addBody(instance, "dynamic", {
        box = {
            halfWidth = 20,
            halfHeight = 20,
            x = 0,
            y = 0
        },
        -- radius = 20,
        density = 12,
        bounce = 0,
        friction = 0
    }
)
instance.isFixedRotation = true

local function enterFrame()
        -- Do this every frame
        local vx, vy = instance:getLinearVelocity()
        local dx = left + right -- dx will be set to 1 or -1

        instance:setLinearVelocity(dx*60, vy) -- as I think linear veloticy means "pixels per second" so 60 is 1 pixel per second
        -- Turn around
        instance.xScale = -flip
    end

OK, first try getting rig of the anchorY adjustment

Next, this box definition doesn’t belong in physics.addBody(). What is this code for?

You can see from the physics hybrid drawmode view that the character (sprite instance) doesn’t match the physics boundaries. For now you should use a simple display object for you character instead of the sprite. Also make sure your instance is local

display.newRect( [parent,] x, y, width, height )

local instance = display.newRect( x, y, 40, 40 ) -- I just guessed at the size you can adjust
-- skip the parent argument unless you are inserting the instance into a display group

What you are looking for is a good clean hybrid view of the physics bodies that match the size of the display objects - so you can tell what is going on.

Next, this box definition doesn’t belong in physics.addBody(). What is this code for?

In the doc says " Offset/Angled Rectangular Body" need this.

I have tried simple rect newRect( x, y, 40, 40 )

    instance = display.newRect(parent, x, y, 40, 40 )
    instance.x, instance.y = x, y
    physics.addBody(instance, "dynamic", {
        density = 12,
        bounce = 0,
        friction = 0
    }

its still get stuck:

Is it no matter with the display object?

1 Like

I notice that the character stops right at the border between the floor physics bodies. That seems like a planned design somewhere in the code as opposed to an error. Can you show us the code for the floor - especially the physics bodies and event listeners.

I think we need to see lot more of the code.

I tried to reproduce it with a simple project.
reproduce.zip (196.1 KB)
Use arrow keys to move left and right, space for “jump”.

local screenW, screenH, halfW = display.actualContentWidth, display.actualContentHeight, display.contentCenterX

local crate
local left, right, hspd = 0, 0, 1

function scene:create( event )

	-- Called when the scene's view does not exist.
	-- 
	-- INSERT code here to initialize the scene
	-- e.g. add display objects to 'sceneGroup', add touch listeners, etc.

	local sceneGroup = self.view

	-- We need physics started to add bodies, but we don't want the simulaton
	-- running until the scene is on the screen.
	physics.start()
	physics.setDrawMode("hybrid");
	physics.setGravity(0, 32);
	physics.pause()


	-- create a grey rectangle as the backdrop
	-- the physical screen will likely be a different shape than our defined content area
	-- since we are going to position the background from it's top, left corner, draw the
	-- background at the real top, left corner.
	local background = display.newRect( display.screenOriginX, display.screenOriginY, screenW, screenH )
	background.anchorX = 0 
	background.anchorY = 0
	background:setFillColor( .5 )
	
	-- make a crate (off-screen), position it, and rotate slightly
	crate = display.newRect(127, 794, 40, 40 )
	
	-- add physics to the crate
	physics.addBody( crate, { density=12.0, friction=0, bounce=0 } )
	crate.isFixedRotation = true

	-- create a grass object and add physics (with custom shape)
	local grass = display.newImageRect( "grass.png", 256, 82 )
	--  draw the grass at the very bottom of the screen
	grass.x, grass.y = 128, 1087
	physics.addBody( grass, "static", { friction=0 } )

	local grass2 = display.newImageRect( "grass.png", 256, 82 )
	--  draw the grass at the very bottom of the screen
	grass2.x, grass2.y = 384, 1087
	physics.addBody( grass2, "static", { friction=0 } )

	local grass3 = display.newImageRect( "grass.png", 256, 82 )
	--  draw the grass at the very bottom of the screen
	grass3.x, grass3.y = 640, 1087
	physics.addBody( grass3, "static", { friction=0 } )

	local wall = display.newImageRect( "grass.png", 32, 992 )
	--  draw the grass at the very bottom of the screen
	wall.x, wall.y = 16, 556
	physics.addBody( wall, "static", { friction=0 } )
	
	-- all display objects must be inserted into group
	sceneGroup:insert( background )
	sceneGroup:insert( grass)
	sceneGroup:insert( grass2)
	sceneGroup:insert( grass3)
	sceneGroup:insert( wall )
	sceneGroup:insert( crate )

	function crate:collision( event )
		local phase = event.phase
		local other = event.other
		local vx, vy = crate:getLinearVelocity()
		if phase == "began" then
            if self.jumping and vy > 0 then
				-- Landed after jumping
				crate.jumping = false
			end
		end
	end

	crate:addEventListener( "collision" )

	-- events
	local function key(event)
        local phase = event.phase
        local name = event.keyName
		local lastEvent = {}
        if (phase == lastEvent.phase) and (name == lastEvent.keyName) then
            return false
        end -- Filter repeating keys
        if phase == "down" then
            if "left" == name or "a" == name then
                left = -hspd
            end
            if "right" == name or "d" == name then
                right = hspd
            elseif "space" == name then
                if not crate.jumping then
					crate:applyLinearImpulse(0, -550)
					crate.jumping = true
				end
            end
        elseif phase == "up" then
            if "left" == name or "a" == name then
                left = 0
            end
            if "right" == name or "d" == name then
                right = 0
            end
        end
        lastEvent = event
    end
	Runtime:addEventListener( "key", key )
end

-- Function to scroll the map
function scene:enterFrame(event)
    -- Easy way to scroll a map based on a character
	local sceneGroup = self.view
	if crate then
		local x, y = crate.x - display.contentCenterX, crate.y - display.contentCenterY -- 镜头位置

		if x < 0 then
			x = 0		
		end
		if y < 0 then
			y = 0		
		end
		sceneGroup.x, sceneGroup.y = -x, -y

		local vx, vy = crate:getLinearVelocity()
        local dx = left + right
        crate:setLinearVelocity(dx*60, vy)
	end
end


function scene:show( event )
	local sceneGroup = self.view
	local sceneself = self
	local phase = event.phase
	
	if phase == "will" then
		-- Called when the scene is still off screen and is about to move on screen
		function ef(event)
			sceneself:enterFrame(event)
		end
		Runtime:addEventListener("enterFrame", ef)
	elseif phase == "did" then
		-- Called when the scene is now on screen
		-- 
		-- INSERT code here to make the scene come alive
		-- e.g. start timers, begin animation, play audio, etc.
		physics.start()
	end
end

It looks like something is going wrong with the scrolling after you jump. Does the problem always happen after a jump?

Try adding this to the enterFrame:

crate:setLinearVelocity(dx*60, vy)
-- add this under setLinearVelocity
print( dx, left, right, sceneGroup.x, sceneGroup.y)

Seeing what these values are will help figure out what is going wrong

Does the problem always happen after a jump?

No. if I use another speed value like hspd = 1.3, it can be happened without jumping.

I tried to remove the scrolling code, even without modifying the sceneGroup.y, and still got the problem:

function scene:show( event )
	local sceneGroup = self.view
	local sceneself = self
	local phase = event.phase
	
	if phase == "will" then
		-- Called when the scene is still off screen and is about to move on screen
		function ef(event)
			sceneself:enterFrame(event)
		end
		sceneGroup.y = -800 -- without scrolling, using fixed position

		Runtime:addEventListener("enterFrame", ef)
	elseif phase == "did" then
		physics.start()
	end
end

print( dx, left, right, sceneGroup.x, sceneGroup.y)
Seeing what these values are will help figure out what is going wrong

It seems no difference between normal and stuck.

I think the problem is that the square is snagging on the ground somehow.

If you switch the physics body to a circle (using radius), it now longer seems to get stuck. Try this:

-- add physics to the crate
	physics.addBody( crate, { density=12.0, friction=0, bounce=0, radius = 20 } )

I believe this is the problem so the next step is to work up a solution. Maybe add a very slight linear impulse up - one that is barely noticeable but clears the snag. I’m still not sure why we are seeing this.

Yes, circle body will not stop like square, but I prefer to use boxes. It’s quite weird that “I can’t play with boxes”.

I found that when the crate cross the border, two collision event always triggerd. It seems like the crate is stopped because the engine believe it collide with the floor on the corner, like this:
QQ图片20220203125510

So my solution is:

-- make it trigger only one event when cross the border? 
-- I dont really understand, but it works.
physics.setAverageCollisionPositions(true)

function crate:preCollision(event)
        local other = event.other
        if event.contact then
			
			-- check: is that collision happened on endpoint?
			if math.round(math.abs(event.x)) == event.other.width/2 and math.round(math.abs(event.y)) == event.other.height/2 then
				print( "position: " .. event.x .. "," .. event.y )
			    print(event.other.x .. "," ..event.other.y .. "," .. event.other.width.. ",".. event.other.height)
				event.contact.isEnabled = false
			end
        end
    end

It is the only way to do that? Using math.round in every collision events is likely to cause a performance issue.

I’m still finding this odd - it really shouldn’t be happening, but a little Google snooping revealed that this can happen when Box2D physics is run on a tile map. I’m not sure what the solutions are but there must be some simple ones. I use continuous physics backgrounds (chain bodies) so I’ve never had this issue.
You could create one physics chain body or one polygonal body that covers all of the floor tile display objects. That way you would lose the edges.

For now, I think you should just run your precollision event and hopefully this can get resolve later.

FYI - feel free to use math round all you like as long as you localize math

local math = require('math')

It’s the precollision event that is more costly as that happens on the Box2D end us very “noisy”. If I think of anything I’ll let you know. @ponywolf who made Sticker Knight and is a moderator here might have some hunches for solutions.

Thank you @sporkfin !
I wii try out replacing bodies.

I believe this article describes the problem in question. It suggests a few ideas for tackling it.

Thanks @StarCrunch , It is exactly what I need.
This article says Box2D’s edge shapes has “ghost vertices” since V2.2, can I use this in Solar2D? I can’t find anything relate to it in doc.

Try using chain bodies to define the polygon and clip the edges

That will let you emulate the solution suggested in the article posted by StarCrunch

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.