Shoot from same point of a rotating image - lua

I am trying to make a basic prototype for a top down shooter in LOVE but I am having some issues getting the bullet to shoot from the same point of the image when it is rotated. This is the code I have so far which works fine as the bullet shoots from the center of the image but I want it to shoot from the right hand side of the sprite where the gun is:
function love.load()
love.graphics.setBackgroundColor(54, 172, 248)
player = love.graphics.newImage('/assets/images/player.png')
playerX = 300
playerY = 300
playerSpeed = 200
bullets = { }
bulletSpeed = 250
cursor = love.mouse.newCursor('assets/images/crosshair.png', 24, 24);
love.mouse.setCursor(cursor);
end
function love.update(dt)
-- Get mouse position to rotate player
local mouseX, mouseY = love.mouse.getPosition()
playerRotation = math.atan2(mouseY - playerY, mouseX - playerX);
-- Keyboard input to move the player
if love.keyboard.isDown('s') then
playerY = playerY + playerSpeed * dt
elseif love.keyboard.isDown('w') then
playerY = playerY - playerSpeed * dt
end
if love.keyboard.isDown('a') then
playerX = playerX - playerSpeed * dt
elseif love.keyboard.isDown('d') then
playerX = playerX + playerSpeed * dt
end
-- update all bullets position
for i, v in ipairs(bullets) do
v.x = v.x + (v.dx * dt)
v.y = v.y + (v.dy * dt)
end
end
function love.draw(dt)
-- Draw player
love.graphics.draw(player, playerX, playerY, playerRotation, 0.5, 0.5, player:getWidth() / 2, player:getHeight() / 2);
-- Draw all bullets
love.graphics.setColor(128, 128, 128)
for i, v in ipairs(bullets) do
love.graphics.circle("fill", v.x, v.y, 3);
end
end
function love.mousepressed(x, y, button)
if button == 1 then
local startX = playerX;
local startY = playerY;
local mouseX = x
local mouseY = y
local angle = math.atan2((mouseY - startY), (mouseX - startX))
local bulletDx = bulletSpeed * math.cos(angle)
local bulletDy = bulletSpeed * math.sin(angle)
table.insert(bullets, {x = startX , y = startY, dx = bulletDx, dy = bulletDy})
end
end
How do I draw it from the same point of the sprite as it is rotated?

If I got you correctly you have an image representing your player. (playerX, playerY) is the position of your player where the center of your image is.
Atm you start your bullets at the image center.
So the starting point of your bullets is invariant to player rotation.
If you move the gun away from the player center it rotates around that point where the radius is the distance of the gun position to the player position.
For distance 1 you can refer to the unit circle
So simply multiply that with your radius so:
local gunXRotated = playerX + r * math.cos(t)
local gunYRotated = playerY + r * math.sin(t)
where t is your rotation angle and r is the Euclidean distance between player center and gun muzzle.
Then simply use the new gun coordinates as the origin of your bullets.

Related

Lua Mouse Drag Quaternion

I am looking for a bit of help with this lua script I am making. I want to use Quaternion's to rotate an object based on your mouse movement/drag. I am using this library for my Quaternion https://github.com/topameng/CsToLua/ . I have got the object to move but all the rotations are messed up, here is a link to a video of what I have so far http://img.bcdojrp.net/videos/uploads/2021-03-17%2003-58-07_Trim.mp4 I have spent hours looking this over and can't find what to change... any help is appreciated, thanks!
function GetCursor()
local sx, sy = GetActiveScreenResolution()
local cx, cy = GetNuiCursorPosition()
local cx, cy = (cx / sx) + 0.008, (cy / sy) + 0.027
return cx, cy
end
if dragging then
-- this is the screen position
local intx, inty = GetCursor()
local deltaMove = {
['x'] = intx-previousMousePosition.x,
['y'] = inty-previousMousePosition.y
}
local deltaRotationQuaternion = Quaternion.Euler(deltaMove.y * 40, deltaMove.x * 40, 0)
local x,y,z,w = GetEntityQuaternion(curObject)
local quatNew = deltaRotationQuaternion.__mul(Quaternion.New(x,y,z,w), deltaRotationQuaternion)
local valX, valY, valZ, valW = quatNew.x, quatNew.y, quatNew.z, quatNew.w
SetEntityQuaternion(curObject, valX, valY, valZ, valW)
--cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);
previousMousePosition.x = intx
previousMousePosition.y = inty
else
previousMousePosition = {
['x'] = 0,
['y'] = 0
}
end

How to "rotate" an ellipse?

Using this:
local W, H = 100, 50
function love.draw()
love.graphics.translate(love.graphics.getWidth()/2,love.graphics.getHeight()/2)
for i = 1, 360 do
local I = math.rad(i)
local x,y = math.cos(I)*W, math.sin(I)*H
love.graphics.line(0, 0, x, y)
end
end
I can connect a line with the center of an ellipse (with length W and height H) and the edge. How do you 'rotate' the ellipse around it's center, with a parameter R? I know you can sort of do it with love.graphics.ellipse and love.graphics.rotate but is there any way I can get the coordinates of the points on a rotated ellipse?
This is a Trigonometry problem, here is how the basic 2D rotation work. Imagine a point located at (x,y). If you want to rotate that point around the origin(in your case 0,0) by the angle θ, the coordinates of the new point would be located at (x1,y1) by using the following transformation
x1 = xcosθ − ysinθ
y1 = ycosθ + xsinθ
In your example, I added a new ellipse after rotations
function love.draw()
love.graphics.translate(love.graphics.getWidth()/2,love.graphics.getHeight()/2)
for i = 1, 360, 5 do
local I = math.rad(i)
local x,y = math.cos(I)*W, math.sin(I)*H
love.graphics.setColor(0xff, 0, 0) -- red
love.graphics.line(0, 0, x, y)
end
-- rotate by angle r = 90 degree
local r = math.rad(90)
for i = 1, 360, 5 do
local I = math.rad(i)
-- original coordinates
local x = math.cos(I) * W
local y = math.sin(I) * H
-- transform coordinates
local x1 = x * math.cos(r) - y * math.sin(r)
local y1 = y * math.cos(r) + x * math.sin(r)
love.graphics.setColor(0, 0, 0xff) -- blue
love.graphics.line(0, 0, x1, y1)
end
end

Change angle (rotation) of entity in steering (seeking) behavior in 2d game

I'm developing 2d space shooter with LOVE2D in Lua and as I'm not math and physics expert I got some questions with implementing steering behavior for enemies.
I finally managed to make enemy "seeking" for a player and it looks like:
Code that makes it work (formula is taken from this answer - https://stackoverflow.com/a/2561054/2117550):
function Enemy:seek ()
local dx = self.player.x - self.x - self.xvel
local dy = self.player.y - self.y - self.yvel
-- normalize
local len = math.sqrt(dx * dx + dy * dy)
dx, dy = dx / len, dy / len
return dx, dy
end
Then I use my seek function in update:
function Enemy:update (dt)
-- seeking acceleration
local dx, dy = self:seek()
-- what is the proper way of calculating enemies rotation?
-- self.rotation = math.atan2(dx, dy) + ANGLE_ACCELERATION * dt
-- update velocity
self.xvel = self.xvel + dx * MAX_SPEED * dt -- * math.cos(self.rotation) it's not needed anymore?
self.yvel = self.yvel + dy * MAX_SPEED * dt -- * math.sin(self.rotation) it's not needed anymore?
-- moving entity in camera
_.checkWorldBounds(self)
local futureX = self.x + self.xvel * dt
local futureY = self.y + self.yvel * dt
local nextX, nextY, collisions, len = self.world:move(self, futureX, futureY, self.collisionFilter)
self.x = nextX
self.y = nextY
self.xvel = self.xvel * 0.99
self.yvel = self.yvel * 0.99
end
So the only problem now is that enemy's rotation is not changing though the front of the spaceship should always look into player's side.
I tried with math.atan2(dx, dy) but it makes entity rotating constantly.
What is missing or what am I doing wrong?
I'm not looking for someone to code this for me (although it would be very nice) but some formula or piece of advice will be highly appreciated.
If you're interested the source code is available at - https://github.com/voronianski-on-games/asteroids-on-steroids-love2d
I can't tell you what is wrong with your code, but hopefully this will help you out.
-- these are the coordinates of the point to which your object should be facing
local playerx, playery = 100, 100
-- these are the current coordinates of the object that needs to have its facing changed
local shipx, shipy = 200, 200
-- the angle to which your object should be rotated is calculated
angle = math.atan2(playery - shipy, playerx - shipx)
Note that by using body:setAngle(angle) ('body' is your objects body) your objects will be instantly rotated to given angle. If you want them to rotate at certain pace you could use:
local currentAngle = body:getAngle()
local adjustedAngle
if currentAngle > angle then
adjustedAngle = currentAngle - rotationSpeed * dt
elseif currentAngle < angle then
adjustedAngle = currentAngle + rotationSpeed * dt
else
adjustedAngle = currentAngle
end
body:setAngle(adjustedAngle)
Also note that the 'front' of your ship/body/object is to the right along the x-axis. In other words the angle is calculated from the x-axis and 0 means that the object is facing right.

Group pitch zoon in corona sdk

In my application i have pitch zoom.There i have followed the example from google search and did it.but while i am zooming in the image went to inside the boundaries.its perfect when zooming out.But while i zooming in at left or right corner,image is not set with that boundaries.Please help any one.
if "moved" == phase then
if ( tempGroup.distance ) then
local dx,dy
if previousTouches and ( numTotalTouches ) >= 2 then
dx,dy,midX,midY,offX,offY = calculateDelta( previousTouches, event )
end
if ( dx and dy ) then
local newDistance = math.sqrt( dx*dx + dy*dy )
modScale = newDistance / tempGroup.distance
if ( modScale > 0 ) then
----MODIFIED BY CON
local newScale=tempGroup.xScaleOriginal * modScale
-- uncomment below to set max and min scales
maxScale,minScale=3,1
if (newScale>maxScale) then
newScale=maxScale
end
if (newScale<minScale) then
newScale=minScale
end
tempGroup.xScale = newScale
tempGroup.yScale = newScale
end
This is a working example. Copy this into your main.lua
-- one more thing
-- turn on multitouch
system.activate("multitouch")
-- which environment are we running on?
local isDevice = (system.getInfo("environment") == "device")
-- returns the distance between points a and b
function lengthOf( a, b )
local width, height = b.x-a.x, b.y-a.y
return (width*width + height*height)^0.5
end
-- returns the degrees between (0,0) and pt
-- note: 0 degrees is 'east'
function angleOfPoint( pt )
local x, y = pt.x, pt.y
local radian = math.atan2(y,x)
local angle = radian*180/math.pi
if angle < 0 then angle = 360 + angle end
return angle
end
-- returns the degrees between two points
-- note: 0 degrees is 'east'
function angleBetweenPoints( a, b )
local x, y = b.x - a.x, b.y - a.y
return angleOfPoint( { x=x, y=y } )
end
-- returns the smallest angle between the two angles
-- ie: the difference between the two angles via the shortest distance
function smallestAngleDiff( target, source )
local a = target - source
if (a > 180) then
a = a - 360
elseif (a < -180) then
a = a + 360
end
return a
end
-- rotates a point around the (0,0) point by degrees
-- returns new point object
function rotatePoint( point, degrees )
local x, y = point.x, point.y
local theta = math.rad( degrees )
local pt = {
x = x * math.cos(theta) - y * math.sin(theta),
y = x * math.sin(theta) + y * math.cos(theta)
}
return pt
end
-- rotates point around the centre by degrees
-- rounds the returned coordinates using math.round() if round == true
-- returns new coordinates object
function rotateAboutPoint( point, centre, degrees, round )
local pt = { x=point.x - centre.x, y=point.y - centre.y }
pt = rotatePoint( pt, degrees )
pt.x, pt.y = pt.x + centre.x, pt.y + centre.y
if (round) then
pt.x = math.round(pt.x)
pt.y = math.round(pt.y)
end
return pt
end
-- calculates the average centre of a list of points
local function calcAvgCentre( points )
local x, y = 0, 0
for i=1, #points do
local pt = points[i]
x = x + pt.x
y = y + pt.y
end
return { x = x / #points, y = y / #points }
end
-- calculate each tracking dot's distance and angle from the midpoint
local function updateTracking( centre, points )
for i=1, #points do
local point = points[i]
point.prevAngle = point.angle
point.prevDistance = point.distance
point.angle = angleBetweenPoints( centre, point )
point.distance = lengthOf( centre, point )
end
end
-- calculates rotation amount based on the average change in tracking point rotation
local function calcAverageRotation( points )
local total = 0
for i=1, #points do
local point = points[i]
total = total + smallestAngleDiff( point.angle, point.prevAngle )
end
return total / #points
end
-- calculates scaling amount based on the average change in tracking point distances
local function calcAverageScaling( points )
local total = 0
for i=1, #points do
local point = points[i]
total = total + point.distance / point.prevDistance
end
return total / #points
end
-- creates an object to be moved
function newTrackDot(e)
-- create a user interface object
local circle = display.newCircle( e.x, e.y, 50 )
-- make it less imposing
circle.alpha = .5
-- keep reference to the rectangle
local rect = e.target
-- standard multi-touch event listener
function circle:touch(e)
-- get the object which received the touch event
local target = circle
-- store the parent object in the event
e.parent = rect
-- handle each phase of the touch event life cycle...
if (e.phase == "began") then
-- tell corona that following touches come to this display object
display.getCurrentStage():setFocus(target, e.id)
-- remember that this object has the focus
target.hasFocus = true
-- indicate the event was handled
return true
elseif (target.hasFocus) then
-- this object is handling touches
if (e.phase == "moved") then
-- move the display object with the touch (or whatever)
target.x, target.y = e.x, e.y
else -- "ended" and "cancelled" phases
-- stop being responsible for touches
display.getCurrentStage():setFocus(target, nil)
-- remember this object no longer has the focus
target.hasFocus = false
end
-- send the event parameter to the rect object
rect:touch(e)
-- indicate that we handled the touch and not to propagate it
return true
end
-- if the target is not responsible for this touch event return false
return false
end
-- listen for touches starting on the touch layer
circle:addEventListener("touch")
-- listen for a tap when running in the simulator
function circle:tap(e)
if (e.numTaps == 2) then
-- set the parent
e.parent = rect
-- call touch to remove the tracking dot
rect:touch(e)
end
return true
end
-- only attach tap listener in the simulator
if (not isDevice) then
circle:addEventListener("tap")
end
-- pass the began phase to the tracking dot
circle:touch(e)
-- return the object for use
return circle
end
-- spawning tracking dots
-- create display group to listen for new touches
local group = display.newGroup()
-- populate display group with objects
local rect = display.newRect( group, 200, 200, 200, 100 )
rect:setFillColor(0,0,255)
rect = display.newRect( group, 300, 300, 200, 100 )
rect:setFillColor(0,255,0)
rect = display.newRect( group, 100, 400, 200, 100 )
rect:setFillColor(255,0,0)
-- keep a list of the tracking dots
group.dots = {}
-- advanced multi-touch event listener
function touch(self, e)
-- get the object which received the touch event
local target = e.target
-- get reference to self object
local rect = self
-- handle began phase of the touch event life cycle...
if (e.phase == "began") then
print( e.phase, e.x, e.y )
-- create a tracking dot
local dot = newTrackDot(e)
-- add the new dot to the list
rect.dots[ #rect.dots+1 ] = dot
-- pre-store the average centre position of all touch points
rect.prevCentre = calcAvgCentre( rect.dots )
-- pre-store the tracking dot scale and rotation values
updateTracking( rect.prevCentre, rect.dots )
-- we handled the began phase
return true
elseif (e.parent == rect) then
if (e.phase == "moved") then
print( e.phase, e.x, e.y )
-- declare working variables
local centre, scale, rotate = {}, 1, 0
-- calculate the average centre position of all touch points
centre = calcAvgCentre( rect.dots )
-- refresh tracking dot scale and rotation values
updateTracking( rect.prevCentre, rect.dots )
-- if there is more than one tracking dot, calculate the rotation and scaling
if (#rect.dots > 1) then
-- calculate the average rotation of the tracking dots
rotate = calcAverageRotation( rect.dots )
-- calculate the average scaling of the tracking dots
scale = calcAverageScaling( rect.dots )
-- apply rotation to rect
rect.rotation = rect.rotation + rotate
-- apply scaling to rect
rect.xScale, rect.yScale = rect.xScale * scale, rect.yScale * scale
end
-- declare working point for the rect location
local pt = {}
-- translation relative to centre point move
pt.x = rect.x + (centre.x - rect.prevCentre.x)
pt.y = rect.y + (centre.y - rect.prevCentre.y)
-- scale around the average centre of the pinch
-- (centre of the tracking dots, not the rect centre)
pt.x = centre.x + ((pt.x - centre.x) * scale)
pt.y = centre.y + ((pt.y - centre.y) * scale)
-- rotate the rect centre around the pinch centre
-- (same rotation as the rect is rotated!)
pt = rotateAboutPoint( pt, centre, rotate, false )
-- apply pinch translation, scaling and rotation to the rect centre
rect.x, rect.y = pt.x, pt.y
-- store the centre of all touch points
rect.prevCentre = centre
else -- "ended" and "cancelled" phases
print( e.phase, e.x, e.y )
-- remove the tracking dot from the list
if (isDevice or e.numTaps == 2) then
-- get index of dot to be removed
local index = table.indexOf( rect.dots, e.target )
-- remove dot from list
table.remove( rect.dots, index )
-- remove tracking dot from the screen
e.target:removeSelf()
-- store the new centre of all touch points
rect.prevCentre = calcAvgCentre( rect.dots )
-- refresh tracking dot scale and rotation values
updateTracking( rect.prevCentre, rect.dots )
end
end
return true
end
-- if the target is not responsible for this touch event return false
return false
end
-- attach pinch zoom touch listener
group.touch = touch
-- listen for touches starting on the touch object
group:addEventListener("touch")

How to calibrate the accelerometer angle based on how the users is holding the device with Corona SDK?

Hey guys, I just finished my app with Corona SDK and thought I'd try to make my first game.
As my first app was learning about the accelerometer I thought my game should be with that too.
So I placed a little doodle on the screen and got him controlled by the accelerometer in both X and Y direction, the game is in landscape but if I have the device on an angle towards me the doodle slides off the screen in Y direction.
If I would be laying in bed or slouching on the couch then the game won't be playable.
How do I write a function that compensate this angle?
Here is the code I have for the accelerometer at the moment;
display.setStatusBar(display.HiddenStatusBar)
system.setAccelerometerInterval( 50 )
_W = display.contentWidth
_H = display.contentHeight
local player = display.newImageRect("doodle.png", 64, 64)
player:setReferencePoint(display.CenterReferencePoint)
player.x = _W/2
player.y = _H/2
-- Set up the Accelerometer values in Landscape
local motionX = 0
local motionY = 0
local function onAccelerate( event )
motionX = 10 * event.yGravity;
motionY = 10 * event.xGravity;
end
Runtime:addEventListener ("accelerometer", onAccelerate);
-- Make the player move on tilt.
local function movePlayer (event)
player.x = player.x + motionX;
player.y = player.y - motionY;
end
Runtime:addEventListener("enterFrame", movePlayer))
We faced a similar challenge for ArdentHD.
Basically what you need to do is calibrate for the "still" X, Y and Z values.
Once you launch your game, keep reading the accelerometer for a few seconds.
During that time you can display a count down or something else to comfort the user.
Calculate the average value for X, Y and Z respectively.
These are the values that represent a "still" device.
So when the user holds the device upright, you'll have X = 0, Y = -1 and Z = 0.
(With the device on it's back, it would be X = 0, Y = 0, Z = -1)
Save those somewhere.
e.g:
xOffset = event.xGravity
yOffset = event.yGravity
zOffset = event.zGravity
Now, instead of executing
motionX = 10 * event.yGravity;
motionY = 10 * event.xGravity;
in your movement calculation function, instead execute
motionX = 10 * (event.yGravity - xOffset);
motionY = 10 * (event.xGravity - yOffset);
This cleans out the original position.
Also, be aware that of you want to really turn the device 360°, you will to calculate both your motionX and motionY as a cotangent of xGravity and zGravity as well as yGravity and zGravity. Otherwise the movement will only feel "real" when the device is horizontal (zGravity constant at -1).

Resources