Rotating around different points depending on controller input

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
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Rotating around different points depending on controller input

Post by nice »

Hello everone!
I'm working on a "Tank" game where you control a Tank (top-down view) with a controller, where you turn in the game by holding down one of the individual triggers on a controller (in this case, an Xbox 360 controller).

What I have right now
What I have right now is that the rotation happens in the middle of the "Tanks" body (The sprite size is 60 x 40) which is fine for testing purposes but that's not what I want.

What I want to happen
What I want to happen is that when you press one of the Triggers (left or right) the point of "rotation" changes to either the left or the right side of the Tank. When that happens (when you for example hold down the left trigger) it gives the Tank a wider turning radius. When you release the Trigger or hold down both, the point of rotation is in the "middle".

What I've tried
What I've tried is to mess around with the "Tank" Sprites origin offset (white "body" in the same link above) but that messes up from where the Tank is drawn from, making the "Tanks" body offset from everything else. There's this thread from 6 years ago that might(?) be similar to what I'm trying to do but I'm having difficulties to "translate" it to my needs and if it's applicable to today.

Also for this project, I'm using the "Baton" library.
Please let me know if something is unclear, I sometimes have the habit explaining problems vaguely.

Code

Code: Select all

local baton = require 'baton'

local input = baton.new {
  controls = {
    left = {'key:left'},
    right = {'key:right'},
    up = {'key:up'},
    down = {'key:down'},
    fire = {'key:x', 'button:a'},
    aimLeft = {'axis:leftx-', 'key:q'},
    aimRight = {'axis:leftx+', 'key:e'},
    aimUp = {'axis:lefty-'},
    aimDown = {'axis:lefty+'},
    turnTankLeft = {'axis:triggerright+', 'key:d'},
    turnTankRight = {'axis:triggerleft+', 'key:a'},
    forwardVelocity = {'axis:triggerleft+' and 'axis:triggerright+'}
  },
  pairs = {
    move = {'left', 'right', 'up', 'down'},
    aim = {'aimLeft','aimRight','aimUp','aimDown'}
  },
  joystick = love.joystick.getJoysticks()[1],
  deadzone = .33,
  }

  -- Common values
  theta = 4.71
  speed = 5


  Tank = {}
  -- Basically a new sprite (60x40)
  Tank.Sprite = love.graphics.newImage("Graphics/tankBody.png")
  -- Tank's position on X/Y
  Tank.posX = love.graphics:getWidth() * 0.5
  Tank.posY = love.graphics:getHeight() * 0.5
  -- Tank Radians (or Rotation)
  Tank.Radians = 0
  -- Scales up (or down) the size of sprite on X/Y
  Tank.scaleFactorX = 1
  Tank.scaleFactorY = 1
  -- Changes origin from where the Sprite is drawn
  --[[ MEMO to self: Consider moving originX/Y to original position
       and find out how to rotate in the middle without moving 
       the origin offset
    ]]
  Tank.originOffsetX = Tank.Sprite:getWidth() * 0.5
  Tank.originOffsetY = Tank.Sprite:getHeight() * 0.5
  
  -- Tank's Width/Height
  Tank.Width = Tank.Sprite:getWidth()
  Tank.Height = Tank.Sprite:getHeight()
  Tank.halfWidth = Tank.Width * 0.5
  Tank.halfHeight = Tank.Height * 0.5
  
  -- Tank Speed/Velocity
  Tank.rotationSpeed = 5
  Tank.Velocity = 0
  
  -- Tank Direction
  Tank.Direction = 1
  Tank.currentDirection = Tank.Direction
  Tank.Theta = theta -- Angle / Angle area
  
  Turret = {}
  -- Turret (Sprite 40x34)
  Turret.Sprite = love.graphics.newImage("Graphics/tankTurret.png")
  -- Turret's X/Y (Shares the same as the Tank)
  -- MEMO to self: Move turret's x/y "forwards a little
  Turret.posX = Tank.posX
  Turret.posY = Tank.posY
  -- Turret Radians (or Rotation)
  Turret.Radians = 0
  -- Scales up (or down) the size of sprite on X/Y
  Turret.scaleFactorX = 1
  Turret.scaleFactorY = 1
  -- Changes origin from where the Sprite is drawn
  Turret.originOffsetX = Turret.Sprite:getWidth() * 0.5
  Turret.originOffsetY = Turret.Sprite:getHeight() * 0.5
  -- Turret's Width/Height
  Turret.Width = Turret.Sprite:getWidth()
  Turret.Height = Turret.Sprite:getHeight()
  -- Turret's Speed/Velocity
  Turret.Velocity = 0
  
  -- Turret's Direction
  Turret.Direction = 0
  Turret.Theta = theta

  Barrel = {}
  -- Barrel Sprite (46x12)
  Barrel.Sprite = love.graphics.newImage("Graphics/tankBarrel.png")
  -- Barrel's X/Y (Shares the same as the Turret)
  Barrel.posX = Tank.posX
  Barrel.posY = Tank.posY
  -- Barrel' Radians (or Rotation)
  Barrel.Radians = 0
  -- Scales up (or down) the size of sprite on X/Y
  Barrel.scaleFactorX = 1
  Barrel.scaleFactorY = 1
  -- Changes origin from where the Sprite is drawn
  Barrel.originOffsetX = -20
  Barrel.originOffsetY = Barrel.Sprite:getHeight() * 0.5
  -- Barrel's Width/Height
  Barrel.Width = Barrel.Sprite:getWidth()
  Barrel.Height = Barrel.Sprite:getHeight()
  -- Barrel's Speed/Velocity
  Barrel.Velocity = 0
  -- Barrel's Direction
  Barrel.Direction = 0
  Barrel.Theta = theta
  
  Track = {}
  -- Track Sprite (50x12)
  Track.Sprite = love.graphics.newImage("Graphics/tankTrack.png")
  -- Track's X/Y (Shares the same as the Tank)
  Track.posX = Tank.posX
  Track.posY = Tank.posY
  -- Track's Radians (or Orientation)
  Track.Radians = 0
  -- Scales up (or down) the size of sprite on X/Y (two extra values to draw the sprite mirrored)
  -- REV = reverse
  Track.scaleFactorX = 1
  Track.scaleFactorY = 1
  Track.scaleFactorX_REV = -1
  Track.scaleFactorY_REV = -1
   -- Changes origin from where the Sprite is drawn
  Track.originOffsetX_LEFT = Track.Sprite:getWidth() - 25
  Track.originOffsetY_LEFT = Tank.Sprite:getHeight() - 8
  Track.originOffsetX_RIGHT = Track.Sprite:getWidth() - 25
  Track.originOffsetY_RIGHT = Tank.Sprite:getHeight() - 8
  -- Track's Width/Height
  Track.Width = Track.Sprite:getWidth()
  Track.Height = Track.Sprite:getHeight()
  -- Track's Speed/Velocity
  Track.Velocity = 0
  -- Track's Direction
  Track.Direction = 0
  Track.Theta = theta  
  
function Tank:update(dt)
  input:update()
  
  if input:pressed 'left' then
    print("left active")
  end
  
  if input:pressed 'right' then
    print("right active")
  end
  
  if input:pressed 'up' then
    print("up active")
  end
  
  if input:pressed 'down' then
    print("down active")
  end
  
  if input:pressed 'fire' then
    print("fire active")
  end

  if input:down 'turnTankLeft' then
    print("rotating left")
    Tank.Theta = Tank.Theta - 5 * dt
    Track.Theta = Track.Theta - 5 * dt
  end
  
  if input:down 'turnTankRight' then
    print("rotating right")
    Tank.Theta = Tank.Theta + 5 * dt
    Track.Theta = Track.Theta + 5 * dt
  end
  
  local x, y = input:getRaw'aim'
  turretAngle = math.atan2((y), (x))
  if input:pressed 'aim' then 
   turretAngle = math.atan2((y), (x))
  end
  
  --[[ forward velocity
  if input:down 'forwardVelocity' then
    print("forward velocity")
    Tank.Velocity = Tank.Velocity + 5 * dt
    Turret.Velocity = Turret.Velocity + 5 * dt
  else
    Tank.Velocity = 0
    Turret.Velocity = 0
  end
  ]]
  
  if input:down 'turnTankLeft' and input:down 'turnTankRight' then
    Tank.Velocity = Tank.Velocity + speed * dt
    Track.Velocity = Track.Velocity + speed * dt
  else
    Tank.Velocity = 0
    Track.Velocity = 0
  end

  -- m = math
  local mCos = math.cos
  local mSin = math.sin

  Tank.posX = Tank.posX + mCos(Tank.Theta) * Tank.Velocity
  Tank.posY = Tank.posY + mSin(Tank.Theta) * Tank.Velocity
  
  Turret.posX = Turret.posX + mCos(Tank.Theta) * Tank.Velocity
  Turret.posY = Turret.posY + mSin(Tank.Theta) * Tank.Velocity
  
  Barrel.posX = Barrel.posX + mCos(Tank.Theta) * Tank.Velocity
  Barrel.posY = Barrel.posY + mSin(Tank.Theta) * Tank.Velocity

  Track.posX = Track.posX + mCos(Tank.Theta) * Tank.Velocity
  Track.posY = Track.posY + mSin(Tank.Theta) * Tank.Velocity

end

function Tank:draw()

  love.graphics.print("angle: "..turretAngle, 10, 10)
  -- Tank Graphics
  love.graphics.draw(Tank.Sprite, 
                     Tank.posX, 
                     Tank.posY,
                     Tank.Theta,
                     Tank.scaleFactorX,
                     Tank.scaleFactorY,
                     Tank.originOffsetX,
                     rotPoint_CURRENT)

    -- Track Graphics (LEFT)
  love.graphics.draw(Track.Sprite,
                     Track.posX,
                     Track.posY,
                     Track.Theta,
                     Track.scaleFactorX,
                     Track.scaleFactorY,
                     Track.originOffsetX_LEFT,
                     Track.originOffsetY_LEFT)
  
  -- Track Graphics (RIGHT)
  love.graphics.draw(Track.Sprite,
                     Track.posX,
                     Track.posY,
                     Track.Theta,
                     Track.scaleFactorX,
                     Track.scaleFactorY_REV,
                     Track.originOffsetX_RIGHT,
                     Track.originOffsetY_RIGHT)
    
  -- Turret Graphics                 
  love.graphics.draw(Turret.Sprite,
                     Turret.posX,
                     Turret.posY,
                     turretAngle,
                     Turret.scaleFactorX,
                     Turret.scaleFactorY,
                     Turret.originOffsetX,
                     Turret.originOffsetY)
                     
  -- Barrel Graphics                 
  love.graphics.draw(Barrel.Sprite,
                     Barrel.posX,
                     Barrel.posY,
                     turretAngle,
                     Barrel.scaleFactorX,
                     Barrel.scaleFactorY,
                     Barrel.originOffsetX,
                     Barrel.originOffsetY)

  love.graphics.rectangle("fill", Turret.posX, Turret.posY, 2, 2)
end

return Tank
:awesome: Have a good day! :ultraglee:
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: Rotating around different points depending on controller input

Post by pgimeno »

Interesting problem. I would probably use separate "left engine" and "right engine" settings which would be affected by the controls. Since you have position and angle, one way to solve this is to update both separately as if they were independent points, and then calculate the new position and angle. It's not going to be very accurate but I think it will work for small dt's.

So, assuming 0° is looking east, think of it as two wheels joined by an axle, where you advance each wheel independently. You need the length of the axle, which I'll call A, so the distance from the centre to each wheel is A/2.

Now, your update would work like this:

Code: Select all

-- Calculate each wheel's position
-- left wheel
local wheel1x = Tank.posX - A/2 * sin(Tank.Theta)
local wheel1y = Tank.posY + A/2 * cos(Tank.Theta)
-- right wheel
local wheel2x = Tank.posX + A/2 * sin(Tank.Theta)
local wheel2y = Tank.posY - A/2 * cos(Tank.Theta)

-- Advance each wheel by its speed
wheel1x = wheel1x + cos(Tank.Theta) * wheel1speed * dt
wheel1y = wheel1y + sin(Tank.Theta) * wheel1speed * dt
wheel2x = wheel2x + cos(Tank.Theta) * wheel2speed * dt
wheel2y = wheel2y + sin(Tank.Theta) * wheel2speed * dt

-- Calculate the new position as the average of both
Tank.posX = (wheel1x + wheel2x) / 2
Tank.posY = (wheel1y + wheel2y) / 2

-- Calculate the new angle
Tank.Theta = atan2(wheel1x - wheel2x, wheel2y - wheel1y)
(Edited at 2019-07-29T14:35Z to fix a bug)

Edit2: Proof of concept:

Use W, S (or Z, S in a French keyboard) for left wheel, I, K for right wheel. This method of control is the same used by Battlezone.

Code: Select all

-- cache
local cos, sin, atan2 = math.cos, math.sin, math.atan2
local lg = love.graphics
local isDown = love.keyboard.isScancodeDown

-- player x, y, angle
local px, py, pa = 0, 0, 0

-- distance between centre and each wheel
local axle = 48

-- speed per wheel while key is down
local v = 25

function love.update(dt)
  -- speed of left/right wheel
  local lspeed, rspeed = 0, 0

  if isDown("w") then lspeed = lspeed + v end
  if isDown("s") then lspeed = lspeed - v end
  if isDown("i") then rspeed = rspeed + v end
  if isDown("k") then rspeed = rspeed - v end

  local plx, ply, prx, pry, pcx, pcy

  local c, s = cos(pa), sin(pa)
  plx = px + axle * s + lspeed * dt * c
  ply = py - axle * c + lspeed * dt * s
  prx = px - axle * s + rspeed * dt * c
  pry = py + axle * c + rspeed * dt * s

  px = (prx + plx) / 2
  py = (pry + ply) / 2
  pa = atan2(plx - prx, pry - ply)
end

function love.draw()
  lg.translate(lg.getWidth()/2, lg.getHeight()/2)

  local s, c = sin(pa), cos(pa)
  lg.polygon("fill",
     px - s * -axle, py + c * -axle,
     px + c * 15, py + s * 15,
     px - s *  axle, py + c *  axle
  )
end

function love.keypressed(k) return k == "escape" and love.event.quit() end
User avatar
Ref
Party member
Posts: 702
Joined: Wed May 02, 2012 11:05 pm

Re: Rotating around different points depending on controller input

Post by Ref »

Could it be so simple as changing ox and oy in

Code: Select all

love.graphics.draw( drawable, x, y, r, sx, sy, ox, oy, kx, ky )
(Probably off topic but 'Tanks' do rotate around their center.)
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: Rotating around different points depending on controller input

Post by pgimeno »

Ref wrote: Mon Jul 29, 2019 3:47 pm Could it be so simple as changing ox and oy in

Code: Select all

love.graphics.draw( drawable, x, y, r, sx, sy, ox, oy, kx, ky )
No. In order to be able to perform several different manoeuvres, you'd also need a method to calculate the new x and y when you change ox and oy to a new position, which isn't trivial. And then that'd only be useful for drawing; you still need the central position and angle for collisions, for placing the turret and cannon and possibly other stuff.

Ref wrote: Mon Jul 29, 2019 3:47 pm (Probably off topic but 'Tanks' do rotate around their center.)
My PoC above allows that.
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: Rotating around different points depending on controller input

Post by nice »

pgimeno wrote: Mon Jul 29, 2019 10:13 am Interesting problem. I would probably use separate "left engine" and "right engine" settings which would be affected by the controls. Since you have position and angle, one way to solve this is to update both separately as if they were independent points, and then calculate the new position and angle. It's not going to be very accurate but I think it will work for small dt's.
Thanks for the response, I didn't see any notification icons for the post which was strange :?
The idea I have for the later stages of developing this game is to add forwards/backwards "gears" to the game, so instead of having a wide turning radius when you want to turn around, you have one "track" in forwards gear and on in backwards to make the Tank turn around in the middle.

While we're at it, I have another question lingering. How do you make the rotation happen in the middle of the object without affecting ox/oy?
What I'm trying to say is that I want to rotate the object without poking around with the origin offset, I would like to keep the origin of the ox/oy at 0 when I start fiddling with collision detection.
Ref wrote: Mon Jul 29, 2019 3:47 pm Could it be so simple as changing ox and oy in

Code: Select all

love.graphics.draw( drawable, x, y, r, sx, sy, ox, oy, kx, ky )
(Probably off topic but 'Tanks' do rotate around their center.)

If we're going to be technical, yes Tanks do rotate in the center but that's by locking one side and moving the other. However, affecting the ox/oy was my first thought too but doing that makes the "body" of the Tank having a life of it's own.
:awesome: Have a good day! :ultraglee:
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: Rotating around different points depending on controller input

Post by pgimeno »

nice wrote: Mon Jul 29, 2019 10:18 pm Thanks for the response, I didn't see any notification icons for the post which was strange :?
That's because I didn't quote you. Now I am quoting you, so you'll get a notification :)

You can subscribe to the thread if you want to ensure you get notifications for any new posts.

nice wrote: Mon Jul 29, 2019 10:18 pm While we're at it, I have another question lingering. How do you make the rotation happen in the middle of the object without affecting ox/oy?
What I'm trying to say is that I want to rotate the object without poking around with the origin offset, I would like to keep the origin of the ox/oy at 0 when I start fiddling with collision detection.
I don't think you really want that. It's easier to find the four vertices given ox/oy and the horizontal and vertical size, than the reverse. What data will you need for collision detection? Would the four vertices suffice? Maybe just the top left vertex and the angle?

The problem you've stated is a special case of the one I told Ref that would need to be solved in order to use ox/oy in the draw function; as I said to him, that's not trivial. I can try to come up with the formulas if you really need them. They will be simpler if you don't need scaling or shearing, by the way, so please let me know if you need those before I work them out.
User avatar
Ref
Party member
Posts: 702
Joined: Wed May 02, 2012 11:05 pm

Re: Rotating around different points depending on controller input

Post by Ref »

Pgimeno obviously has a better understanding of what you want to accomplish.
From your initial post I could see how you could accomplish what you asked using ox and oy and applying an offset to the x and y coordinates.
For the life of me, I can't visualize exactly how you want the mechanics to work but that just me.
Best!
edit: Guess my problem (among many) is that I am only considering image orientation and not path change.
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: Rotating around different points depending on controller input

Post by nice »

Ref wrote: Wed Jul 31, 2019 3:37 pm Pgimeno obviously has a better understanding of what you want to accomplish.
From your initial post I could see how you could accomplish what you asked using ox and oy and applying an offset to the x and y coordinates.
For the life of me, I can't visualize exactly how you want the mechanics to work but that just me.
Best!
edit: Guess my problem (among many) is that I am only considering image orientation and not path change.
It's the thought that counts and there's no shame to tryu to help :awesome:
:awesome: Have a good day! :ultraglee:
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 19 guests