2

Small gif showing problem but I don't have 10 reputation yet (this is only my 2nd question) and thus have to use link

I have a simple test program with a Spaceship which is supposed to point toward a planet, but it points in odd directions instead.

The purpose of the program is to test whether my test program can get the angle from the spaceship to the planet, so an alternate method won't work, since I need the angle to determine which direction to apply "gravity" in (which is what this test is for).

This is also why the script repeats multiple times but only the final angle will be graphically displayed.

My program uses code gotten from SO question Calculate angle (clockwise) between two points

The answer by "Chris St Pierre" just gave me an error for attempting to divide a float by zero (?).

The answer by "ali_m" just gave me a problem like this one.

I'm using the answer by "Colin Basnett", which doesn't work for me either, but it's my favorite method so far because it doesn't require plugins (and because it's short and doesn't just straight-away throw an error at me).

I adapted it into the function below:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def get_angle_between(x0,y0,x1,y1): 
    v1 = Vector(x0, y0)
    v2 = Vector(x1, y1)
    
    v1_theta = math.atan2(v1.y, v1.x)
    v2_theta = math.atan2(v2.y, v2.x)
    
    r = (v2_theta - v1_theta) * (180.0 / math.pi)
    
    return r

It is called by this script in the Spaceship sprite's "move" function:

if gravityJsonQ:
            for item in planets:
                centreOfGravityX = planets[item]["x"] + (planets[item]["s"] / 2)
                centreOfGravityY = planets[item]["y"] + (planets[item]["s"] / 2)
                centreOfGravityGravity = float(planets[item]["g"])
                pendingUtil = get_points(prevSubPositionX,prevSubPositionY,subPositionX,subPositionY)
                for item2 in pendingUtil:
                    cfx,cfy = item2
                    circular_percentage = get_angle_between(cfx,cfy,centreOfGravityX,centreOfGravityY) / 3.6

circular_percentage (cp) is essentially degrees / 3.6 (anticlockwise, which, although the link is for clockwise angles, I tried subtracting it from 100cp (360deg) to no avail, so I doubt that's the problem)

get_points() is "Bresenham's line algorithm", and it works fine.

planets is the following dictionary:

{"Earthlike": {"x": 375, "y": 375, "s": 200, "i": "earthlike_1_beveled.png", "g": 11}}

I've tried fiddling with it a bit to see if it would start working, but the main problem is I don't understand any of the math involved, so the linked Wikipedia article(s) went right over my head. I have (frankly) not a clue to what's causing the problem or how to solve it.

Here's a link to download all 194KB (it's actually 10KB smaller when unzipped) of the program and it's textures. (Use WASD/arrow keys to move, the problem is in either lines 49 to 63 or 100 to 108 (the first line is #1 not #0)):

https://www.filehosting.org/file/details/920907/SOQ.zip

There might be some unnecessary code since I just got my main program and cut out most of the bits that weren't needed.

Just in case, here's the code (it's in the zip, but I figured I'm probably supposed to put it here anyway even though it is unrunable (real word?) without the textures):

#See lines (this line is #1) - 49 to 63 - and - 100 to 108


import json, math, os, pygame, sys, time
from pygame.locals import *
pygame.init()
baseFolder = __file__[:-10]
FPS = 30
FramePerSec = pygame.time.Clock()
xVelocity = yVelocity = rVelocity = float(0)
def get_points(x0,y0,x1,y1):
    pointlist = []
    x0,y0 = int(x0),int(y0)
    x1,y1 = int(x1),int(y1)
    dx = abs(x1-x0)
    dy = abs(y1-y0)
    if x0 < x1: sx = 1
    else: sx = -1
    if y0 < y1: sy = 1
    else: sy = -1
    err = dx-dy
    while True:
        pointlist.append((x0,y0))
        if x0 == x1 and y0 == y1: return pointlist
        e2 = 2 * err
        if e2 > -dy:
            err = err - dy
            x0 += sx
        if e2 < dx:
            err = err + dx
            y0 += sy
screen_size = 750
spaceship_texture = "spaceship.png"
spaceship_texture = spaceship_texture.replace("\n","")
spaceship_size = 60
gravityJsonQ = True
planets = {"Earthlike": {"x": 375, "y": 375, "s": 200, "i": "earthlike_1_beveled.png", "g": 11}}
displaySurf = pygame.display.set_mode((screen_size,screen_size))
displaySurf.fill((0,0,0))
subPositionX = subPositionY = float(screen_size / 2)
circular_percentage = 0




#Problem:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def get_angle_between(x0,y0,x1,y1): 
    v1 = Vector(x0, y0)
    v2 = Vector(x1, y1)
    
    v1_theta = math.atan2(v1.y, v1.x)
    v2_theta = math.atan2(v2.y, v2.x)
    
    r = (v2_theta - v1_theta) * (180.0 / math.pi)
    
    return r

#or mabye...




class Spaceship(pygame.sprite.Sprite):
    def __init__(self):
        global baseFolder, screen_size, spaceship_images, spaceship_size, spaceship_texture
        super().__init__()
        spaceship_images = {}
        for pendingUtil in range(0,100): spaceship_images[str(pendingUtil)]  = pygame.image.load(baseFolder + "\\" + spaceship_texture + ".texture_map\\" + str(pendingUtil) + ".png")
        self.image = spaceship_images["0"]
        self.surf = pygame.Surface((int(spaceship_size), int(spaceship_size)))
        self.rect = self.surf.get_rect(center = (int(screen_size / 2),int(screen_size / 2)))
        self.image = pygame.transform.scale(self.image,(spaceship_size,spaceship_size))
    def move(self):
        global circular_percentage, rVelocity, prevSubPositionX, prevSubPositionY, subPositionX, subPositionY, xVelocity, yVelocity
        pressed_keys = pygame.key.get_pressed()
        if pressed_keys[K_UP] or pressed_keys[K_w]:
            yVelocity -= 0.1
        if pressed_keys[K_DOWN] or pressed_keys[K_s]:
            yVelocity += 0.1
        if pressed_keys[K_LEFT] or pressed_keys[K_a]:
            xVelocity -= 0.1
        if pressed_keys[K_RIGHT] or pressed_keys[K_d]:
            xVelocity += 0.1
        prevSubPositionX,prevSubPositionY = subPositionX,subPositionY
        subPositionX += xVelocity
        subPositionY += yVelocity
        
        
        
        
        #Problem:
        
        if gravityJsonQ:
            for item in planets:
                centreOfGravityX = planets[item]["x"] + (planets[item]["s"] / 2)
                centreOfGravityY = planets[item]["y"] + (planets[item]["s"] / 2)
                centreOfGravityGravity = float(planets[item]["g"])
                pendingUtil = get_points(prevSubPositionX,prevSubPositionY,subPositionX,subPositionY)
                for item2 in pendingUtil:
                    cfx,cfy = item2
                    circular_percentage = get_angle_between(cfx,cfy,centreOfGravityX,centreOfGravityY) / 3.6
        
        #Problem will (very likely) be either here or noted area above
        
        
        
        
        while circular_percentage < 0: circular_percentage += 100
        while circular_percentage > 99: circular_percentage -= 100
        self.rect = self.surf.get_rect(center = (int(subPositionX),int(subPositionY)))
        self.image = spaceship_images[str(int(circular_percentage))]
        self.image = pygame.transform.scale(self.image,(spaceship_size,spaceship_size))
Player = Spaceship()
all_sprites = pygame.sprite.Group()
all_sprites.add(Player)
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
    displaySurf.fill((0,0,0))
    for item in planets:
        current_planet_image = pygame.image.load(planets[item]["i"])
        current_planet_image = pygame.transform.scale(current_planet_image,(planets[item]["s"],planets[item]["s"]))
        displaySurf.blit(current_planet_image,(planets[item]["x"],planets[item]["y"]))
    for entity in all_sprites:
        displaySurf.blit(entity.image,entity.rect)
        entity.move()
    pygame.display.update()
    FramePerSec.tick(FPS)
Rabbid76
  • 142,694
  • 23
  • 71
  • 112

1 Answers1

1

Note, Pygame provides the pygame.math.Vector2 class. It is not necessary to paint 100 images of the space ship with different angles. You can rotate an image with pygame.transform.rotate. See How do I rotate an image around its center using PyGame?.


The vector from the point (x0, y0) to the point (x1, y1) is:

v = Vector(x1-x0, y1-y0)

The angle of the vector is (see How to know the angle between two points?):

math.atan2(y1-y0, x1-x0)

The top left of the pygame coordinate system is (0, 0). Therefore the y-axis points downwards. Hence you have to invert the y-axis for the calculation of the angle.

get_angle_between function:

def get_angle_between(x0, y0, x1, y1):
    v = Vector(x1-x0, y1-y0)
    return math.degrees(math.atan2(-v.y, v.x))

In the above formula, an angle of 0 means that the spaceship is pointing to the right. If your spaceship is pointing up at a 0 angle, you'll need to add a correction angle (see How to rotate an image(player) to the mouse direction?):

def get_angle_between(x0, y0, x1, y1):
    v = Vector(x1-x0, y1-y0)
    return math.degrees(math.atan2(-v.y, v.x)) - 90

In this answer I want to explain the steps for the calculation. I want to keep it comprehensible. Of course, you can skip constructing the Vector object and put everything in one line of code:

def get_angle_between(x0, y0, x1, y1):
    return math.degrees(math.atan2(y0-y1, x1-x0)) - 90

However, the bottleneck in the calculation is the function math.atan2.

Rabbid76
  • 142,694
  • 23
  • 71
  • 112