[Solved] Pointing at an object that's out of sight
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
- Gunroar:Cannon()
- Party member
- Posts: 1088
- Joined: Thu Dec 10, 2020 1:57 am
[Solved] Pointing at an object that's out of sight
How(what's the logic) do you draw an arrow, at the edge of the screen, to point at an object (in a 2d game) that can't be seen in the screen, like another player or a power up.
Last edited by Gunroar:Cannon() on Fri Jan 08, 2021 10:21 pm, edited 1 time in total.
Re: Pointing at an object that's out of sight
There are many ways to do this. It depends on what you want. I can see two possibilities:
- A simple approach, where you just point in the approximate direction: Hereby you determine in which part of the playing field the object/enemy (X) is relative to the player (P): the number shown. Here the screen of the player is the shown by thick black lines. If it is above (2), below (8), to the left (4) or the right (6) just draw an arrow at the position of the object on the border (example in light blue). If the object/enemy is in one of the other parts of the playing field (1, 3, 7 or 9), just draw an arrow at the corresponding corner (example in purple).
- A complex approach, where you point directly at the object with the correct angle: Here you have to calculate the angle between the player (P) and the enemy/object (X) and the intersection point between the screen border (thick black lines) and the line from player to enemy/object (pencil). Then you can draw the arrow at the right position with the correct angle. Hereby the angle is the easy part, but the calculation the intersection is more difficult. I need about a page of math to get a formula for calculation the intersection point. And the formula won't look pretty.
- Gunroar:Cannon()
- Party member
- Posts: 1088
- Joined: Thu Dec 10, 2020 1:57 am
Re: Pointing at an object that's out of sight
Thnx, I want 2. Don't you use something like math.atan2(obj.y-p.y,obj.x-p.x)?
Re: Pointing at an object that's out of sight
Yes, that is the formula to calculate the angle. But that is the easy part. Calculating the point on the border is harder. But I found a simpler in my sleep (simpler than what I thought of yesterday):
- The first step is to find out which screen-border the pointer needs to be. This can be done by calculating the angles of the screen-corners (e.g. alpha and gamma) and compare them to the angle between player and object (beta). In my case this is the lower screen border (since alpha <= beta <= gamma). With this you know the "height" (y-value) of the lower screen border (I_y in my notation).
- The second step is to calculate the x-value of the intersection (I_x). This can be done with simple trigonometry (see my sketch).
Re: Pointing at an object that's out of sight
Dealing with angles is prone to bugs due to lack of handling special cases, depending on the signs of the angles. This task can be solved by casting 4 rays, each to an axis-aligned line. The formula for line-line intersection is very simple when one of the lines is axis-aligned.
Problem: calculate the intersection between a line of the form v*t + p (where v is a vector from the player to the enemy, p is the player position and t is an arbitrary number; each value of t yields a point in the line) and either a vertical line (of the form x = constant) or a horizontal one (of the form y = constant). We need to find the point at which one line intersects the other, that is, we need to find the value of t that gives the point where the lines meet. We then have a linear equation which is quickly solved:
vx*t + px = x (for vertical line) -> t = (x - px) / vx
vy*t + py = y (for horizontal line) -> t = (y - py) / vy
Do that for each of the four lines, and take the smallest positive t of all four. Plug the obtained t into v*t + p and you will have the missing coordinate.
But if the arrow is an image, you still need to use atan2 to find the angle with which to draw it, because Löve doesn't allow specifying the rotation as a matrix for drawing, you need an angle. Furthermore, the image needs to rotate over its centre, and the lines must be at a certain distance from the border (half the image's width and height).
PoC attached. Mouse wheel to zoom out, LMB to place player, RMB to place enemy. The algorithm fails when the player is not in the screen, due to the condition that t must be positive, but that shouldn't be a problem.
Problem: calculate the intersection between a line of the form v*t + p (where v is a vector from the player to the enemy, p is the player position and t is an arbitrary number; each value of t yields a point in the line) and either a vertical line (of the form x = constant) or a horizontal one (of the form y = constant). We need to find the point at which one line intersects the other, that is, we need to find the value of t that gives the point where the lines meet. We then have a linear equation which is quickly solved:
vx*t + px = x (for vertical line) -> t = (x - px) / vx
vy*t + py = y (for horizontal line) -> t = (y - py) / vy
Do that for each of the four lines, and take the smallest positive t of all four. Plug the obtained t into v*t + p and you will have the missing coordinate.
But if the arrow is an image, you still need to use atan2 to find the angle with which to draw it, because Löve doesn't allow specifying the rotation as a matrix for drawing, you need an angle. Furthermore, the image needs to rotate over its centre, and the lines must be at a certain distance from the border (half the image's width and height).
PoC attached. Mouse wheel to zoom out, LMB to place player, RMB to place enemy. The algorithm fails when the player is not in the screen, due to the condition that t must be positive, but that shouldn't be a problem.
- Attachments
-
- arrow-to-enemy.love
- (1.49 KiB) Downloaded 203 times
- Gunroar:Cannon()
- Party member
- Posts: 1088
- Joined: Thu Dec 10, 2020 1:57 am
Re: Pointing at an object that's out of sight
Thnxs alot, Xugro for your input and help! And your example wrapped it all up and fixed my problem, pgimeno, thnx!! I definitely couldn't have done it myself .
Re: Pointing at an object that's out of sight
That's a cool solution pgimeno, very fast and elegant.
The method I came up with when thinking of this is more verbose and I think slower aswell, using the point-slope form of the line equation to get the intersections, but I wanted to put it to practice anyway. The code is inline as I made the arrow with a mesh object so it can be standalone:
Code: Select all
local arrow
local arrowHalfW, arrowHalfH
local zoom = 0.75
local playerX, playerY, playerW, playerH = 100, 200, 20, 40
local enemyX, enemyY, enemyW, enemyH = 300, 400, 20, 40
local isEnemyOff = false
local arrowPosX, arrowPosY
function love.load()
arrow = love.graphics.newMesh(
{{'VertexPosition', 'float', 2}},
{{0, 10}, {15, 10}, {15, 20}, {15, 20}, {0, 20}, {0, 10}, {15, 0}, {30, 15}, {15, 30}},
'triangles',
'static'
)
arrowHalfW = 15
arrowHalfH = 15
end
function love.wheelmoved(x, y)
zoom = zoom * 1.1 ^ y
if zoom > 1 then zoom = 1 end
end
-- Using the point-slope method, by RNavega.
function love.update(dt)
local screenW, screenH = love.graphics.getDimensions()
isEnemyOff = enemyX < 0 or enemyX > screenW
or enemyY < 0 or enemyY > screenH
if isEnemyOff then
local dx, dy = enemyX - playerX, enemyY - playerY
if dx == 0.0 then -- Happens when (enemyX == playerX).
arrowPosX = enemyX
arrowPosY = (enemyY > screenH) and (screenH - arrowHalfH) or arrowHalfH
elseif dy == 0.0 then -- Happens when (enemyY == playerY).
arrowPosX = (enemyX > screenW) and (screenW - arrowHalfW) or arrowHalfW
arrowPosY = enemyY
else
-- The point-slope equation of a line:
-- y - y1 = m * (x - x1)
local m = dy / dx
-- We actually test intersections with the edges of a smaller screen, padded by
-- arrowHalfW horizontally and arrowHalfH vertically.
local screenRightX = screenW - arrowHalfW
local screenBottomY = screenH - arrowHalfH
-- Isolating 'y' for use with the vertical edges:
-- y = m * (x - x1) + y1
if enemyX < arrowHalfW then
-- Left edge.
local intersectY = m * (arrowHalfW - playerX) + playerY
if intersectY >= arrowHalfH and intersectY <= screenBottomY then
arrowPosX = arrowHalfW
arrowPosY = intersectY
end
elseif enemyX > screenRightX then
-- Right edge.
local intersectY = m * (screenRightX - playerX) + playerY
if intersectY >= arrowHalfH and intersectY <= screenBottomY then
arrowPosX = screenRightX
arrowPosY = intersectY
end
end
-- Isolating 'x' for use with the horizontal edges:
-- x = (y - y1 + m*x1) / m
if enemyY < arrowHalfH then
-- Top edge.
local intersectX = (arrowHalfH - playerY + m * playerX) / m
if intersectX >= arrowHalfW and intersectX <= screenRightX then
arrowPosX = intersectX
arrowPosY = arrowHalfH
end
elseif enemyY > screenBottomY then
-- Bottom edge.
local intersectX = (screenBottomY - playerY + m * playerX) / m
if intersectX >= arrowHalfW and intersectX <= screenRightX then
arrowPosX = intersectX
arrowPosY = screenBottomY
end
end
end
end
end
-- Using 2D vectors, by pgimeno.
function love.updateOriginal(dt)
isEnemyOff = enemyX < 0 or enemyX >= love.graphics.getWidth()
or enemyY < 0 or enemyY >= love.graphics.getHeight()
if isEnemyOff then
local screenW, screenH = love.graphics.getDimensions()
local vx, vy = enemyX - playerX, enemyY - playerY
local t, minT
minT = math.huge
t = (arrowHalfW - playerX) / vx
if t == t and t > 0 and t < minT then
minT = t
arrowPosX = arrowHalfW
arrowPosY = vy * t + playerY
end
t = (arrowHalfH - playerY) / vy
if t == t and t > 0 and t < minT then
minT = t
arrowPosX = vx * t + playerX
arrowPosY = arrowHalfH
end
t = (screenW - arrowHalfW - playerX) / vx
if t == t and t > 0 and t < minT then
minT = t
arrowPosX = screenW - arrowHalfW
arrowPosY = vy * t + playerY
end
t = (screenH - arrowHalfH - playerY) / vy
if t > 0 and t < minT then
-- minT = t -- doesn't matter anymore
arrowPosX = vx * t + playerX
arrowPosY = screenH - arrowHalfH
end
end
end
function love.draw()
local screenW, screenH = love.graphics.getDimensions()
love.graphics.translate(screenW/2, screenH/2)
love.graphics.scale(zoom)
love.graphics.translate(screenW/-2, screenH/-2)
-- Draw screen border
love.graphics.setLineWidth(1/zoom)
love.graphics.rectangle("line", -.5, -.5, screenW+1, screenH+1)
-- Draw player and enemy
love.graphics.setColor(0, 1, 1)
love.graphics.rectangle("fill", playerX - playerW/2, playerY - playerH/2, playerW, playerH)
love.graphics.setColor(1, 0, 0)
love.graphics.rectangle("fill", enemyX - enemyW/2, enemyY - enemyH/2, enemyW, enemyH)
-- Draw arrow if visible
if isEnemyOff then
love.graphics.setColor(1, 1, 0)
love.graphics.draw(arrow, arrowPosX, arrowPosY,
math.atan2(enemyY - playerY, enemyX - playerX),
1, 1, arrowHalfW, arrowHalfH)
end
-- Restore colour
love.graphics.setColor(1, 1, 1)
end
function love.mousemoved(x, y)
-- transform screen to zoomed coordinates
x = (x - love.graphics.getWidth() / 2) / zoom + love.graphics.getWidth() / 2
y = (y - love.graphics.getHeight() / 2) / zoom + love.graphics.getHeight() / 2
if love.mouse.isDown(1) then
playerX = x
playerY = y
elseif love.mouse.isDown(2) then
enemyX = x
enemyY = y
end
end
love.mousepressed = love.mousemoved
love.mousereleased = love.mousemoved
function love.keypressed(key)
if key == 'escape' then
love.event.quit()
end
end
Re: [Solved] Pointing at an object that's out of sight
...I should add, I had the idea while looking at the diagrams drawn by Xugro above, so thanks for that!
Re: [Solved] Pointing at an object that's out of sight
I have dealt with this issue before and the easiest way is to use my algorithm for SegmentVSAABB:
https://2dengine.com/?p=intersections#section_15
The segment is the ray from the center of the screen to the target point and the AABB is defined by the edges of the screen.
https://2dengine.com/?p=intersections#section_15
The segment is the ray from the center of the screen to the target point and the AABB is defined by the edges of the screen.
Re: Pointing at an object that's out of sight
I forgot one 't == t' (to discard this edge when t is NaN) in the condition for the last screen border. The last lines in love.update should be like this:
[edited to remove excess pickiness]
Code: Select all
t = (screenH - arrowHalfH - playerY) / vy
if t == t and t > 0 and t < minT then --<<-- 't == t' was missing here ***
-- minT = t -- doesn't matter anymore
arrowPosX = vx * t + playerX
arrowPosY = screenH - arrowHalfH
end
end
end
Who is online
Users browsing this forum: Ahrefs [Bot] and 95 guests