Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame trigonometry: Following the hypotenuse?

I have a method in my Enemy class called huntPlayer. It takes a player object p. Here it is:

def huntPlayer(self, p):   
  if self.dist2p < 200:
    self.hunting = True
    if p.x > self.rect.x:
      self.rect.x += self.speed #this is a constant at value 1
    elif p.x < self.rect.x:
      self.rect.x -= self.speed
    else:
      self.rect.x += 0
    if p.y > self.rect.y:
      self.rect.y += self.speed
    elif p.y < self.rect.y:
      self.rect.y -= self.speed 
    else:
      self.rect.y += 0
  else:
    self.rect.x += 0
    self.rect.y += 0

The enemies are randomly placed around a 2d top down plain, they randomly roam this plain. I have calculated the hypotenuse which is shortest distance to the player = Enemy.dist2p -- When the dist2p value < 200. The enemy will move towards player, p.x and p.y respectively.

My solution above is crude so therefore my problem is the enemy equally moves 1 place on the x or y axis resulting in a diagonal movement to each axis, then sliding along the axis until it reaches the player. (The player is in a fixed position near the centre screen.)

Can you help me fix the huntPlayer method/algorithm so the enemy follows the hypotenuse path to the player, rather than quickest path to x/y axis?

EDIT: If you need any further info I may have left out, let me know.

like image 647
zzoop Avatar asked Feb 06 '23 03:02

zzoop


2 Answers

Moving on the hypotenuse will most likely require your object to move less than one pixel each frame in either the y or x-axis, and since rects only can hold integers you'd need a new attribute position which contains the position of the sprite in float precision. You can use pygame.math.Vector2 to create a vector with useful methods such as normalize() and adding, subtracting, multiplying with other vectors etc.

Assuming you've created an attribute self.position = pygame.math.Vector2(0, 0) (or whatever position you want it to start on) you could do something like this:

def hunt_player(self, player):
    player_position = pygame.math.Vector2(player.rect.topleft)
    direction = player_position - self.position
    velocity = direction.normalize() * self.speed

    self.position += velocity
    self.rect.topleft = self.position

By subtracting the player's position with the enemy's position, you'll get a vector that points from the enemy to the player. If we would to add the direction vector to our position we would teleport to the player immediately. Instead we normalize the vector (making it to length 1 pixel) and multiply our speed attribute. The newly created vector will be an vector pointing towards the player with the length of our speed.

Full example

import pygame
pygame.init()


SIZE = WIDTH, HEIGHT = 720, 480
FPS = 60
BACKGROUND_COLOR = pygame.Color('white')

screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()


class Hunter(pygame.sprite.Sprite):

    def __init__(self, position):
        super(Hunter, self).__init__()

        self.image = pygame.Surface((32, 32))
        self.image.fill(pygame.Color('red'))
        self.rect = self.image.get_rect(topleft=position)
        self.position = pygame.math.Vector2(position)
        self.speed = 2

    def hunt_player(self, player):
        player_position = player.rect.topleft
        direction = player_position - self.position
        velocity = direction.normalize() * self.speed

        self.position += velocity
        self.rect.topleft = self.position

    def update(self, player):
        self.hunt_player(player)


class Player(pygame.sprite.Sprite):

    def __init__(self, position):
        super(Player, self).__init__()

        self.image = pygame.Surface((32, 32))
        self.image.fill(pygame.Color('blue'))
        self.rect = self.image.get_rect(topleft=position)

        self.position = pygame.math.Vector2(position)
        self.velocity = pygame.math.Vector2(0, 0)
        self.speed = 3

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.velocity.x = -self.speed
        elif keys[pygame.K_RIGHT]:
            self.velocity.x = self.speed
        else:
            self.velocity.x = 0

        if keys[pygame.K_UP]:
            self.velocity.y = -self.speed
        elif keys[pygame.K_DOWN]:
            self.velocity.y = self.speed
        else:
            self.velocity.y = 0

        self.position += self.velocity
        self.rect.topleft = self.position

player = Player(position=(350, 220))
monster = Hunter(position=(680, 400))
running = True
while running:

    clock.tick(FPS)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    player.update()
    monster.update(player)

    screen.fill(BACKGROUND_COLOR)
    screen.blit(player.image, player.rect)
    screen.blit(monster.image, monster.rect)

    pygame.display.update()

Result

enter image description here

like image 86
Ted Klein Bergman Avatar answered Feb 07 '23 17:02

Ted Klein Bergman


Since we want to move along the hypotenuse we can use Pythagoras theorem. Here's a brief snippet the should give you the general idea.

I'll use p.x, p.y for the player's position and e.x, e.y for the enemy's position.

# Find the horizontal & vertical distances between player & enemy
dx = p.x - e.x
dy = p.y - e.y

#Get the hypotenuse
d = sqrt(dx*dx + dy*dy)

#Calculate the change to the enemy position
cx = speed * dx / d
cy = speed * dy / d
# Note that sqrt(cx*cx + cy*cy) == speed

# Update enemy position
e.x += cx
e.y += cy

You need to add some extra code to this to make sure that d isn't zero, or you'll get a division by zero error, but that only happens when the enemy reaches the player, so I assume you want to do something special when that happens anyway. :)

I should mention that this technique works best if the positions are floats, not integer pixel coordinates.

like image 34
PM 2Ring Avatar answered Feb 07 '23 16:02

PM 2Ring