Related
I am making small game with pygame and I have made a gun that rotates around its center.
My problem is that I want the gun to rotate by itself to the enemy direction, but I couldn't do that because I can't find the angle between the gun and the enemy to make the gun rotate to it
I have searched and I found that I have to use the atan2 but I didn't find any working code so I hope someone could help me.
Here is my code:
import pygame
from pygame.locals import*
pygame.init()
height=650
width=650
screen=pygame.display.set_mode((height,width))
clock=pygame.time.Clock()
gun=pygame.image.load("m2.png").convert_alpha()
gun=pygame.transform.smoothscale(gun,(200,200)).convert_alpha()
angle=0
angle_change=0
RED=(255,0,0)
x=525
y=155
while True :
screen.fill((150,150,150))
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
quit()
if event.type==KEYDOWN:
if event.key==K_a:
angle_change=+1
if event.key==K_d:
angle_change=-1
elif event.type==KEYUP:
angle_change=0
angle+=angle_change
if angle>360:
angle=0
if angle<0:
angle=360
pygame.draw.rect(screen,RED,(x,y,64,64))
position = (height/2,width/2)
gun_rotate=pygame.transform.rotate(gun,angle)
rotate_rect = gun_rotate.get_rect()
rotate_rect.center = position
screen.blit(gun_rotate, rotate_rect)
pygame.display.update()
clock.tick(60)
And here is a picture trying to make it clear:
How do I solve the problem?
The tangent of the angle between two points is defined as delta y / delta x
That is (y2 - y1)/(x2-x1). This means that math.atan2(dy, dx) give the angle between the two points assuming that you know the base axis that defines the co-ordinates.
Your gun is assumed to be the (0, 0) point of the axes in order to calculate the angle in radians. Once you have that angle, then you can use the angle for the remainder of your calculations.
Note that since the angle is in radians, you need to use the math.pi instead of 180 degrees within your code. Also your test for more than 360 degrees (2*math.pi) is not needed. The test for negative (< 0) is incorrect as you then force it to 0, which forces the target to be on the x axis in the positive direction.
Your code to calculate the angle between the gun and the target is thus
myradians = math.atan2(targetY-gunY, targetX-gunX)
If you want to convert radians to degrees
mydegrees = math.degrees(myradians)
To convert from degrees to radians
myradians = math.radians(mydegrees)
Python ATAN2
The Python ATAN2 function is one of the Python Math function which is
used to returns the angle (in radians) from the X -Axis to the
specified point (y, x).
math.atan2()
Definition Returns the tangent(y,x) in radius.
Syntax
math.atan2(y,x)
Parameters
y,x=numbers
Examples
The return is:
>>> import math
>>> math.atan2(88,34)
1.202100424136847
>>>
Specifically for working with shapely linestring objects, assuming your object (two points) is of the form (min long, min lat, max long, max lat)
from math import atan2,degrees
line = #Your-LineString-Object
lineList = list(line.coords)
def AngleBtw2Points(pointA, pointB):
changeInX = pointB[0] - pointA[0]
changeInY = pointB[1] - pointA[1]
return degrees(atan2(changeInY,changeInX)) #remove degrees if you want your answer in radians
AngleBtw2Points(lineList[0],lineList[1])
In general, the angle of a vector (x, y) can be calculated by math.atan2(y, x). The vector can be defined by 2 points (x1, y1) and (x2, y2) on a line. Therefore the angle of the line is math.atan2(y2-y1, x2-x1).
Be aware that the y-axis needs to be reversed (-y respectively y1-y2) because the y-axis is generally pointing up but in the PyGame coordinate system the y-axis is pointing down. The unit of the angle in the Python math module is Radian, but the unit of the angle in PyGame functions like pygame.transform.rotate() is Degree. Hence the angle has to be converted from Radians to Degrees by math.degrees:
import math
def angle_of_vector(x, y):
return math.degrees(math.atan2(-y, x))
def angle_of_line(x1, y1, x2, y2):
return math.degrees(math.atan2(-(y2-y1), x2-x1))
This can be simplified by using the angle_to method of the pygame.math.Vector2 object. This method computes the angle between 2 vectors in the PyGame coordinate system in degrees. Therefore it is not necessary to reverse the y-axis and convert from radians to degrees. Just calculate the angle between the vector and (1, 0):
def angle_of_vector(x, y):
return pygame.math.Vector2(x, y).angle_to((1, 0))
def angle_of_line(x1, y1, x2, y2):
return angle_of_vector(x2-x1, y2-y1)
Minimale example:
import pygame
import math
def angle_of_vector(x, y):
#return math.degrees(math.atan2(-y, x)) # 1: with math.atan
return pygame.math.Vector2(x, y).angle_to((1, 0)) # 2: with pygame.math.Vector2.angle_to
def angle_of_line(x1, y1, x2, y2):
#return math.degrees(math.atan2(-y1-y2, x2-x1)) # 1: math.atan
return angle_of_vector(x2-x1, y2-y1) # 2: pygame.math.Vector2.angle_to
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
angle = 0
radius = 150
vec = (radius, 0)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cpt = window.get_rect().center
pt = cpt[0] + vec[0], cpt[1] + vec[1]
angle = angle_of_vector(*vec)
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 1)
pygame.draw.line(window, (0, 255, 0), cpt, (cpt[0] + radius, cpt[1]), 3)
pygame.draw.line(window, (255, 0, 0), cpt, pt, 3)
text_surf = font.render(str(round(angle/5)*5) + "°", True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, text_surf.get_rect(bottomleft = (cpt[0]+20, cpt[1]-20)))
pygame.display.flip()
angle = (angle + 1) % 360
vec = radius * math.cos(angle*math.pi/180), radius * -math.sin(angle*math.pi/180)
pygame.quit()
exit()
angle_to can be used to calculate the angle between 2 vectors or lines:
def angle_between_vectors(x1, y1, x2, y2):
return pygame.math.Vector2(x1, y1).angle_to((x2, y2))
Minimal example:
import pygame
import math
def angle_between_vectors(x1, y1, x2, y2):
return pygame.math.Vector2(x1, y1).angle_to((x2, y2))
def angle_of_vector(x, y):
return pygame.math.Vector2(x, y).angle_to((1, 0))
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
angle = 0
radius = 150
vec1 = (radius, 0)
vec2 = (radius, 0)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cpt = window.get_rect().center
pt1 = cpt[0] + vec1[0], cpt[1] + vec1[1]
pt2 = cpt[0] + vec2[0], cpt[1] + vec2[1]
angle = angle_between_vectors(*vec2, *vec1)
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 1)
pygame.draw.line(window, (0, 255, 0), cpt, pt1, 3)
pygame.draw.line(window, (255, 0, 0), cpt, pt2, 3)
text_surf = font.render(str(round(angle/5)*5) + "°", True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, text_surf.get_rect(bottomleft = (cpt[0]+20, cpt[1]-20)))
pygame.display.flip()
angle1 = (angle_of_vector(*vec1) + 1/3) % 360
vec1 = radius * math.cos(angle1*math.pi/180), radius * -math.sin(angle1*math.pi/180)
angle2 = (angle_of_vector(*vec2) + 1) % 360
vec2 = radius * math.cos(angle2*math.pi/180), radius * -math.sin(angle2*math.pi/180)
pygame.quit()
exit()
I used following code.
import math
def get_angle(x1,y1,x2,y2):
myradians = math.atan2(y1-y2, x1-x2)
mydegrees = math.degrees(myradians)
return mydegrees
# it should return -90 degree
print(get_angle(0,0,0,100))
As one commenter already said, there is only an angle between three points or between two intersecting vectors, that can be derived from this threee points. I assume you want the angle, that the gun and the target (vector 1) and the X-Axis (vector 2) has. Here is a link to a page, that explains how to calculate that angle. http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
Python example:
import math
def angle(vector1, vector2):
length1 = math.sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1])
length2 = math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1])
return math.acos((vector1[0] * vector2[0] + vector1[1] * vector2[1])/ (length1 * length2))
vector1 = [targetX - gunX, targetY - gunY] # Vector of aiming of the gun at the target
vector2 = [1,0] #vector of X-axis
print(angle(vector1, vector2))
You can just use the as_polar method of Pygame's Vector2 class which returns the polar coordinates of the vector (radius and polar angle (in degrees)).
So just subtract the first point vector from the second and call the as_polar method of the resulting vector.
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
point = Vector2(320, 240)
mouse_pos = Vector2(0, 0)
radius, angle = (mouse_pos - point).as_polar()
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
mouse_pos = event.pos
radius, angle = (mouse_pos - point).as_polar()
screen.fill(BG_COLOR)
pg.draw.line(screen, (0, 100, 255), point, mouse_pos)
pg.display.set_caption(f'radius: {radius:.2f} angle: {angle:.2f}')
pg.display.flip()
clock.tick(60)
For anyone wanting a bit more information on this subject check out omnicalculator's break down of the subject:
https://www.omnicalculator.com/math/angle-between-two-vectors
Extracted relevant information:
from math import acos, sqrt, degrees
# returns angle in radians between two points pt1, pt2 where pt1=(x1, y1) and pt2=(x2, y2)
angle = acos((x1 * x2 + y1 * y2)/(sqrt(x1**2 + y1**2) * sqrt(x2**2 + y2**2)))
# convert to degrees
deg_angle = degrees(angle)
I am making small game with pygame and I have made a gun that rotates around its center.
My problem is that I want the gun to rotate by itself to the enemy direction, but I couldn't do that because I can't find the angle between the gun and the enemy to make the gun rotate to it
I have searched and I found that I have to use the atan2 but I didn't find any working code so I hope someone could help me.
Here is my code:
import pygame
from pygame.locals import*
pygame.init()
height=650
width=650
screen=pygame.display.set_mode((height,width))
clock=pygame.time.Clock()
gun=pygame.image.load("m2.png").convert_alpha()
gun=pygame.transform.smoothscale(gun,(200,200)).convert_alpha()
angle=0
angle_change=0
RED=(255,0,0)
x=525
y=155
while True :
screen.fill((150,150,150))
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
quit()
if event.type==KEYDOWN:
if event.key==K_a:
angle_change=+1
if event.key==K_d:
angle_change=-1
elif event.type==KEYUP:
angle_change=0
angle+=angle_change
if angle>360:
angle=0
if angle<0:
angle=360
pygame.draw.rect(screen,RED,(x,y,64,64))
position = (height/2,width/2)
gun_rotate=pygame.transform.rotate(gun,angle)
rotate_rect = gun_rotate.get_rect()
rotate_rect.center = position
screen.blit(gun_rotate, rotate_rect)
pygame.display.update()
clock.tick(60)
And here is a picture trying to make it clear:
How do I solve the problem?
The tangent of the angle between two points is defined as delta y / delta x
That is (y2 - y1)/(x2-x1). This means that math.atan2(dy, dx) give the angle between the two points assuming that you know the base axis that defines the co-ordinates.
Your gun is assumed to be the (0, 0) point of the axes in order to calculate the angle in radians. Once you have that angle, then you can use the angle for the remainder of your calculations.
Note that since the angle is in radians, you need to use the math.pi instead of 180 degrees within your code. Also your test for more than 360 degrees (2*math.pi) is not needed. The test for negative (< 0) is incorrect as you then force it to 0, which forces the target to be on the x axis in the positive direction.
Your code to calculate the angle between the gun and the target is thus
myradians = math.atan2(targetY-gunY, targetX-gunX)
If you want to convert radians to degrees
mydegrees = math.degrees(myradians)
To convert from degrees to radians
myradians = math.radians(mydegrees)
Python ATAN2
The Python ATAN2 function is one of the Python Math function which is
used to returns the angle (in radians) from the X -Axis to the
specified point (y, x).
math.atan2()
Definition Returns the tangent(y,x) in radius.
Syntax
math.atan2(y,x)
Parameters
y,x=numbers
Examples
The return is:
>>> import math
>>> math.atan2(88,34)
1.202100424136847
>>>
Specifically for working with shapely linestring objects, assuming your object (two points) is of the form (min long, min lat, max long, max lat)
from math import atan2,degrees
line = #Your-LineString-Object
lineList = list(line.coords)
def AngleBtw2Points(pointA, pointB):
changeInX = pointB[0] - pointA[0]
changeInY = pointB[1] - pointA[1]
return degrees(atan2(changeInY,changeInX)) #remove degrees if you want your answer in radians
AngleBtw2Points(lineList[0],lineList[1])
In general, the angle of a vector (x, y) can be calculated by math.atan2(y, x). The vector can be defined by 2 points (x1, y1) and (x2, y2) on a line. Therefore the angle of the line is math.atan2(y2-y1, x2-x1).
Be aware that the y-axis needs to be reversed (-y respectively y1-y2) because the y-axis is generally pointing up but in the PyGame coordinate system the y-axis is pointing down. The unit of the angle in the Python math module is Radian, but the unit of the angle in PyGame functions like pygame.transform.rotate() is Degree. Hence the angle has to be converted from Radians to Degrees by math.degrees:
import math
def angle_of_vector(x, y):
return math.degrees(math.atan2(-y, x))
def angle_of_line(x1, y1, x2, y2):
return math.degrees(math.atan2(-(y2-y1), x2-x1))
This can be simplified by using the angle_to method of the pygame.math.Vector2 object. This method computes the angle between 2 vectors in the PyGame coordinate system in degrees. Therefore it is not necessary to reverse the y-axis and convert from radians to degrees. Just calculate the angle between the vector and (1, 0):
def angle_of_vector(x, y):
return pygame.math.Vector2(x, y).angle_to((1, 0))
def angle_of_line(x1, y1, x2, y2):
return angle_of_vector(x2-x1, y2-y1)
Minimale example:
import pygame
import math
def angle_of_vector(x, y):
#return math.degrees(math.atan2(-y, x)) # 1: with math.atan
return pygame.math.Vector2(x, y).angle_to((1, 0)) # 2: with pygame.math.Vector2.angle_to
def angle_of_line(x1, y1, x2, y2):
#return math.degrees(math.atan2(-y1-y2, x2-x1)) # 1: math.atan
return angle_of_vector(x2-x1, y2-y1) # 2: pygame.math.Vector2.angle_to
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
angle = 0
radius = 150
vec = (radius, 0)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cpt = window.get_rect().center
pt = cpt[0] + vec[0], cpt[1] + vec[1]
angle = angle_of_vector(*vec)
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 1)
pygame.draw.line(window, (0, 255, 0), cpt, (cpt[0] + radius, cpt[1]), 3)
pygame.draw.line(window, (255, 0, 0), cpt, pt, 3)
text_surf = font.render(str(round(angle/5)*5) + "°", True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, text_surf.get_rect(bottomleft = (cpt[0]+20, cpt[1]-20)))
pygame.display.flip()
angle = (angle + 1) % 360
vec = radius * math.cos(angle*math.pi/180), radius * -math.sin(angle*math.pi/180)
pygame.quit()
exit()
angle_to can be used to calculate the angle between 2 vectors or lines:
def angle_between_vectors(x1, y1, x2, y2):
return pygame.math.Vector2(x1, y1).angle_to((x2, y2))
Minimal example:
import pygame
import math
def angle_between_vectors(x1, y1, x2, y2):
return pygame.math.Vector2(x1, y1).angle_to((x2, y2))
def angle_of_vector(x, y):
return pygame.math.Vector2(x, y).angle_to((1, 0))
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
angle = 0
radius = 150
vec1 = (radius, 0)
vec2 = (radius, 0)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cpt = window.get_rect().center
pt1 = cpt[0] + vec1[0], cpt[1] + vec1[1]
pt2 = cpt[0] + vec2[0], cpt[1] + vec2[1]
angle = angle_between_vectors(*vec2, *vec1)
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 1)
pygame.draw.line(window, (0, 255, 0), cpt, pt1, 3)
pygame.draw.line(window, (255, 0, 0), cpt, pt2, 3)
text_surf = font.render(str(round(angle/5)*5) + "°", True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, text_surf.get_rect(bottomleft = (cpt[0]+20, cpt[1]-20)))
pygame.display.flip()
angle1 = (angle_of_vector(*vec1) + 1/3) % 360
vec1 = radius * math.cos(angle1*math.pi/180), radius * -math.sin(angle1*math.pi/180)
angle2 = (angle_of_vector(*vec2) + 1) % 360
vec2 = radius * math.cos(angle2*math.pi/180), radius * -math.sin(angle2*math.pi/180)
pygame.quit()
exit()
I used following code.
import math
def get_angle(x1,y1,x2,y2):
myradians = math.atan2(y1-y2, x1-x2)
mydegrees = math.degrees(myradians)
return mydegrees
# it should return -90 degree
print(get_angle(0,0,0,100))
As one commenter already said, there is only an angle between three points or between two intersecting vectors, that can be derived from this threee points. I assume you want the angle, that the gun and the target (vector 1) and the X-Axis (vector 2) has. Here is a link to a page, that explains how to calculate that angle. http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
Python example:
import math
def angle(vector1, vector2):
length1 = math.sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1])
length2 = math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1])
return math.acos((vector1[0] * vector2[0] + vector1[1] * vector2[1])/ (length1 * length2))
vector1 = [targetX - gunX, targetY - gunY] # Vector of aiming of the gun at the target
vector2 = [1,0] #vector of X-axis
print(angle(vector1, vector2))
You can just use the as_polar method of Pygame's Vector2 class which returns the polar coordinates of the vector (radius and polar angle (in degrees)).
So just subtract the first point vector from the second and call the as_polar method of the resulting vector.
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
point = Vector2(320, 240)
mouse_pos = Vector2(0, 0)
radius, angle = (mouse_pos - point).as_polar()
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
mouse_pos = event.pos
radius, angle = (mouse_pos - point).as_polar()
screen.fill(BG_COLOR)
pg.draw.line(screen, (0, 100, 255), point, mouse_pos)
pg.display.set_caption(f'radius: {radius:.2f} angle: {angle:.2f}')
pg.display.flip()
clock.tick(60)
For anyone wanting a bit more information on this subject check out omnicalculator's break down of the subject:
https://www.omnicalculator.com/math/angle-between-two-vectors
Extracted relevant information:
from math import acos, sqrt, degrees
# returns angle in radians between two points pt1, pt2 where pt1=(x1, y1) and pt2=(x2, y2)
angle = acos((x1 * x2 + y1 * y2)/(sqrt(x1**2 + y1**2) * sqrt(x2**2 + y2**2)))
# convert to degrees
deg_angle = degrees(angle)
In my pygame-code, I have a drone that is supposed to follow a flight path.
I used pygame.draw.lines to draw lines between specified points. Now, I have a flight path with 10 points where after each point the path angle changes (a bit like a zigzag). The player can move the drone by pressing the keys.
My goal is to print a warning once the drone deviates from the path, e.g. by +/-30. I have been racking my brain for two days but can't come up with a condition to detect a deviation. I just can't figure out how to approach this.
I can determine the drone's x-coordinate at any time but how do I determine the offset from the path? I have attached an image to visualize my problem.
Edit:
As I am a beginner my code is a mess but when copy-pasting it, I guess only the lines 35-91 are interesting. Thank you for any kind of advice in advance!!
import pygame
import pygame.gfxdraw
import random
import sys
import math
pygame.init()
# Define some colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
red_transp = (255,0,0, 150)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
X = 0
Y = 250
#Display
display_width, display_height = 1200, 700
h_width, h_height = display_width/2, display_height/2
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Game Display')
#Drone Sprite Image Load Function
droneImg_interim = pygame.image.load('drone.png')
droneImg = pygame.transform.scale(droneImg_interim, [50,50])
drone_width, drone_height = droneImg.get_rect().size
#Create 11 Waypoints with the same coordinates
p1=[X, Y]
p2=[X, Y]
p3=[X, Y]
p4=[X, Y]
p5=[X, Y]
p6=[X, Y]
p7=[X, Y]
p8=[X, Y]
p9=[X, Y]
p10=[X, Y]
p11=[X, Y]
pointlist = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11]
x_min=drone_width
x_max=100
#Setting new x-coordinate for each point
for i in pointlist:
i[0] = random.randrange(x_min, x_max)
x_min+=250
x_max+=250
#Setting new y-coordinate for each point
for i in range(len(pointlist)):
if i == 0:
pointlist[i][1] = random.randrange(200, 400)
else:
prev = pointlist[i-1][1]
pointlist[i][1] = random.randrange(200, prev+100)
#Plotting pointlist on gameDisplay and connecting dots
def flightpath(pointlist):
pygame.draw.lines(gameDisplay, (255, 0, 0), False, pointlist, 2)
def margin(x):
for i in range(len(pointlist)-1):
p1_x = pointlist[i][0]
p2_x = pointlist[i+1][0]
p1_y = pointlist[i][1]
p2_y = pointlist[i+1][1]
distance_x = p2_x - p1_x
distance = math.sqrt((p2_x-p1_x)**2+(p2_y-p1_y)**2)
halfwaypoint_x = math.sqrt((p2_x - p1_x)**2)/2 + p1_x
halfwaypoint_y = math.sqrt((p2_y - p1_y)**2)/2 + p1_y
if p2_y < p1_y:
angle_rad = math.acos(distance_x/distance)
elif p2_y > p1_y:
angle_rad = 0 - math.acos(distance_x/distance)
angle_deg = math.degrees(angle_rad)
rect_width = distance
rect_height = 60
"""
This part of the code is meant for displaying the margins (the rectangles) around the flight path on the display.
marginSize = (rect_width, rect_height)
surface = pygame.Surface(marginSize, pygame.SRCALPHA)
surface.fill((255,0,0,25))
rotated_surface = pygame.transform.rotate(surface, angle_deg)
#new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((pointlist[i][0], pointlist[i][1]))).center)
new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((halfwaypoint_x, halfwaypoint_y))).center)
#gameDisplay.blit(rotated_surface, new_rect)
"""
#Placing drone on the screen
def drone(x,y):
rect = droneImg.get_rect ()
rect.center=(x, y)
gameDisplay.blit(droneImg,rect)
def displayMSG(value,ttext,posx,posy):
myFont = pygame.font.SysFont("Verdana", 12)
Label = myFont.render(ttext, 1, black)
Value = myFont.render(str(value), 1, black)
gameDisplay.blit(Label, (posx, posy))
gameDisplay.blit(Value, (posx + 100, posy))
#Main Loop Object
def game_loop():
global X, Y, FThrustX, FThrustY, FDragY, Time
FThrustY = 0
gameExit = False
while not gameExit:
#Event Checker (Keyboard, Mouse, etc.)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed() #checking pressed keys
if keys[pygame.K_LEFT]:
X -= 1
if keys[pygame.K_RIGHT]:
X +=1
if keys[pygame.K_DOWN]:
Y += 1
if keys[pygame.K_UP]:
Y -=1
#Display Background Fill
gameDisplay.fill(white)
#Plot flightpath
flightpath(pointlist)
#YS: Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
displayMSG(current_pos_x,'x:',20,665)
displayMSG(current_pos_y,'y:',20,680)
#Plot margin
margin(5)
#Move Drone Object
drone(X,Y)
#Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
#No exceeding of display edge
if X > display_width - drone_width: X = display_width - drone_width
if Y > display_height - drone_height: Y = display_height - drone_height
if X < drone_width: X = drone_width
if Y < drone_height: Y = drone_height
pygame.display.update()
#MAIN
game_loop()
pygame.quit()
sys.exit()
One approach is to find the minimum distance between the center of the drone and the line.
Write the function that calculates the minimum distance from a point to a line segment. To do this, use pygame.math.Vector2 and the Dot product:
def distance_point_linesegment(pt, l1, l2):
LV = pygame.math.Vector2(l2[0] - l1[0], l2[1] - l1[1])
PV = pygame.math.Vector2(pt[0] - l1[0], pt[1]- l1[1])
dotLP = LV.dot(PV)
if dotLP < 0:
return PV.length()
if dotLP > LV.length_squared():
return pygame.math.Vector2(pt[0] - l2[0], pt[1]- l2[1]).length()
NV = pygame.math.Vector2(l1[1] - l2[1], l2[0] - l1[0])
return abs(NV.normalize().dot(PV))
Find the line segment with the shortest distance in a loop:
def minimum_distance(pt, pointlist):
min_dist = -1
for i in range(len(pointlist)-1):
dist = distance_point_linesegment(pt, pointlist[i], pointlist[i+1])
if i == 0 or dist < min_dist:
min_dist = dist
return min_dist
Create an alert when the distance exceeds a certain threshold:
def game_loop():
# [...]
while not gameExit:
# [...]
dist_to_path = minimum_distance((X, Y), pointlist)
if dist_to_path > 25:
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
Another possible solution is to use pygame.Rect.clipline and detect the collision of the line segments and the rectangle surrounding the drone:
def intersect_rect(rect, pointlist):
for i in range(len(pointlist)-1):
if rect.clipline(pointlist[i], pointlist[i+1]):
return True
return False
def game_loop():
# [...]
while not gameExit:
# [...]
if not intersect_rect(droneImg.get_rect(center = (X, Y)), pointlist):
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
The interesting part of the question is of course finding the nearest point on the desired path to the actual position; distance is easy. The hard part of that is in turn identifying the nearest element (line segment) of the path; projecting onto it is also straightforward.
If the path is simple enough (in particular, if it doesn’t branch and it’s impossible/disallowed to skip sections at a self-intersection), you can finesse that part by just maintaining that current element in a variable and updating it to the previous or next element when the projection onto one of them is closer than the projection onto the current one. This is a typical algorithm used by racing games to determine the instantaneous order of racers.
I am making small game with pygame and I have made a gun that rotates around its center.
My problem is that I want the gun to rotate by itself to the enemy direction, but I couldn't do that because I can't find the angle between the gun and the enemy to make the gun rotate to it
I have searched and I found that I have to use the atan2 but I didn't find any working code so I hope someone could help me.
Here is my code:
import pygame
from pygame.locals import*
pygame.init()
height=650
width=650
screen=pygame.display.set_mode((height,width))
clock=pygame.time.Clock()
gun=pygame.image.load("m2.png").convert_alpha()
gun=pygame.transform.smoothscale(gun,(200,200)).convert_alpha()
angle=0
angle_change=0
RED=(255,0,0)
x=525
y=155
while True :
screen.fill((150,150,150))
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
quit()
if event.type==KEYDOWN:
if event.key==K_a:
angle_change=+1
if event.key==K_d:
angle_change=-1
elif event.type==KEYUP:
angle_change=0
angle+=angle_change
if angle>360:
angle=0
if angle<0:
angle=360
pygame.draw.rect(screen,RED,(x,y,64,64))
position = (height/2,width/2)
gun_rotate=pygame.transform.rotate(gun,angle)
rotate_rect = gun_rotate.get_rect()
rotate_rect.center = position
screen.blit(gun_rotate, rotate_rect)
pygame.display.update()
clock.tick(60)
And here is a picture trying to make it clear:
How do I solve the problem?
The tangent of the angle between two points is defined as delta y / delta x
That is (y2 - y1)/(x2-x1). This means that math.atan2(dy, dx) give the angle between the two points assuming that you know the base axis that defines the co-ordinates.
Your gun is assumed to be the (0, 0) point of the axes in order to calculate the angle in radians. Once you have that angle, then you can use the angle for the remainder of your calculations.
Note that since the angle is in radians, you need to use the math.pi instead of 180 degrees within your code. Also your test for more than 360 degrees (2*math.pi) is not needed. The test for negative (< 0) is incorrect as you then force it to 0, which forces the target to be on the x axis in the positive direction.
Your code to calculate the angle between the gun and the target is thus
myradians = math.atan2(targetY-gunY, targetX-gunX)
If you want to convert radians to degrees
mydegrees = math.degrees(myradians)
To convert from degrees to radians
myradians = math.radians(mydegrees)
Python ATAN2
The Python ATAN2 function is one of the Python Math function which is
used to returns the angle (in radians) from the X -Axis to the
specified point (y, x).
math.atan2()
Definition Returns the tangent(y,x) in radius.
Syntax
math.atan2(y,x)
Parameters
y,x=numbers
Examples
The return is:
>>> import math
>>> math.atan2(88,34)
1.202100424136847
>>>
Specifically for working with shapely linestring objects, assuming your object (two points) is of the form (min long, min lat, max long, max lat)
from math import atan2,degrees
line = #Your-LineString-Object
lineList = list(line.coords)
def AngleBtw2Points(pointA, pointB):
changeInX = pointB[0] - pointA[0]
changeInY = pointB[1] - pointA[1]
return degrees(atan2(changeInY,changeInX)) #remove degrees if you want your answer in radians
AngleBtw2Points(lineList[0],lineList[1])
In general, the angle of a vector (x, y) can be calculated by math.atan2(y, x). The vector can be defined by 2 points (x1, y1) and (x2, y2) on a line. Therefore the angle of the line is math.atan2(y2-y1, x2-x1).
Be aware that the y-axis needs to be reversed (-y respectively y1-y2) because the y-axis is generally pointing up but in the PyGame coordinate system the y-axis is pointing down. The unit of the angle in the Python math module is Radian, but the unit of the angle in PyGame functions like pygame.transform.rotate() is Degree. Hence the angle has to be converted from Radians to Degrees by math.degrees:
import math
def angle_of_vector(x, y):
return math.degrees(math.atan2(-y, x))
def angle_of_line(x1, y1, x2, y2):
return math.degrees(math.atan2(-(y2-y1), x2-x1))
This can be simplified by using the angle_to method of the pygame.math.Vector2 object. This method computes the angle between 2 vectors in the PyGame coordinate system in degrees. Therefore it is not necessary to reverse the y-axis and convert from radians to degrees. Just calculate the angle between the vector and (1, 0):
def angle_of_vector(x, y):
return pygame.math.Vector2(x, y).angle_to((1, 0))
def angle_of_line(x1, y1, x2, y2):
return angle_of_vector(x2-x1, y2-y1)
Minimale example:
import pygame
import math
def angle_of_vector(x, y):
#return math.degrees(math.atan2(-y, x)) # 1: with math.atan
return pygame.math.Vector2(x, y).angle_to((1, 0)) # 2: with pygame.math.Vector2.angle_to
def angle_of_line(x1, y1, x2, y2):
#return math.degrees(math.atan2(-y1-y2, x2-x1)) # 1: math.atan
return angle_of_vector(x2-x1, y2-y1) # 2: pygame.math.Vector2.angle_to
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
angle = 0
radius = 150
vec = (radius, 0)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cpt = window.get_rect().center
pt = cpt[0] + vec[0], cpt[1] + vec[1]
angle = angle_of_vector(*vec)
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 1)
pygame.draw.line(window, (0, 255, 0), cpt, (cpt[0] + radius, cpt[1]), 3)
pygame.draw.line(window, (255, 0, 0), cpt, pt, 3)
text_surf = font.render(str(round(angle/5)*5) + "°", True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, text_surf.get_rect(bottomleft = (cpt[0]+20, cpt[1]-20)))
pygame.display.flip()
angle = (angle + 1) % 360
vec = radius * math.cos(angle*math.pi/180), radius * -math.sin(angle*math.pi/180)
pygame.quit()
exit()
angle_to can be used to calculate the angle between 2 vectors or lines:
def angle_between_vectors(x1, y1, x2, y2):
return pygame.math.Vector2(x1, y1).angle_to((x2, y2))
Minimal example:
import pygame
import math
def angle_between_vectors(x1, y1, x2, y2):
return pygame.math.Vector2(x1, y1).angle_to((x2, y2))
def angle_of_vector(x, y):
return pygame.math.Vector2(x, y).angle_to((1, 0))
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
angle = 0
radius = 150
vec1 = (radius, 0)
vec2 = (radius, 0)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
cpt = window.get_rect().center
pt1 = cpt[0] + vec1[0], cpt[1] + vec1[1]
pt2 = cpt[0] + vec2[0], cpt[1] + vec2[1]
angle = angle_between_vectors(*vec2, *vec1)
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 1)
pygame.draw.line(window, (0, 255, 0), cpt, pt1, 3)
pygame.draw.line(window, (255, 0, 0), cpt, pt2, 3)
text_surf = font.render(str(round(angle/5)*5) + "°", True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, text_surf.get_rect(bottomleft = (cpt[0]+20, cpt[1]-20)))
pygame.display.flip()
angle1 = (angle_of_vector(*vec1) + 1/3) % 360
vec1 = radius * math.cos(angle1*math.pi/180), radius * -math.sin(angle1*math.pi/180)
angle2 = (angle_of_vector(*vec2) + 1) % 360
vec2 = radius * math.cos(angle2*math.pi/180), radius * -math.sin(angle2*math.pi/180)
pygame.quit()
exit()
I used following code.
import math
def get_angle(x1,y1,x2,y2):
myradians = math.atan2(y1-y2, x1-x2)
mydegrees = math.degrees(myradians)
return mydegrees
# it should return -90 degree
print(get_angle(0,0,0,100))
As one commenter already said, there is only an angle between three points or between two intersecting vectors, that can be derived from this threee points. I assume you want the angle, that the gun and the target (vector 1) and the X-Axis (vector 2) has. Here is a link to a page, that explains how to calculate that angle. http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
Python example:
import math
def angle(vector1, vector2):
length1 = math.sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1])
length2 = math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1])
return math.acos((vector1[0] * vector2[0] + vector1[1] * vector2[1])/ (length1 * length2))
vector1 = [targetX - gunX, targetY - gunY] # Vector of aiming of the gun at the target
vector2 = [1,0] #vector of X-axis
print(angle(vector1, vector2))
You can just use the as_polar method of Pygame's Vector2 class which returns the polar coordinates of the vector (radius and polar angle (in degrees)).
So just subtract the first point vector from the second and call the as_polar method of the resulting vector.
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
point = Vector2(320, 240)
mouse_pos = Vector2(0, 0)
radius, angle = (mouse_pos - point).as_polar()
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
mouse_pos = event.pos
radius, angle = (mouse_pos - point).as_polar()
screen.fill(BG_COLOR)
pg.draw.line(screen, (0, 100, 255), point, mouse_pos)
pg.display.set_caption(f'radius: {radius:.2f} angle: {angle:.2f}')
pg.display.flip()
clock.tick(60)
For anyone wanting a bit more information on this subject check out omnicalculator's break down of the subject:
https://www.omnicalculator.com/math/angle-between-two-vectors
Extracted relevant information:
from math import acos, sqrt, degrees
# returns angle in radians between two points pt1, pt2 where pt1=(x1, y1) and pt2=(x2, y2)
angle = acos((x1 * x2 + y1 * y2)/(sqrt(x1**2 + y1**2) * sqrt(x2**2 + y2**2)))
# convert to degrees
deg_angle = degrees(angle)
Below is the code I wrote.
The object moves along a circular path when I constantly calculate its position and give the coordinates to the obj.rect.x and object.rect.y.
What I need to know is how to rotate the object by something like below.
obj.rect.x += incrementx
obj.rect.y += incrementy
I implemented this in my code bu then the motion becomes anything but circluar.
Please help.
The two images used are here.
http://s5.postimg.org/fs4adqqib/crate_B.png
http://s5.postimg.org/vevjr44ab/plt0.png
import sys, os, pygame
from math import sin,cos,pi, radians
from pygame.locals import *
from standard_object_creator import *
SCREENW = 800
SCREENH = 700
BLUE = (0, 50, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
PURPLE = (145, 0, 100)
YELLOW = (220,220, 0)
pygame.init()
FPSCLOCK = pygame.time.Clock()
FONT1= "data\Cookie-Regular.ttf"
if sys.platform == 'win32' or sys.platform == 'win64':
#os.environ['SDL_VIDEO_CENTERED'] = '2'# center of screen
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (10,30)#top left corner
SCREEN = pygame.display.set_mode((SCREENW, SCREENH))
## self, imagelist, posx, posy, speedx = 0, speedy = 0, value = 0
plat = pygame.image.load("grfx\plt0.png").convert_alpha()
box = pygame.image.load("grfx\crateB.png").convert_alpha()
FPS = 160 # frames per second
platforms = pygame.sprite.Group()
boxes = pygame.sprite.Group()
def maketext(msg,fontsize, colour = YELLOW, font = FONT1):
mafont = pygame.font.Font(font, fontsize)
matext = mafont.render(msg, True, colour)
matext = matext.convert_alpha()
return matext
box = object_factory ([box], 340, 50, 0, 1)
boxes.add(box)
center_x = 450 # x pos in relation to screen width
center_y = 400 # y pos in relation to screen height
radius = 200
angle = -90 #pi / 4 # starting angle 45 degrees
omega = .001 #Angular velocity
for x in xrange(6):
xpos = radius * cos(angle) #+ center_x #Starting position x
ypos = radius * sin(angle) #+ center_x #Startinh position y
obj = object_factory([plat], xpos, ypos)
obj.angle = angle
obj.omega = omega #angula velocity
obj.radius = radius
platforms.add(obj)
angle += 60
mouseposlist = []
all2gether = [platforms, boxes]
while True:
SCREEN.fill(BLACK)
## MOVE THE SPRITE IN A CIRCLE. Each object is placed by varying the step)
for obj in platforms:
obj.angle = obj.angle + obj.omega
## THE CODE BELOW WORKS
obj.rect.x = center_x + (cos(obj.angle) * obj.radius)
obj.rect.y = center_y + (sin(obj.angle) * obj.radius)
## How can I get the same thing to work in this way? by adding the rate of change to the box objects rect.x and rec.t? Why does this not work?
#obj.rect.x += obj.radius * obj.omega * cos(obj.angle)
#obj.rect.y -= obj.radius * obj.omega * sin(obj.angle)
pygame.draw.line(SCREEN, BLUE, (center_x, center_y), (obj.rect.x, obj.rect.y), 2)
for hp in boxes:
hp.rect.x += hp.speedx
hp.rect.y += hp.speedy
hp.move()
hp.collide(platforms)
for thing in all2gether:
thing.update()
thing.draw(SCREEN)
pygame.draw.line(SCREEN, BLUE, (0, SCREENH / 2), (SCREENW, SCREENH / 2), 2)
pygame.draw.line(SCREEN, BLUE, (SCREENW / 2, 0), (SCREENW / 2, SCREENH), 2)
pygame.display.update()
FPSCLOCK.tick(FPS)
##--------------------------------------------------------------
pygame.event.pump()
keys = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
pos = pygame.mouse.get_pos()
val = [pos[0], pos[1], 0, 0]
print val
mouseposlist.append(val)
elif event.button == 3 and mouseposlist != []:
mouseposlist.pop(-1)
if event.type == KEYDOWN and event.key == K_ESCAPE:
print mouseposlist
pygame.quit()
sys.exit()
pygame.time.wait(0)
Your solution for moving the sprite in a circle is the time evaluation of the positional equation. You need to calculate the angle as a function of time. x = r * cos (omega * time). your first solution is a loop on time, incrementing omega by the fractional angle that is provided by the angular velocity. To evaluate a position take the amount of time multiplied by the angular velocity....
I manged to solve my problem and would like to share it. The new code is given below.
This works with Python / Pygame
center_of_rotation_x = SCREENW/2
center_of_rotation_y = SCREENH/2
radius = 200
angle = radians(45) #pi/4 # starting angle 45 degrees
omega = 0.1 #Angular velocity
x = center_of_rotation_x + radius * cos(angle) #Starting position x
y = center_of_rotation_y - radius * sin(angle) #Starting position y
SCREEN.blit(star, (x, y)) # Draw current x,y
angle = angle + omega # New angle, we add angular velocity
x = x + radius * omega * cos(angle + pi / 2) # New x
y = y - radius * omega * sin(angle + pi / 2) # New y
The above code works as it is. But when applied as a class it works differently. I will ask that in another question