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