Unreliable physics? Jump sometimes high, sometimes lower

Hi, I’ve spent the evening trying to fix a bug in my game and I’ve drawn a blank so I thought I’d post here on the off-chance someone might give me a clue…

I have a character that jumps when a button is tapped.

Physics is running and the button applies a linear impulse.

I’m also using Dusk engine to display a tilemap and the character is inserted into that map.

The button is not inserted into the tilemap - it’s in a higher level scenegroup.

OK, so the problem is that sometimes the button tap triggers a small jump. When the game is restarted using Composer to return to the start scene the jump height is consistent throughout.

When the simulator is restarted the jump height may change to a higher jump - and again there’s consistency when Composer is used to restart.

On my Android phone the jump heights are the same as I see in the simulator.

On another test phone the jump is much higher, sending the character off the screen.

I think the correct jump height is the higher of the two variations and for some reason most of the time it’s ‘dampened’ somehow to a consistently lower height.

Could this be connected to build.settings? I’m using:

[lua]application = {

content = {

width = 320,

height = 480, 

scale = “letterBox”,

fps = 30[/lua]

Could it be connected to how I’m using the tilemap?

Many thanks for any pointers.

Hi @davidroa,

This may be a slight misunderstanding on how the content area works. The physics should be consistent within the content area, meaning that if your character is at the bottom of this area, and the button makes him jump to near the top of the content area, that should remain consistent across devices and the Simulator.

For testing, can you place an opaque rectangle over your content area and compare how the jumping character relates to it on different devices and Simulator views?

[lua]

local testRect = display.newRect( 0, 0, display.contentWidth, display.contentHeight )

testRect.x = display.contentCenterX

testRect.y = display.contentCenterY

testRect:setFillColor( 1, 0, 0, 0.3 )

testRect:toFront()

[/lua]

Best regards,

Brent

Hi Brent,

Thanks for your help. I’ve tested with the content area rectangle - even in the simulator viewing as the same device, sometimes there’s a high jump, sometimes lower - so there’s no consistency.

I see the jump button is mainly lying outside the display area (but still fully on screen):

[lua]

jumpbutton = display.newRect( display.contentWidth+((display.actualContentWidth-display.contentWidth)/2), display.contentHeight-50, screenW/8, 50 )

jumpbutton.anchorX = 1

jumpbutton.anchorY = 0

jumpbutton:setFillColor( 1, 0, 1 )

jumpbutton.id = “jumpbutton”

guifront:insert(jumpbutton)[/lua]

This has only just start occurring as I’m completing the game - in the earlier stages the jump seemed consistent…

I wonder if it’s connected with other objects being positioned outside the display area, or newly added display groups?

Many thanks.

Hi David,

If the jumping itself seems inconsistent, then it could be how you’re using the physics and applying impulse. I’d need to see the relevant code for that…

Also, are you using “tap” or “touch” for the jump button? If “touch”, what “phase” are you using in its listener function?

Brent

Hi Brent,

Here’s the relevant code - I realise I’m not using composer well. As you mention, it’s likely to be the jump function itself?

[lua]

display.setStatusBar(display.HiddenStatusBar)

local composer = require( “composer” )

local scene = composer.newScene()

local physics = require( “physics” )

system.activate( “multitouch” )

physics.start()

physics.setGravity( 0, 9.8 )

physics.pause()

local dusk = require(“Dusk.Dusk”)

local textureFilter = “nearest”

display.setDefault(“magTextureFilter”, textureFilter)


– forward declarations and other locals


local arthur

local arthurData

local jumped = audio.loadSound ( “audio/jump.wav” )

local jumpbutton


function scene:create( event )


local sceneGroup = self.view

local gui = display.newGroup()

local guifront = display.newGroup()

gui:insert(guiback)

gui:insert(guifront) 

map = dusk.buildMap(“levelgraphic2.json”)

map:scale (1, 1)

map.setTrackingLevel(0.9)

guiback:insert(map)

map.setCameraBounds ({ xMin = display.contentCenterX, yMin = display.contentCenterY, xMax = map.data.width - display.contentCenterX, yMax = map.data.height - display.contentCenterY })

map.layer[1].setCameraOffset(14, 24)

map.layer[2].setCameraOffset(14, 24)

map.updateView ()

-----------------------------------------------------------------------------Player

sheet1 = graphics.newImageSheet( “arthur.png”, { width=25, height=37, numFrames=14 } )

arthurData =

{

   { name=“standing”, frames= { 13 } },

   { name=“running”, start=1, count=12, time=framerate },

   { name=“beenhit”, frames= { 14 } }

}

arthur = display.newSprite( sheet1, arthurData )

arthur.x = 140

arthur.y = 106

arthur.id = “arthur”

arthur.title = “arthur”

arthur.myName = “arthur”

arthur.rotation = 0

arthur.anchorY = 1

arthur.anchorX = 0.5

arthur:setSequence(“standing”)

arthur:play()

physics.addBody( arthur, “dynamic”, { density=1, friction=0.5, bounce=0} )

arthur.isFixedRotation = true

map.layer[1]:insert(arthur)

------------------------------------------------------------------------------------- jump button

jumpbutton = display.newRect( display.contentWidth+((display.actualContentWidth-display.contentWidth)/2), display.contentHeight-50, screenW/8, 50 )

jumpbutton.anchorX = 1

jumpbutton.anchorY = 0

jumpbutton:setFillColor( 1, 0, 1 )

jumpbutton.id = “jumpbutton”

guifront:insert(jumpbutton)

------------------------------------------------------------------------------------------jumping function

local function onJumpButtonRelease ( event )

if event.phase == “began” and arthur.y >105 then

print( “jumped” )

audio.play ( jumped  )

arthur:applyLinearImpulse( 0, -2, arthur.x, arthur.y )

canJump = 0

return true

end

end

end


function scene:show( event )


local sceneGroup = self.view

local phase = event.phase

if phase == “will” then

physics.start()

elseif phase == “did” then

end

end


function scene:hide( event )


local sceneGroup = self.view

local phase = event.phase

if event.phase == “will” then

elseif phase == “did” then

end

end

function scene:destroy( event )

local sceneGroup = self.view

transition.cancel()

physics.pause()

physics.removeBody( arthur )

jumpbutton:removeEventListener( “touch”, onJumpButtonRelease )

display.remove(map)

map = nil

arthur:removeSelf()

arthur = nil

jumpbutton:removeSelf()

jumpbutton = nil

end


– Listener setup

scene:addEventListener( “create”, scene )

scene:addEventListener( “show”, scene )

scene:addEventListener( “hide”, scene )

scene:addEventListener( “destroy”, scene )


return scene

[/lua]

Many thanks.

Hi David,

I see a couple things which may be causing the inconsistency.

First, I notice that you have enabled multitouch. This is fine, and many apps allow for it, but you need to be careful that you’re not letting the user multi-touch the jump button somehow. If it’s a small button, I doubt this is the issue, because they couldn’t put two fingers within its range, but I just want to mention it as something to be careful with. You may consider just changing it to a “tap” listener instead.

More likely, the issue is related to your physics handling. I see that you allow the character to jump when its Y value is >105. That’s OK, but it’s very possible that you’re letting him jump again when he still has downward momentum from the previous jump (so, he’s falling and hasn’t yet stopped on the ground). So then, you apply an upward linear impulse, but you’re fighting against the downward momentum, which means that the upward impulse will be weakened by how much opposing downward momentum you’re dealing with at that moment. Remember that even though the character may appear to be stopped to you, he might have a tiny amount of remaining momentum which is affecting the following jumps.

So, a better approach is to completely stop the character before you apply a new upward impulse. Try setting its linear Y velocity to 0, then apply the new upward impulse. Now, “setLinearVelocity()” function accepts both X and Y values, so if you have any X linear velocity that you need to keep (like the character is drifting right or left), you’ll need to get that X value first then re-apply it in the set function:

[lua]

local vx, vy = arthur:getLinearVelocity()

arthur:setLinearVelocity( vx, 0 )

– apply new upward impulse here

[/lua]

Finally, some things in the physics engine need to be delayed by a tiny amount of time (10-20 milliseconds) so Box2D can process the previous action before moving on. So, you might need to delay the new upward impulse by 10-20 milliseconds by using a “timer.performWithDelay()” call, or flagging the impulse to happen on the next game timestep.

Hope this helps,

Brent

Great thanks Brent, you’ve given me a lot to go on there. I’ll have a go and see if I can get things working a little better.

Hi @davidroa,

This may be a slight misunderstanding on how the content area works. The physics should be consistent within the content area, meaning that if your character is at the bottom of this area, and the button makes him jump to near the top of the content area, that should remain consistent across devices and the Simulator.

For testing, can you place an opaque rectangle over your content area and compare how the jumping character relates to it on different devices and Simulator views?

[lua]

local testRect = display.newRect( 0, 0, display.contentWidth, display.contentHeight )

testRect.x = display.contentCenterX

testRect.y = display.contentCenterY

testRect:setFillColor( 1, 0, 0, 0.3 )

testRect:toFront()

[/lua]

Best regards,

Brent

Hi Brent,

Thanks for your help. I’ve tested with the content area rectangle - even in the simulator viewing as the same device, sometimes there’s a high jump, sometimes lower - so there’s no consistency.

I see the jump button is mainly lying outside the display area (but still fully on screen):

[lua]

jumpbutton = display.newRect( display.contentWidth+((display.actualContentWidth-display.contentWidth)/2), display.contentHeight-50, screenW/8, 50 )

jumpbutton.anchorX = 1

jumpbutton.anchorY = 0

jumpbutton:setFillColor( 1, 0, 1 )

jumpbutton.id = “jumpbutton”

guifront:insert(jumpbutton)[/lua]

This has only just start occurring as I’m completing the game - in the earlier stages the jump seemed consistent…

I wonder if it’s connected with other objects being positioned outside the display area, or newly added display groups?

Many thanks.

Hi David,

If the jumping itself seems inconsistent, then it could be how you’re using the physics and applying impulse. I’d need to see the relevant code for that…

Also, are you using “tap” or “touch” for the jump button? If “touch”, what “phase” are you using in its listener function?

Brent

Hi Brent,

Here’s the relevant code - I realise I’m not using composer well. As you mention, it’s likely to be the jump function itself?

[lua]

display.setStatusBar(display.HiddenStatusBar)

local composer = require( “composer” )

local scene = composer.newScene()

local physics = require( “physics” )

system.activate( “multitouch” )

physics.start()

physics.setGravity( 0, 9.8 )

physics.pause()

local dusk = require(“Dusk.Dusk”)

local textureFilter = “nearest”

display.setDefault(“magTextureFilter”, textureFilter)


– forward declarations and other locals


local arthur

local arthurData

local jumped = audio.loadSound ( “audio/jump.wav” )

local jumpbutton


function scene:create( event )


local sceneGroup = self.view

local gui = display.newGroup()

local guifront = display.newGroup()

gui:insert(guiback)

gui:insert(guifront) 

map = dusk.buildMap(“levelgraphic2.json”)

map:scale (1, 1)

map.setTrackingLevel(0.9)

guiback:insert(map)

map.setCameraBounds ({ xMin = display.contentCenterX, yMin = display.contentCenterY, xMax = map.data.width - display.contentCenterX, yMax = map.data.height - display.contentCenterY })

map.layer[1].setCameraOffset(14, 24)

map.layer[2].setCameraOffset(14, 24)

map.updateView ()

-----------------------------------------------------------------------------Player

sheet1 = graphics.newImageSheet( “arthur.png”, { width=25, height=37, numFrames=14 } )

arthurData =

{

   { name=“standing”, frames= { 13 } },

   { name=“running”, start=1, count=12, time=framerate },

   { name=“beenhit”, frames= { 14 } }

}

arthur = display.newSprite( sheet1, arthurData )

arthur.x = 140

arthur.y = 106

arthur.id = “arthur”

arthur.title = “arthur”

arthur.myName = “arthur”

arthur.rotation = 0

arthur.anchorY = 1

arthur.anchorX = 0.5

arthur:setSequence(“standing”)

arthur:play()

physics.addBody( arthur, “dynamic”, { density=1, friction=0.5, bounce=0} )

arthur.isFixedRotation = true

map.layer[1]:insert(arthur)

------------------------------------------------------------------------------------- jump button

jumpbutton = display.newRect( display.contentWidth+((display.actualContentWidth-display.contentWidth)/2), display.contentHeight-50, screenW/8, 50 )

jumpbutton.anchorX = 1

jumpbutton.anchorY = 0

jumpbutton:setFillColor( 1, 0, 1 )

jumpbutton.id = “jumpbutton”

guifront:insert(jumpbutton)

------------------------------------------------------------------------------------------jumping function

local function onJumpButtonRelease ( event )

if event.phase == “began” and arthur.y >105 then

print( “jumped” )

audio.play ( jumped  )

arthur:applyLinearImpulse( 0, -2, arthur.x, arthur.y )

canJump = 0

return true

end

end

end


function scene:show( event )


local sceneGroup = self.view

local phase = event.phase

if phase == “will” then

physics.start()

elseif phase == “did” then

end

end


function scene:hide( event )


local sceneGroup = self.view

local phase = event.phase

if event.phase == “will” then

elseif phase == “did” then

end

end

function scene:destroy( event )

local sceneGroup = self.view

transition.cancel()

physics.pause()

physics.removeBody( arthur )

jumpbutton:removeEventListener( “touch”, onJumpButtonRelease )

display.remove(map)

map = nil

arthur:removeSelf()

arthur = nil

jumpbutton:removeSelf()

jumpbutton = nil

end


– Listener setup

scene:addEventListener( “create”, scene )

scene:addEventListener( “show”, scene )

scene:addEventListener( “hide”, scene )

scene:addEventListener( “destroy”, scene )


return scene

[/lua]

Many thanks.

Hi David,

I see a couple things which may be causing the inconsistency.

First, I notice that you have enabled multitouch. This is fine, and many apps allow for it, but you need to be careful that you’re not letting the user multi-touch the jump button somehow. If it’s a small button, I doubt this is the issue, because they couldn’t put two fingers within its range, but I just want to mention it as something to be careful with. You may consider just changing it to a “tap” listener instead.

More likely, the issue is related to your physics handling. I see that you allow the character to jump when its Y value is >105. That’s OK, but it’s very possible that you’re letting him jump again when he still has downward momentum from the previous jump (so, he’s falling and hasn’t yet stopped on the ground). So then, you apply an upward linear impulse, but you’re fighting against the downward momentum, which means that the upward impulse will be weakened by how much opposing downward momentum you’re dealing with at that moment. Remember that even though the character may appear to be stopped to you, he might have a tiny amount of remaining momentum which is affecting the following jumps.

So, a better approach is to completely stop the character before you apply a new upward impulse. Try setting its linear Y velocity to 0, then apply the new upward impulse. Now, “setLinearVelocity()” function accepts both X and Y values, so if you have any X linear velocity that you need to keep (like the character is drifting right or left), you’ll need to get that X value first then re-apply it in the set function:

[lua]

local vx, vy = arthur:getLinearVelocity()

arthur:setLinearVelocity( vx, 0 )

– apply new upward impulse here

[/lua]

Finally, some things in the physics engine need to be delayed by a tiny amount of time (10-20 milliseconds) so Box2D can process the previous action before moving on. So, you might need to delay the new upward impulse by 10-20 milliseconds by using a “timer.performWithDelay()” call, or flagging the impulse to happen on the next game timestep.

Hope this helps,

Brent

Great thanks Brent, you’ve given me a lot to go on there. I’ll have a go and see if I can get things working a little better.