2

So I'm using pygame to create a simple top-down shooter game, and I'm doing lots of angle calculations from the top-down perspective. Let's use a simple arrow and a ball as an example, I want the red arrow to keep pointing at the blue ball regardless of where the blue ball moves:

enter image description here

And it seemed easy enough, I just needed atan2:

angle = math.atan2(blue.y - red.y, blue.x - red.x)

But the problem is, atan2 works for a mathematical coordinate grid like this:

enter image description here

Where alpha = math.atan2(blue.y - red.y, blue.x - red.x)

But the thing with pygame (on Windows at least) is that the coordinate grid doesn't work like a mathematical coordinate grid, it's actually upside down starting from the left top corner of the game window:

enter image description here

So while it looks like the blue ball is higher up and thus mathematically blue.y should be larger than red.y, this is actually not the case due to the upside down coordinate grid, which Python's math.atan2() doesn't know of, and the original calculation I had:

angle = math.atan2(blue.y - red.y, blue.x - red.x)

Actually yields the correct angle's negation.

Now the obvious first solution that I came up with was to just flip the sign, and fair enough it worked with this:

angle = -math.atan2(blue.y - red.y, blue.x - red.x)

But the issues started again once I needed to do further calculations based on the previously calculated angle, which technically is now upside down.

What countermeasures could I take to "permanently" get rid of this issue?

Here's an actual example of where I need this, I have a "zombie" entity which does nothing but follows the target it has been given:

class Zombie(Entity):

    def __init__(self, *args, target=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.target = target

    def update(self, dt, app):
        if self.target:
            # Face towards target
            dx = self.target.x - self.x
            dy = self.target.y - self.y
            self.angle = math.atan2(dy, dx)
            # Change velocity towards target
            speed = self.get_max_speed()
            vel_x = math.cos(angle) * speed
            vel_y = math.sin(angle) * speed
            self.velocity = (vel_x, vel_y)
        else:
            self.velocity = (0, 0)
        # Moves the zombie based on velocity
        super().update(dt, app)

For this particular case I managed to solve it by storing the angle into a separate variable for later use, and negating it separately upon setting the self.angle:

# Face towards target
dx = self.target.x - self.x
dy = self.target.y - self.y
angle = math.atan2(dy, dx)
self.angle = -angle
# Change velocity towards target
speed = self.get_max_speed()
vel_x = math.cos(angle) * speed
vel_y = math.sin(angle) * speed
self.velocity = (vel_x, vel_y)

But this is just begging for more bugs, and I'm looking for a more generic solution to the issue.

Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119
  • 1
    pygame has `pygame.math.Vector2`, goot for calculations . BTW: I never had problem with angle using even `math` module – furas Dec 10 '17 at 05:34
  • 1
    I don't have problems with this solution either. What issues exactly do you mean? I usually use `pygame.math.Vector2`s as well. [Here's an example](https://stackoverflow.com/a/47166984/6220679) in which I also use the `as_polar` method instead of `math.atan2` to get the angle. – skrx Dec 10 '17 at 05:55
  • 1
    Well, never heard of `Vector2` before, should've probably used that to begin with. Omw to refactor then... Thanks guys – Markus Meskanen Dec 10 '17 at 06:19
  • BTW: few days ago I made example with `Vector2` - https://stackoverflow.com/a/47644510/1832058 – furas Dec 10 '17 at 15:22
  • as I read your question then I think problem is not angle but flipped coordinates. You calculate all in system with `(0,0)` in **bottom** left corner and you have to draw it in flipped system with `(0,0)` in **top** left corner. So you have to flip all `y` values before drawing and then you get `(0,0)` in **bottom** corrner. `screen_y = SCREEN_HEIGHT - real_y` – furas Dec 10 '17 at 15:36

1 Answers1

0

Instead of changing to a negative sign, you can calculate the angle your self. I've used your ball and arrow example. It's very simple trigonometry:

#Import needed programs
import pygame, sys, time, math
from pygame.locals import *

#Initialize pygame
pygame.init()
window = pygame.display.set_mode((600, 450))
pygame.display.set_caption('PyCloud')

#Define colours
aqua = 0,255,255
white = 255,255,255

#Defne an arrow sprite class
arrow = pygame.image.load("arrow.png")
arrow = pygame.transform.scale(arrow,(50,200))

#Define a function to calculate angle
def calcAngle(triangleA,triangleB):
    sinAngle = triangleA/triangleB
    angle = math.asin(sinAngle)
    return math.degrees(angle)    

#Creat clock lop for stability
clock = pygame.time.Clock()

#start main loop
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()
        else:
            pass

    #Get mouse pos
    mouse = pygame.mouse.get_pos()
    x = mouse[0]
    y = mouse[1]

    #calculate triangle lengths
    triangleA = x
    triangleB = math.hypot(x,(450-y))
    angle = calcAngle(triangleA,triangleB)
    print(angle)

    #draw screen
    window.fill(white)
    pygame.draw.circle(window,aqua,(x,y),10,0)
    window.blit(arrow,(0,350))
    clock.tick(1000)
    pygame.display.update()

The program stores the exact angle that that the arrow should turn in a variable called angle. I will let you decide what you ant to do with the angle itself. The difference between this and what you did is it calculates it based on the pygame axis, not a mathematical one.

Here is the arrow image in case you want to test it out. Just save the below image in the same folder as the program: Arrow image used in program