What is best way to make a sprite solid? Physics engine or something else?

Hi

Currently, I put treasure chests on the map, by adding objects in Tiled on the object layer. All those objects cause treasure chest sprites to be added when the map loads:

mte.addSprite(sprite, {

            kind = “sprite”,

            layer = value[“layer”],

            locX = spriteposition[“x”],

            locY = spriteposition[“y”],

            levelWidth = 32,

            levelHeight = 32

        })

That part works, but how do I make the player not just walk over them?

Currently, I’m using the following detection code and altered the last part to look for a property of “solid” on all layers within a level.

–CREATE COLLISION DETECTION COORDINATES -----------------------------------------------

–[[

    In this sample the player is a rectangle 32 pixels across and 32 pixels wide with an

offset of -16 in the Y direction. 

The origin of a sprite is it’s center, but in this case, due to the -16 pixel offset, the

origin of the sprite as reckoned by MTE is 16 pixels from the bottom of the sprite, or

the center of the lower tile in the stack.

    To detect platforms and obstacles around the player we must define points around the

player to be checked by our code. There are many ways to do this, but in this example the

points are defined in relation to the player sprite. The top1 and top2 points are along

the top of the player sprite. The sprite is 32 pixels high and the origin is 16 pixels from

it’s bottom, so the top is -16 pixels from the origin. The rest of the points are defined

in the same manner.

]]–

local offsets = {top1 = {-12, -16},

                top2 = {12, -16},

                bottom1 = {-12, 16},

                bottom2 = {12, 16},

                left1 = {-16, -12},

                left2 = {-16, -32},

                right1 = {16, -12},

                right2 = {16, -32}

}

–[[

    These are variables for storing the true coordinates of the collision detection 

points in relation to the level rather than the player sprite. These values are 

calculated by the detectObstacle function below, which is called in an enterFrame.

]]–

local sensors = {top1 = {},

                top2 = {},

                bottom1 = {},

                bottom2 = {},

                left1 = {},

                left2 = {},

                right1 = {},

                right2 = {}

}

local detect = {} 

–OBSTACLE DETECTION FUNCTION-----------------------------------------------------------

local detectObstacle = function()    

    --CALCULATE SENSOR POSITIONS

    local playerPosX = player.getLevelPosX() + velX

    local playerPosY = player.getLevelPosY() + velY

    sensors.top1[1], sensors.top1[2] = playerPosX + offsets.top1[1], playerPosY + offsets.top1[2]

    sensors.top2[1], sensors.top2[2] = playerPosX + offsets.top2[1], playerPosY + offsets.top2[2]

    sensors.bottom1[1], sensors.bottom1[2] = playerPosX + offsets.bottom1[1], playerPosY + offsets.bottom1[2]

    sensors.bottom2[1], sensors.bottom2[2] = playerPosX + offsets.bottom2[1], playerPosY + offsets.bottom2[2]

    sensors.left1[1], sensors.left1[2] = playerPosX + offsets.left1[1], playerPosY + offsets.left1[2]

    sensors.left2[1], sensors.left2[2] = playerPosX + offsets.left2[1], playerPosY + offsets.left2[2]

    sensors.right1[1], sensors.right1[2] = playerPosX + offsets.right1[1], playerPosY + offsets.right1[2]

    sensors.right2[1], sensors.right2[2] = playerPosX + offsets.right2[1], playerPosY + offsets.right2[2]

    

    --CHECK FOR TILE PROPERTIES

    

    

    

    detect.top1 = mte.getTileProperties({levelPosX = sensors.top1[1], levelPosY = sensors.top1[2], level = 1})

    detect.top2 = mte.getTileProperties({levelPosX = sensors.top2[1], levelPosY = sensors.top2[2], level = 1})

    detect.bottom1 = mte.getTileProperties({levelPosX = sensors.bottom1[1], levelPosY = sensors.bottom1[2], level = 1})

    detect.bottom2 = mte.getTileProperties({levelPosX = sensors.bottom2[1], levelPosY = sensors.bottom2[2], level = 1})

    detect.left1 = mte.getTileProperties({levelPosX = sensors.left1[1], levelPosY = sensors.left1[2], level = 1})

    detect.left2 = mte.getTileProperties({levelPosX = sensors.left2[1], levelPosY = sensors.left2[2], level = 1})

    detect.right1 = mte.getTileProperties({levelPosX = sensors.right1[1], levelPosY = sensors.right1[2], level = 1})

    detect.right2 = mte.getTileProperties({levelPosX = sensors.right2[1], levelPosY = sensors.right2[2], level = 1}) 

    

end

–CHECK FOR NORTH COLLISION

    

    for key,value in pairs(detect.top1) do

        if(value.properties and value.properties.solid) then

            local loc1X, loc1Y = mte.convert(“levelPosToLoc”, sensors.top1[1]), mte.convert(“levelPosToLoc”, nil, sensors.top1[2])

            local pos1X, pos1Y = mte.convert(“locToLevelPos”, loc1X) - mte.worldScaleX * 0.5, mte.convert(“locToLevelPos”, nil, loc1Y) - mte.worldScaleY * 0.5    

            if velY < 0 then

                tempVelY = velY + (pos1Y - sensors.top1[2] + mte.worldScaleY)

                velY = 0

            end

        end

    end

    for key,value in pairs(detect.top2) do

        if(value.properties and value.properties.solid) then

            local loc1X, loc1Y = mte.convert(“levelPosToLoc”, sensors.top1[1]), mte.convert(“levelPosToLoc”, nil, sensors.top1[2])

            local pos1X, pos1Y = mte.convert(“locToLevelPos”, loc1X) - mte.worldScaleX * 0.5, mte.convert(“locToLevelPos”, nil, loc1Y) - mte.worldScaleY * 0.5    

            if velY < 0 then

                tempVelY = velY + (pos1Y - sensors.top1[2] + mte.worldScaleY)

                velY = 0

            end

        end

    end

I see you’re using a modification of the non-physics platformer sample project code. This can certainly be made to work, but I recommend trying out the physics support first. Physics condenses most of this into a scarce handful of lines, and it comes ready to deal with such niceties as sloped walls and the like. The old non-physics code would have to be modified extensively to deal with every new situation. 

OK. Does this mean that the wall tiles that are currently using “solid” “true” would not be done that way, but all by physics?

OK. Answered my own question. I added the property of “physics” = true to the wall tiles and all walls are solid. 

However this did cause my player to stop when I walk under the level above the first level. How do you tell the player to only worry about colliding with physics objects on the current level?

Also, is there a way to automatically hide the upper levels when the player sprite is under them? (cant see the player because the upper level(s) is over them?)

Thanks

The simplest way to prevent player collisions with physics objects on different levels is to call mte.toggleLayerPhysicsActive(layer, false) on each of the other level’s layers. The caveat is that no physics interactions or physics movement will take place on disabled layers.

A more foolproof but somewhat harder to understand feature of Corona’s Physics API is the concept of categoryBits and maskBits. “An object will only collide with other objects if their categoryBits are among its assigned maskBits.” You can read more about it towards the bottom of this page: http://developer.coronalabs.com/content/game-edition-collision-detection and see a convenient worksheet for figuring out the correct bits here: http://developer.coronalabs.com/forum/2010/10/25/collision-filters-helper-chart

You can set the categoryBits and maskBits of individual tiles, Tiled objects, and layers via Tiled properties. Tiles will inherit their layer’s values if they do not have their own. The Platformer - Angled PHYSICS 0v872 sample project includes a map, “map/AngledPhysics2”, which demonstrates physics collision filters.

There is no way to automatically hide layers when a sprite walks under them. I recommend hiding and unhiding layers using properties assigned to the tiles used to move between levels, such as stairs, as seen in CastleDemo. 

Thank you Dyson. My game will be multiplayer and players may be on different levels so it sounds like the categoryBits is the way to go. 

As I read about categoryBits. It makes sense, but I wonder if it will work for my situation of multiple players on different levels in the same game. If I change a categoryBit or maskBit won’t it change for the other players? I suppose not, if each individual’s device handles assigning and changing these variables, but seems like there would be sideeffects to that. It seems like I could write a function.

is there a way to write a function that detects a precolision with an object and if it is on another level have it filter or ignore the collision? Thanks Guys

Hmmm… Well, I guess what you could do is assign the categoryBits and maskBits to the layers and have those identical across all instances of your game for the multiple players. What you would do is alter the catBits and maskBits of the player sprite itself, by removing the physics body and re-adding it with the correct bits. This could be triggered by the same Tiled object you use to hide/show levels, positioned over the stair tiles or what have you. This might be simpler than writing a function for detecting collisions before they happen.

Check it out, it works great: 

–function for player to detect and ignore collisions with crap on other levels

function player:preCollision( event )

    local otherobject = event.other

    if mte.getLevel(otherobject.layer) ~= player.level then

        event.contact.isEnabled = false

        pr(otherobject)

    end

end

player:addEventListener( “preCollision” )

This brings out an interesting thing with the movespriteto function. It can’t move to the upper stairs because it is being blocked by the wall because of physics: See screenshot - there’s a gap between stairs so going up could bring someone to another area of the map. http://screencast.com/t/6PZ6JGxR

So I tried the movesprite function because it’s not linear movement, but it’s giving an error:stack traceback:

    [C]: ?

    …s/Work/home/!!IPAD GAMES/raiders/Raiders/MTE/mte.lua:10529: in function ‘moveSprite’

    …umes/Work/home/!!IPAD GAMES/raiders/Raiders/main.lua:534: in function ‘entersNewTile’

    …umes/Work/home/!!IPAD GAMES/raiders/Raiders/main.lua:558: in function <…umes/Work/home/!!IPAD GAMES/raiders/Raiders/main.lua:552>

    ?: in function <?:218>

Attempt to perform arithmetic on local ‘velX’ (a nil value)

I don’t use velX anymore because I’m using physics. 

Maybe I’m going about this wrong?

That function is a lot more concise than I thought it could be, very nice!

MoveSprite is little more than a convenience function, it does all of three things before passing its data internally to moveSpriteTo. It still requires a velX and velY argument even with physics- the correct syntax is moveSprite(sprite, velX, velY)- however it will not work any better for you than moveSpriteTo given the circumstances.

If you set your body.isBodyActive to false it will not collide with any other objects, so it could pass through the walls to reach the upper stair. The engine isn’t set up to move inactive physics objects, but this is easy enough to change. Go into mte.lua, find the update() function around line 9309, and find this bit of code starting at line 9315:

if not M.spritesFrozen then --UPDATE SPRITES for key,value in pairs(movingSprites) do if key and objects and objects[key].isMoving then --[[...]]-- local object = objects[key] local velX = object.deltaX[1] local velY = object.deltaY[1] if object.bodyType then --print(object.x, object.y) --print("\*\*", velX, velY) object.x = object.x + velX object.y = object.y + velY else local remainingVelX = abs(object.deltaX[1]) local remainingVelY = abs(object.deltaY[1]) local velXSign = 1 local velYSign = 1 ...

Edit the “if object.bodyType then” and “else” lines as follows:

if not M.spritesFrozen then --UPDATE SPRITES for key,value in pairs(movingSprites) do if key and objects and objects[key].isMoving then --[[...]]-- local object = objects[key] local velX = object.deltaX[1] local velY = object.deltaY[1] --if object.bodyType then if object.bodyType and object.isBodyActive then --print(object.x, object.y) --print("\*\*", velX, velY) object.x = object.x + velX object.y = object.y + velY --else elseif not object.bodyType or not object.isBodyActive then local remainingVelX = abs(object.deltaX[1]) local remainingVelY = abs(object.deltaY[1]) local velXSign = 1 local velYSign = 1 ...

The next update will include this fix as well.

Makes sense.

Works Perfectly! Thank you Dyson

I see you’re using a modification of the non-physics platformer sample project code. This can certainly be made to work, but I recommend trying out the physics support first. Physics condenses most of this into a scarce handful of lines, and it comes ready to deal with such niceties as sloped walls and the like. The old non-physics code would have to be modified extensively to deal with every new situation. 

OK. Does this mean that the wall tiles that are currently using “solid” “true” would not be done that way, but all by physics?

OK. Answered my own question. I added the property of “physics” = true to the wall tiles and all walls are solid. 

However this did cause my player to stop when I walk under the level above the first level. How do you tell the player to only worry about colliding with physics objects on the current level?

Also, is there a way to automatically hide the upper levels when the player sprite is under them? (cant see the player because the upper level(s) is over them?)

Thanks

The simplest way to prevent player collisions with physics objects on different levels is to call mte.toggleLayerPhysicsActive(layer, false) on each of the other level’s layers. The caveat is that no physics interactions or physics movement will take place on disabled layers.

A more foolproof but somewhat harder to understand feature of Corona’s Physics API is the concept of categoryBits and maskBits. “An object will only collide with other objects if their categoryBits are among its assigned maskBits.” You can read more about it towards the bottom of this page: http://developer.coronalabs.com/content/game-edition-collision-detection and see a convenient worksheet for figuring out the correct bits here: http://developer.coronalabs.com/forum/2010/10/25/collision-filters-helper-chart

You can set the categoryBits and maskBits of individual tiles, Tiled objects, and layers via Tiled properties. Tiles will inherit their layer’s values if they do not have their own. The Platformer - Angled PHYSICS 0v872 sample project includes a map, “map/AngledPhysics2”, which demonstrates physics collision filters.

There is no way to automatically hide layers when a sprite walks under them. I recommend hiding and unhiding layers using properties assigned to the tiles used to move between levels, such as stairs, as seen in CastleDemo. 

Thank you Dyson. My game will be multiplayer and players may be on different levels so it sounds like the categoryBits is the way to go. 

As I read about categoryBits. It makes sense, but I wonder if it will work for my situation of multiple players on different levels in the same game. If I change a categoryBit or maskBit won’t it change for the other players? I suppose not, if each individual’s device handles assigning and changing these variables, but seems like there would be sideeffects to that. It seems like I could write a function.

is there a way to write a function that detects a precolision with an object and if it is on another level have it filter or ignore the collision? Thanks Guys

Hmmm… Well, I guess what you could do is assign the categoryBits and maskBits to the layers and have those identical across all instances of your game for the multiple players. What you would do is alter the catBits and maskBits of the player sprite itself, by removing the physics body and re-adding it with the correct bits. This could be triggered by the same Tiled object you use to hide/show levels, positioned over the stair tiles or what have you. This might be simpler than writing a function for detecting collisions before they happen.

Check it out, it works great: 

–function for player to detect and ignore collisions with crap on other levels

function player:preCollision( event )

    local otherobject = event.other

    if mte.getLevel(otherobject.layer) ~= player.level then

        event.contact.isEnabled = false

        pr(otherobject)

    end

end

player:addEventListener( “preCollision” )

This brings out an interesting thing with the movespriteto function. It can’t move to the upper stairs because it is being blocked by the wall because of physics: See screenshot - there’s a gap between stairs so going up could bring someone to another area of the map. http://screencast.com/t/6PZ6JGxR

So I tried the movesprite function because it’s not linear movement, but it’s giving an error:stack traceback:

    [C]: ?

    …s/Work/home/!!IPAD GAMES/raiders/Raiders/MTE/mte.lua:10529: in function ‘moveSprite’

    …umes/Work/home/!!IPAD GAMES/raiders/Raiders/main.lua:534: in function ‘entersNewTile’

    …umes/Work/home/!!IPAD GAMES/raiders/Raiders/main.lua:558: in function <…umes/Work/home/!!IPAD GAMES/raiders/Raiders/main.lua:552>

    ?: in function <?:218>

Attempt to perform arithmetic on local ‘velX’ (a nil value)

I don’t use velX anymore because I’m using physics. 

Maybe I’m going about this wrong?

That function is a lot more concise than I thought it could be, very nice!

MoveSprite is little more than a convenience function, it does all of three things before passing its data internally to moveSpriteTo. It still requires a velX and velY argument even with physics- the correct syntax is moveSprite(sprite, velX, velY)- however it will not work any better for you than moveSpriteTo given the circumstances.

If you set your body.isBodyActive to false it will not collide with any other objects, so it could pass through the walls to reach the upper stair. The engine isn’t set up to move inactive physics objects, but this is easy enough to change. Go into mte.lua, find the update() function around line 9309, and find this bit of code starting at line 9315:

if not M.spritesFrozen then --UPDATE SPRITES for key,value in pairs(movingSprites) do if key and objects and objects[key].isMoving then --[[...]]-- local object = objects[key] local velX = object.deltaX[1] local velY = object.deltaY[1] if object.bodyType then --print(object.x, object.y) --print("\*\*", velX, velY) object.x = object.x + velX object.y = object.y + velY else local remainingVelX = abs(object.deltaX[1]) local remainingVelY = abs(object.deltaY[1]) local velXSign = 1 local velYSign = 1 ...

Edit the “if object.bodyType then” and “else” lines as follows:

if not M.spritesFrozen then --UPDATE SPRITES for key,value in pairs(movingSprites) do if key and objects and objects[key].isMoving then --[[...]]-- local object = objects[key] local velX = object.deltaX[1] local velY = object.deltaY[1] --if object.bodyType then if object.bodyType and object.isBodyActive then --print(object.x, object.y) --print("\*\*", velX, velY) object.x = object.x + velX object.y = object.y + velY --else elseif not object.bodyType or not object.isBodyActive then local remainingVelX = abs(object.deltaX[1]) local remainingVelY = abs(object.deltaY[1]) local velXSign = 1 local velYSign = 1 ...

The next update will include this fix as well.