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

Rotating around different points depending on controller input

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],
}

-- 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
-- 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
-- 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
-- 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
-- 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

Have a good day!

pgimeno
Party member
Posts: 1937
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Rotating around different points depending on controller input

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


Ref
Party member
Posts: 686
Joined: Wed May 02, 2012 11:05 pm

Re: Rotating around different points depending on controller input

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.)

pgimeno
Party member
Posts: 1937
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Rotating around different points depending on controller input

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.

nice
Party member
Posts: 188
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: Rotating around different points depending on controller input

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.
Have a good day!

pgimeno
Party member
Posts: 1937
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Rotating around different points depending on controller input

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.

Ref
Party member
Posts: 686
Joined: Wed May 02, 2012 11:05 pm

Re: Rotating around different points depending on controller input

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.

nice
Party member
Posts: 188
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: Rotating around different points depending on controller input

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
Have a good day!

Who is online

Users browsing this forum: drfractal, Google [Bot] and 11 guests