[SOLVED] Help with STI movement implementation

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
giantofbabil
Prole
Posts: 32
Joined: Tue Dec 15, 2015 6:07 pm

[SOLVED] Help with STI movement implementation

Post by giantofbabil »

Had problems setting up movement with Box2D, now they are fixed.

level01.lua

Code: Select all

--set up for STI
local sti = require 'sti'
local physics = require 'assets.loaders.physics'

function loadLevel_01()
 
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --grab window size
  windowWidth  = love.graphics.getWidth()
  windowHeight = love.graphics.getHeight()
  
  --set world "meter" size in pixels (tile length)
  love.physics.setMeter(64)
  
  --load a map exported to lua from Tiled
  map = sti.new('assets/maps/map01.lua', {'box2d'})
  
  --prepare physics world with horizontal and vertical gravity
  world = love.physics.newWorld(0, 3500, true)
  
  --prepare collision objects
  map:box2d_init(world)
  world:setCallbacks(beginContact)
  
  --create custom layer for sprites
  map:addCustomLayer('Sprite Layer', 4)
  
  --add data to custom layer
  spriteLayer = map.layers['Sprite Layer']
  
  spriteLayer.sprites = {
               player = {
                 img = love.graphics.newImage('assets/sprites/gus.png'),
                 x = 250,
                 y = 1200,
                 r = 0}}
  
  --call physics setup for spriteLayer
  spritePhysics()

end

function updateLevel_01(dt)
  
  --map update for sti
  world:update(dt)
  --MOVEMENT SETUP------------------------------------------------------------------------------------------------------------
  local sprite = map.layers['Sprite Layer'].sprites.player
  local x, y = 0, 0
	if love.keyboard.isDown('a', 'left') and not sprite.isOnWall then 
    x = x - 8000
  elseif love.keyboard.isDown('a', 'left') and sprite.isOnWall then
    --code for wall sticking animations
  end
	if love.keyboard.isDown('d', 'right') and not sprite.isOnWall then 
    x = x + 8000 
  elseif love.keyboard.isDown('d', 'right') and sprite.isOnWall then
    --code for wall sticking animations
  end
  sprite.body:applyForce(x, y)
	sprite.x, sprite.y = sprite.body:getWorldCenter()
  
  --JUMPING CODE--------------------------------------------------------------------------------------------------------------
  
  function beginContact(a, b, ctct)
    if a == sprite.groundSensor or b == sprite.groundSensor then --sprite is on ground
      sprite.isOnGround = true
      sprite.doubleJump = false
    elseif a == sprite.wallSensor or b == sprite.wallSensor and sprite.isOnWall == false then --sprite is on wall
      sprite.wallCheck = true
    end
  end
      
  world:setCallbacks(beginContact)
    
  function love.keypressed(key)
    if key == ' ' and sprite.isOnGround then --jump and allow double jump
      sprite.doubleJump = true
      sprite.body:applyLinearImpulse(0, -8000, sprite.x, sprite.y)
      sprite.isOnGround = false
    elseif key == ' ' and sprite.doubleJump then --double jump
      sprite.body:applyLinearImpulse(0, -6500, sprite.x, sprite.y)
      sprite.doubleJump = false
    elseif key == 'lshift' or 'rshift' and not sprite.isOnGround and sprite.wallCheck then --stick to wall
      sprite.wallCheck = false
      sprite.isOnWall   = true
      sprite.doubleJump = false
      sprite.body:setActive(false)
    elseif key == ' ' and sprite.isOnWall then --jump off of wall
      sprite.body:setActive(true)
      sprite.body:applyLinearImpulse(1000, -6500, sprite.x, sprite.y)
      sprite.isOnWall = false
    end
  end
  
  map:update(dt)
end

function drawLevel_01()
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --translation based on player's x/y
  local translateX = 0
  local translateY = 0
  
  --draw range culls unnecessary tiles
  map:setDrawRange(-translateX, -translateY, windowWidth, windowHeight)
  
  --draw map and all objects within
  map:draw()
  
  --draw collision map (useful for debugging) comment out to get rid of collision boxes
  love.graphics.setColor(255, 0, 0, 255)
  map:box2d_draw()
  local sprite = map.layers['Sprite Layer'].sprites.player
  love.graphics.setColor(255, 0, 0, 255)
	love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.shape:getPoints()))
  love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.gShape:getPoints()))
  love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.wShape:getPoints()))
  love.graphics.circle("line",sprite.x, sprite.y + 20, 38)

  -----------------------------------------------------------------------------------------------------------------------------
  
end
physics.lua

Code: Select all

--Calls for physics set up

--Set up Sprite Layer and associated physics
function spritePhysics()
  
  --update callback for custom layer
  function spriteLayer:update(dt)
    for _, sprite in pairs(self.sprites) do
      
    end
  end
  
  --draw callback for custom layer
  function spriteLayer:draw()
    for _, sprite in pairs(self.sprites) do
      local x = math.floor(sprite.x)
      local y = math.floor(sprite.y)
      local r = sprite.r
      love.graphics.draw(sprite.img, x, y - 10, r, 1, 1, 48,68 )
    end
  end
  
  --set up sprite physics
  local sprite = map.layers['Sprite Layer'].sprites.player
  sprite.body = love.physics.newBody(world, sprite.x/2, sprite.y/2, 'dynamic')
  sprite.body:setMassData(0, 0, 200, 0)
  sprite.body:setLinearDamping(5)
	sprite.body:setFixedRotation(true)
	sprite.shape   = love.physics.newRectangleShape(0, 0, 75, 80, 0)
  sprite.gShape  = love.physics.newRectangleShape(1, 78, 70, 10, 0)
  sprite.fShape  = love.physics.newCircleShape(0, 40, 38)
  sprite.wShape  = love.physics.newRectangleShape(0, 0, 85, 50, 0)
  sprite.fixture = love.physics.newFixture(sprite.body, sprite.shape)
  sprite.feet    = love.physics.newFixture(sprite.body, sprite.fShape)
  sprite.groundSensor = love.physics.newFixture(sprite.body, sprite.gShape)
  sprite.wallSensor   = love.physics.newFixture(sprite.body, sprite.wShape)
  sprite.groundSensor:setSensor(true)
  sprite.wallSensor:setSensor(true)
  sprite.isOnGround = false
  sprite.isOnWall   = false
  sprite.wallCheck  = false
  sprite.doubleJump = false
  
end
Any help at all is appreciated!
Last edited by giantofbabil on Fri Dec 18, 2015 10:39 pm, edited 9 times in total.

Code: Select all

if enemy == lowerClassSaiyan and powerLevel > 9000 then
    love.graphics.print("What?! 9000?! There's no way that could be right!", 10, 200)
else
    love.graphics.print("You fool!", 10, 200)
end
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: Help with STI movement implementation

Post by pgimeno »

giantofbabil wrote: 1) Implement jumping and fix the physics because I probably have it set up horribly. Right now my sprite just "flies" when you hold space bar. Tried to add mass to the sprite, but that just made things worse no matter what I set gravity to I couldn't get it to work right :P
I haven't tried, but I guess that you need to apply the impulse only when the key goes from not down to down:

Code: Select all

local spaceWasDown = false
...

  if love.keyboard.isDown(' ') then 
    if not spaceWasDown then
        sprite.body:applyLinearImpulse(0, -100, sprite.x, sprite.y)
        spaceWasDown = true
    end
  else
    spaceWasDown = false
  end


giantofbabil wrote: 2) Right now my sprite's collision is a box the size of the image. I would like it to be more like an oval, or "pill" shape but I don't know how to do that I can only find options for circles which don't work all that well either.
Can't help with that, sorry. Maybe two circles and a box would make a pill-shaped collision skeleton.
User avatar
giantofbabil
Prole
Posts: 32
Joined: Tue Dec 15, 2015 6:07 pm

Re: Help with STI movement implementation

Post by giantofbabil »

Your jumping idea didn't work, but I did get it to work! Your idea made me revisit using the function love.keypressed, at first it didn't work but then I added mass to to the sprite and cranked up the velocity and I now have jumping!
pgimeno wrote:Can't help with that, sorry. Maybe two circles and a box would make a pill-shaped collision skeleton.
Working on this now, I had a problem earlier when trying to do this where when I tried to make a second circle on the same physics object it would overwrite the first one. Persistence seems to be key though...

Now I just have to find a way to make the game recognize when the character is standing on a platform so you can't jump in the air infinite times.

Code: Select all

if enemy == lowerClassSaiyan and powerLevel > 9000 then
    love.graphics.print("What?! 9000?! There's no way that could be right!", 10, 200)
else
    love.graphics.print("You fool!", 10, 200)
end
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Help with STI movement implementation

Post by s-ol »

You can add a "feet" fixture that is below the body (but does not extend over the full character's width, or you get wall-jumping aswell) and set it to be a sensor (https://www.love2d.org/wiki/Fixture:setSensor) then use the world callbacks (https://www.love2d.org/wiki/World:setCallbacks) to store a isOnGround Boolean in your player.
Alternatively you can query a rectangle below your player with https://www.love2d.org/wiki/World:queryBoundingBox but this is very inaccurate for rotated environment pieces and anything that is not a box.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
giantofbabil
Prole
Posts: 32
Joined: Tue Dec 15, 2015 6:07 pm

Re: Help with STI movement implementation

Post by giantofbabil »

S0lll0s wrote:You can add a "feet" fixture that is below the body (but does not extend over the full character's width, or you get wall-jumping aswell) and set it to be a sensor (https://www.love2d.org/wiki/Fixture:setSensor) then use the world callbacks (https://www.love2d.org/wiki/World:setCallbacks) to store a isOnGround Boolean in your player.
Alternatively you can query a rectangle below your player with https://www.love2d.org/wiki/World:queryBoundingBox but this is very inaccurate for rotated environment pieces and anything that is not a box.
I tried to set this up but I'm having a problem. The fixture I made is called sprite.groundSensor, and I set it a boolean called isOnGround to go true when this fixture begins contact and false when it ends contact. The problem is that sprite.groundSensor is never triggering contact, and I think it's because it's collidable for some reason no it's never overlapping the collidable surfaces like a sensor is supposed to(I know this because now I can't jump at all), but I don't know how to make it not collidable. Here's the updated code:

Code: Select all

--set up for STI
local sti = require 'sti'

function loadLevel_01()
  
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --grab window size
  windowWidth  = love.graphics.getWidth()
  windowHeight = love.graphics.getHeight()
  
  --set world "meter" size in pixels (tile length)
  love.physics.setMeter(64)
  
  --load a map exported to lua from Tiled
  map = sti.new('assets/maps/map01.lua', {'box2d'})
  
  --prepare physics world with horizontal and vertical gravity
  world = love.physics.newWorld(0, 3500, true)
  
  --prepare collision objects
  map:box2d_init(world)
  
  --create custom layer
  map:addCustomLayer('Sprite Layer', 2)
  
  --add data to custom layer
  local spriteLayer = map.layers['Sprite Layer']
  
  spriteLayer.sprites = {
               player = {
                 img = love.graphics.newImage('assets/sprites/gus.png'),
                 x = 96,
                 y = 636,
                 r = 0}
               }

  --update callback for custom layer
  function spriteLayer:update(dt)
    for _, sprite in pairs(self.sprites) do
      
    end
  end
  
  --draw callback for custom layer
  function spriteLayer:draw()
    for _, sprite in pairs(self.sprites) do
      local x = math.floor(sprite.x)
      local y = math.floor(sprite.y)
      local r = sprite.r
      love.graphics.draw(sprite.img, x, y - 10, r, 1, 1, 48,68 )
    end
  end
  
  --set up sprite physics
  local sprite = map.layers['Sprite Layer'].sprites.player
  sprite.body = love.physics.newBody(world, sprite.x/2, sprite.y/2, 'dynamic')
  sprite.body:setMassData(0, 0, 200, 0)
  sprite.body:setLinearDamping(5)
	sprite.body:setFixedRotation(true)
	sprite.shape   = love.physics.newRectangleShape(0, 0, 75, 120, 0)
	sprite.fixture = love.physics.newFixture(sprite.body, sprite.shape)
  sprite.feet    = love.physics.newRectangleShape(0, 57, 70, 5, 0)
  sprite.groundSensor = love.physics.newFixture(sprite.body, sprite.feet)
  sprite.groundSensor:setSensor()
  
  -----------------------------------------------------------------------------------------------------------------------------
    
end

function updateLevel_01(dt)
  
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --map update for sti
  world:update(dt)
  --MOVEMENT SETUP------------------------------------------------------------------------------------------------------------
  local sprite = map.layers['Sprite Layer'].sprites.player
  local x, y = 0, 0
	if love.keyboard.isDown('a','left')	 then x = x - 8000 end
	if love.keyboard.isDown('d','right') then x = x + 8000 end
  sprite.body:applyForce(x, y)
	sprite.x, sprite.y = sprite.body:getWorldCenter()
  
  --JUMPING CODE--------------------------------------------------------------------------------------------------------------
  world:setCallbacks(beginContact, endContact)
  local isOnGround = false
  if sprite.groundSensor == beginContact then
    isOnGround = true
  elseif sprite.groundSensor == endContact then
    isOnGround = false
  end
    
  function love.keypressed(key)
    if key == ' ' and isOnGround then
    sprite.body:applyLinearImpulse(0, -5000, sprite.x, sprite.y)
    end
  end
  
  map:update(dt)
end

function drawLevel_01()
  
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --translation based on player's x/y
  local translateX = 0
  local translateY = 0
  
  --draw range culls unnecessary tiles
  map:setDrawRange(-translateX, -translateY, windowWidth, windowHeight)
  
  --draw map and all objects within
  map:draw()
  
  --draw collision map (useful for debugging) comment out to get rid of collision boxes
  love.graphics.setColor(255, 0, 0, 255)
  map:box2d_draw()
  local sprite = map.layers['Sprite Layer'].sprites.player
  love.graphics.setColor(255, 0, 0, 255)
	love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.shape:getPoints()))
  love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.feet:getPoints()))

  -----------------------------------------------------------------------------------------------------------------------------
  
end

Code: Select all

if enemy == lowerClassSaiyan and powerLevel > 9000 then
    love.graphics.print("What?! 9000?! There's no way that could be right!", 10, 200)
else
    love.graphics.print("You fool!", 10, 200)
end
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Help with STI movement implementation

Post by s-ol »

giantofbabil wrote:
S0lll0s wrote:You can add a "feet" fixture that is below the body (but does not extend over the full character's width, or you get wall-jumping aswell) and set it to be a sensor (https://www.love2d.org/wiki/Fixture:setSensor) then use the world callbacks (https://www.love2d.org/wiki/World:setCallbacks) to store a isOnGround Boolean in your player.
Alternatively you can query a rectangle below your player with https://www.love2d.org/wiki/World:queryBoundingBox but this is very inaccurate for rotated environment pieces and anything that is not a box.
I tried to set this up but I'm having a problem. The fixture I made is called sprite.groundSensor, and I set it a boolean called isOnGround to go true when this fixture begins contact and false when it ends contact. The problem is that sprite.groundSensor is never triggering contact, and I think it's because it's collidable for some reason no it's never overlapping the collidable surfaces like a sensor is supposed to(I know this because now I can't jump at all), but I don't know how to make it not collidable. Here's the updated code:

Code: Select all

--set up for STI
local sti = require 'sti'

function loadLevel_01()
  
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --grab window size
  windowWidth  = love.graphics.getWidth()
  windowHeight = love.graphics.getHeight()
  
  --set world "meter" size in pixels (tile length)
  love.physics.setMeter(64)
  
  --load a map exported to lua from Tiled
  map = sti.new('assets/maps/map01.lua', {'box2d'})
  
  --prepare physics world with horizontal and vertical gravity
  world = love.physics.newWorld(0, 3500, true)
  
  --prepare collision objects
  map:box2d_init(world)
  
  --create custom layer
  map:addCustomLayer('Sprite Layer', 2)
  
  --add data to custom layer
  local spriteLayer = map.layers['Sprite Layer']
  
  spriteLayer.sprites = {
               player = {
                 img = love.graphics.newImage('assets/sprites/gus.png'),
                 x = 96,
                 y = 636,
                 r = 0}
               }

  --update callback for custom layer
  function spriteLayer:update(dt)
    for _, sprite in pairs(self.sprites) do
      
    end
  end
  
  --draw callback for custom layer
  function spriteLayer:draw()
    for _, sprite in pairs(self.sprites) do
      local x = math.floor(sprite.x)
      local y = math.floor(sprite.y)
      local r = sprite.r
      love.graphics.draw(sprite.img, x, y - 10, r, 1, 1, 48,68 )
    end
  end
  
  --set up sprite physics
  local sprite = map.layers['Sprite Layer'].sprites.player
  sprite.body = love.physics.newBody(world, sprite.x/2, sprite.y/2, 'dynamic')
  sprite.body:setMassData(0, 0, 200, 0)
  sprite.body:setLinearDamping(5)
	sprite.body:setFixedRotation(true)
	sprite.shape   = love.physics.newRectangleShape(0, 0, 75, 120, 0)
	sprite.fixture = love.physics.newFixture(sprite.body, sprite.shape)
  sprite.feet    = love.physics.newRectangleShape(0, 57, 70, 5, 0)
  sprite.groundSensor = love.physics.newFixture(sprite.body, sprite.feet)
  sprite.groundSensor:setSensor()
  
  -----------------------------------------------------------------------------------------------------------------------------
    
end

function updateLevel_01(dt)
  
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --map update for sti
  world:update(dt)
  --MOVEMENT SETUP------------------------------------------------------------------------------------------------------------
  local sprite = map.layers['Sprite Layer'].sprites.player
  local x, y = 0, 0
	if love.keyboard.isDown('a','left')	 then x = x - 8000 end
	if love.keyboard.isDown('d','right') then x = x + 8000 end
  sprite.body:applyForce(x, y)
	sprite.x, sprite.y = sprite.body:getWorldCenter()
  
  --JUMPING CODE--------------------------------------------------------------------------------------------------------------
  world:setCallbacks(beginContact, endContact)
  local isOnGround = false
  if sprite.groundSensor == beginContact then
    isOnGround = true
  elseif sprite.groundSensor == endContact then
    isOnGround = false
  end
    
  function love.keypressed(key)
    if key == ' ' and isOnGround then
    sprite.body:applyLinearImpulse(0, -5000, sprite.x, sprite.y)
    end
  end
  
  map:update(dt)
end

function drawLevel_01()
  
  --STI INITIALIZATION CODE---------------------------------------------------------------------------------------------------
  --translation based on player's x/y
  local translateX = 0
  local translateY = 0
  
  --draw range culls unnecessary tiles
  map:setDrawRange(-translateX, -translateY, windowWidth, windowHeight)
  
  --draw map and all objects within
  map:draw()
  
  --draw collision map (useful for debugging) comment out to get rid of collision boxes
  love.graphics.setColor(255, 0, 0, 255)
  map:box2d_draw()
  local sprite = map.layers['Sprite Layer'].sprites.player
  love.graphics.setColor(255, 0, 0, 255)
	love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.shape:getPoints()))
  love.graphics.polygon("line", sprite.body:getWorldPoints(sprite.feet:getPoints()))

  -----------------------------------------------------------------------------------------------------------------------------
  
end
You are using setWorldCallbacks wrong. The two parameters are supposed to be functions called when contacts begin or end. Here is what that portion of your code should look like:

Code: Select all


function beginContact(a, b, ctct)
  if a == sprite.groundSensor or b == sprite.groundSensor then
     isOnGround = true
  end
end
function endContact(a, b, ctct)
  if a == sprite.groundSensor or b == sprite.groundSensor then
     isOnGround = false
  end
end


 world:setCallbacks(beginContact, endContact)
of course you need to make isOnGround a non-local variable (possibly move it into the "sprite" table aswell) and you need to take care that the two functions are defined by the time you call :setCallbacks (and only do that once).

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
giantofbabil
Prole
Posts: 32
Joined: Tue Dec 15, 2015 6:07 pm

Re: Help with STI movement implementation

Post by giantofbabil »

Oh my god what a ride, thanks so much! Just so you know though the code you gave me didn't work consistently so I've been working at it and this is how I finally got it to work right on every ground contact:

Code: Select all

  --JUMPING CODE--------------------------------------------------------------------------------------------------------------

  function beginContact(a, b, ctct)
    if a == sprite.groundSensor or b == sprite.groundSensor then
      sprite.isOnGround = true
    end
  end
      
    world:setCallbacks(beginContact)
    
  function love.keypressed(key)
    if key == ' ' and sprite.isOnGround then
    sprite.body:applyLinearImpulse(0, -5000, sprite.x, sprite.y)
    sprite.isOnGround = false
    end
  end
I'll also note that I had the sensor set up wrong it needed to be sprite.groundSensor:setSensor(true) and I didn't have the true in there so my sensor wasn't actually a sensor at first :P

You saved me though I never would've figured out the callback functions without your help thanks :awesome:

Code: Select all

if enemy == lowerClassSaiyan and powerLevel > 9000 then
    love.graphics.print("What?! 9000?! There's no way that could be right!", 10, 200)
else
    love.graphics.print("You fool!", 10, 200)
end
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 55 guests