如何知道两个向量之间的夹角?

38

我正在使用Pygame制作小游戏,我已经制作了一支围绕中心旋转的枪支。我的问题是,我希望这支枪能够自动旋转到敌人方向,但我无法找到枪和敌人之间的角度使得枪可以旋转。我搜索了一些资料,发现需要使用atan2,但我没有找到任何可行的代码,所以希望有人能够帮助我。

这是我的代码:

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) 

这里有一张图片,试图让它更清晰明了:

enter image description here

我该如何解决这个问题?


3
在将数学公式用代码实现之前,您可能希望先在纸上绘制出来。 - DogEatDog
18
两个点之间没有角度... 只有三个点之间才会有角度... - Willem Van Onsem
1
两个向量之间的夹角是angle = acos(v1•v2),其中表示“点乘”?这听起来好像这两个向量由射手当前的位置和枪支当前指向的方向以及敌人的当前位置和位置定义。 - martineau
@martineau 由于枪和目标相对于隐式的 x,y 坐标轴进行定义,因此将使用 tangent = (y2-y1)/(x2-x1)。这允许使用 atan2 函数。 - sabbahillel
在这种情况下,我会假设他想要到坐标系下一个90度分割的角度。 - Neo42
简单易懂:如果你有两个二维向量a和b,那么cos(t) = a点乘b / mag(a) / mag(b),其中mag(x) = sqrt(x1x1 + x2x2)。 - duffymo
7个回答

72

两点之间夹角的切线定义为Δy / Δx,即(y2 - y1) / (x2 - x1)。这意味着math.atan2(dy, dx)给出了两点之间的角度假设你知道定义坐标的基础轴。

假定您的枪是坐标轴中(0, 0)点,以便计算弧度角。一旦您获得了该角度,就可以在其余计算中使用该角度。

请注意,由于角度以弧度为单位,因此您需要在代码中使用math.pi而不是180度。另外,您不需要测试超过360度(2*math.pi)。负数(< 0)的测试是不正确的,因为这样会将其强制为0,这会将目标强制在正方向的x轴上。

因此,计算枪和目标之间角度的代码如下:

myradians = math.atan2(targetY-gunY, targetX-gunX)

如果您想将弧度转换为角度

mydegrees = math.degrees(myradians)

将角度转换为弧度

myradians = math.radians(mydegrees)

Python ATAN2

Python的ATAN2函数是Python数学函数之一,用于返回从X轴到指定点(y,x)的角度(以弧度为单位)。

math.atan2()

定义:返回弧度中的正切(y,x)。

语法
math.atan2(y,x)

参数
y,x=数字

示例
返回值为:

>>> import math  
>>> math.atan2(88,34)  
1.202100424136847  
>>>

非常感谢您,先生。这对我的代码非常有用,也有助于更好地理解角度。 - مهند عبد الجليل
@مهند عبد الجليل 谢谢您,如果您愿意,可以在答案旁边勾选复选框接受它。 - sabbahillel
不好意思,我之前不知道回答问题时要进行翻译。这是我在这里的新手。完成了:) - مهند عبد الجليل
@مهند عبد الجليل,你不必强制选择,如果你愿意,可以标记接受的答案。 - sabbahillel
供参考,这是关于math.atan2()的官方和最新文档:https://docs.python.org/3/library/math.html#math.atan2 - kmui2

7
一般来说,向量(x,y)的角度可以通过math.atan2(y,x)计算。 向量可以由线上的2个点(x1,y1)(x2,y2)定义。 因此,线的角度是math.atan2(y2-y1,x2-x1)
请注意,需要反转y轴(分别为-yy1-y2),因为y轴通常指向上方,但在PyGame坐标系中,y轴指向下方。 在Python的math模块中,角度的单位是弧度,但是在PyGame函数(例如pygame.transform.rotate())中的角度单位为度数。 因此,必须通过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))

这可以通过使用pygame.math.Vector2对象的angle_to方法来简化。该方法计算PyGame坐标系中2个向量之间的角度(以度为单位)。因此,不需要反转y轴并将弧度转换为度数。只需计算矢量与(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)

最简单的例子:

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可用于计算2个向量或线之间的夹角:

def angle_between_vectors(x1, y1, x2, y2):
    return pygame.math.Vector2(x1, y1).angle_to((x2, y2))

最简单的示例:

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

6

针对与 shapely linestring 对象一起使用的情况,假设你的对象(两个点)的形式为 (最小经度, 最小纬度, 最大经度, 最大纬度)

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

3
我用了以下代码。
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))

1
你可以使用 Pygame 的 Vector2 类的 as_polar 方法,该方法返回向量的 极坐标(半径和极角(以度为单位))。
因此,只需从第二个点向量中减去第一个点向量,并调用结果向量的 as_polar 方法即可。
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)

1

正如一位评论者所说,只有三个点或两个相交向量之间才有角度,可以从这三个点中推导出来。我假设您想要枪支和目标(向量1)以及X轴(向量2)之间的角度。这里是一个链接,解释了如何计算该角度。 http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm

Python示例:

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

1
由于枪和目标相对于隐式的x、y轴定义,因此可以使用tangent = (y2-y1)/(x2-x1)。这允许使用atan2。您不需要计算(隐式)轴向量。 - sabbahillel
你说得对,atan2是一个可能的快捷方式。我展示了一般的数学公式以解释原因。 - Alex
1
无论哪种情况,您都不需要使用向量2,因为向量1允许您进行所需的计算。 - sabbahillel

0

对于任何想要了解更多关于此主题的信息的人,请查看omnicalculator对该主题的分析: https://www.omnicalculator.com/math/angle-between-two-vectors

提取相关信息:

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接