如何使用 math.atan2 函数检测一个对象是否在另一个对象的 "line of sight" 中?

How to detect if an object is in the "line of sight" of another object using math.atan2 function?

我在尝试理解如何 "detect" 如果某些东西在玩家的想象 "line of sight" 中时遇到了严重的问题。我创建了一个简单的墙。这个想法是在玩家瞄准墙壁并同时点击鼠标按钮时打印一些东西。

这是我的代码:

import sys
import pygame
import math
pygame.init()

screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
running = True

class Actor:

    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

class Wall:

    def __init__(self):
        Actor.__init__(self, 300, 100, 128, 32)
        self.surface = pygame.Surface((self.w, self.h))
        self.surface.fill((0, 0, 0))

    def draw(self):
        screen.blit(self.surface, (self.x, self.y))

class Player(Actor):

    def __init__(self):
        Actor.__init__(self, 0, 0, 64, 64)
        self.surface = pygame.transform.scale(pygame.image.load("GFX/player.png"), (self.w, self.h))
        self.rotated_surface = self.surface.copy()
        self.rect = self.surface.get_rect()
        self.directions = [False, False, False, False]
        self.speed = 0.1
        self.running = False

    def rotate(self):
        mouse_x = pygame.mouse.get_pos()[0]
        mouse_y = pygame.mouse.get_pos()[1]
        angle = math.atan2(mouse_y - (self.y + (self.w / 2)), mouse_x - (self.x + (self.w / 2)))
        angle = angle * (180 / math.pi)
        rot_image = pygame.transform.rotozoom(self.surface, -angle + 270, 1)
        rot_rect = self.rect.copy()
        rot_rect.center = rot_image.get_rect().center
        self.rotated_surface = rot_image

    def move(self):
        if self.directions[0]:
            self.y -= self.speed
        if self.directions[1]:
            self.y += self.speed
        if self.directions[2]:
            self.x -= self.speed
        if self.directions[3]:
            self.x += self.speed
        if self.running:
            self.speed = 0.2
        else:
            self.speed = 0.1

    def draw(self):
        screen.blit(self.rotated_surface, (self.x, self.y))
        pygame.draw.aaline(screen, (0, 255, 0), (player.x + (player.w / 2), player.y), pygame.mouse.get_pos())

    def fire(self, actor):
        bullet_x_pos = self.x + (self.w / 2)
        bullet_y_pos = self.y
        # ...

player = Player()
wall = Wall()

def redraw():
    screen.fill((75, 0, 0))
    player.draw()
    player.move()
    wall.draw()
    pygame.display.flip()

while (running):
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            sys.exit()
        elif e.type == pygame.KEYDOWN:
            if e.key == pygame.K_ESCAPE:
                sys.exit()
            if e.key == pygame.K_w:
                player.directions[0] = True
            if e.key == pygame.K_s:
                player.directions[1] = True
            if e.key == pygame.K_a:
                player.directions[2] = True
            if e.key == pygame.K_d:
                player.directions[3] = True
            if e.key == pygame.K_LSHIFT:
                player.running = True
        elif e.type == pygame.KEYUP:
            if e.key == pygame.K_w:
                player.directions[0] = False
            if e.key == pygame.K_s:
                player.directions[1] = False
            if e.key == pygame.K_a:
                player.directions[2] = False
            if e.key == pygame.K_d:
                player.directions[3] = False
            if e.key == pygame.K_LSHIFT:
                player.running = False
        elif e.type == pygame.MOUSEMOTION:
            player.rotate()
        elif e.type == pygame.MOUSEBUTTONDOWN:
            print("Yep")

    redraw()

图片如下:

网络上没有足够的资料,尤其是 Python / Pygame(我猜射手在图书馆里不是很常见)。我不知道如何根据来自 atan2 的 return 角度 return 一个布尔值,告诉我玩家正在瞄准物体。

Python 的 atan2 是一种神奇的东西,它不需要玩弄 "x" 和 "y" 方向的符号来获得完整的 360 度角。 Ant Python 的数学模型甚至有一个函数可以将弧度结果转换回度数(然后,只需将 360 加到负数上即可得到 0:360 范围而不是 -180:180)。

鉴于此,稍微注意正确获取位置 - 您可以通过检查所有矩形的角方向并选择来检查矩形 "line of sight" 相对于给定位置的最小和最大角度最极端的值。 也就是说,由于发现了循环值,所以有很多极端情况 - 我想我已经涵盖了所有情况。

class Actor(object):
    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def __len__(self):
        return 4

    def __getitem__(self, item):
        return {0: self.x, 1: self.y, 2: self.w, 3: self.h}[item]


def get_direction_range(rect, position):
    min_d = 181
    max_d = -181
    # Due to the cases where the target rect crosses the 180, -180 line, always keep the bottom corners last:
    for corner in (('top', 'left'), ('top', 'right'), ('bottom', 'left'), ('bottom', 'right')):
        tx, ty = getattr(rect, corner[1]), getattr(rect, corner[0])
        print(tx, ty)
        # Calculate vector from given position to target corner
        vx = tx - position[0]
        vy = -(ty - position[1])

        direction = math.degrees(math.atan2(vy, vx))
        if direction < min_d and (not (direction < 0 and min_d > 0 and min_d > 90) or min_d == 181) :
            min_d = direction
        if direction > max_d or (direction < 0 and max_d > 0 and max_d > 90):
            print(direction, "max")
            max_d = direction
    min_d += 0 if min_d > 0 else 360
    max_d += 0 if max_d > 0 else 360
    return min_d, max_d


def check_target(direction, position, rect):
    if not isinstance(rect, pygame.Rect):
        rect = pygame.Rect(rect)
    if position in rect:
        return True

    min_d, max_d = get_direction_range(rect, position)
    if max_d < min_d:
        max_d, min_d = min_d, max_d
    if abs(max_d - min_d) > 180:
        return max_d <= direction <= 360 or 0 <= direction <= min_d
    return min_d <= direction <= max_d


def dl(scr, pos, direction):
    xx = math.cos(math.radians(direction)) * 200
    yy = -math.sin(math.radians(direction)) * 200
    pygame.draw.line(scr, (0,255,0), pos, (pos[0] + xx, pos[1] + yy))
    pygame.display.flip()

你可以让 "Actor" class 从 pygame.Rect 派生(如果你不能使用 Rect 本身) - 我在它上面添加了方法所以它至少可以转换为Rect,它使我们可以轻松地选择角坐标。 无论如何,请注意,如果您正在使用 Python 2.x(我相信是这样,由于在 Python3 下安装 pygame 很困难),您所有的 classes 应该继承自object.

我在实时会话中用来调试的 dl 函数 - 它可能对您也很有用。