Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
232 views
in Technique[技术] by (71.8m points)

python - pygame 2 dimensional movement of an enemy towards the player, how to calculate x and y velocity?

In the game i am currently making i need to make an ennemy move towards a player (here the enemy is a blob) to make this, i already created a function : def calculate_enemy_movement(enemy): and inside of it i put what the function needs to do :

calculate what x value needs to be added each frame(x velocity), and what y value needs to be added each frame(y velocity) to the enemy.x and enemy.y to walk towards the player.x and player.y and then return x velocity and return y velocity.
This function gets called here : add x velocity and y velocity to x and y pos of enemy each frame enemy_blob.x,enemy_blob.y += calculate_enemy_movement(enemy_blob)

now i just need someone to help me create this function. I've already looked into it and i know it has something to do with the vector mathematics but i'm not sure how exactly it works, so if someone could write the function and explain me how it works i would be really pleased.

if you need the rest of the code :

import pygame
import os
import sys
from math import *
import math
from typing import Tuple
from pygame.locals import *

running = False

class Game():
    def __init__(self):
        main()

pygame.init()

FPS = 60
ani = 4

def diagonal_x_vel(velocity):
    diagonal_x = int((math.pi / 4) * velocity)
    return diagonal_x

def diagonal_y_vel(velocity):
    diagonal_y = int(sin(math.pi / 4) * velocity)
    return diagonal_y

def move(rect, movement):
    global player_rect
    rect.x += movement[0]
    rect.y += movement[1]
    return rect

WIDTH, HEIGHT = 1920, 1080
player_action = "idle"
player_frame = 0

class Player():
    def __init__(self):
        self.main_char_height, self.main_char_width = 35 * 4, 35 * 4
        self.main_char_x = WIDTH / 2 - self.main_char_width / 2
        self.main_char_y = HEIGHT / 2 - self.main_char_height / 2
        self.current_health = 80
        self.maximum_health = 100
        self.health_bar_length = 500
        self.health_ratio = self.maximum_health / self.health_bar_length
        self.moving_right, self.moving_left, self.moving_down, self.moving_up, self.idle = False, False, False, False, True
        self.player_rect = pygame.Rect(self.main_char_x, self.main_char_y, self.main_char_width, self.main_char_height)

        self.player_flip = False
        self.VEL = 8

        def update():
            pass

        def take_dmg(self, amount):
            if self.current_health > 0:
                self.current_health -= amount
            if self.current_health <= 0:
                self.current_health = 0

        def get_health(self, amount):
            if self.current_health < self.maximum_health:
                self.current_health += amount
            if self.current_health >= self.maximum_health:
                self.current_health = self.maximum_health

class Enemy():
    def __init__(self, health, attack_damage, raw_speed, x, y, height, width, action,diagonal_vel_x,diangonal_vel_y):
        self.x = x
        self.y = y
        self.health = health
        self.attack_damage = attack_damage
        self.speed = raw_speed
        self.enemy_rect = pygame.Rect(x,y,width,height)
        self.action = action

WIN = pygame.display.set_mode([WIDTH, HEIGHT], pygame.FULLSCREEN)
pygame.display.set_caption("first game")

player = Player()
animation_frames = {}

def load_animation(path, image_name, frame_duration, scale_width, scale_height):
    global animation_frames

    animation_frame_data = []
    n = 0
    for frame in frame_duration:
        animation_frame_id = image_name + "_" + str(n)
        img_loc = path + "/" + animation_frame_id + ".png"
        animation_image = pygame.transform.scale(pygame.image.load(img_loc),
                                                 (scale_width, scale_height)).convert_alpha()
        animation_image.convert()

        animation_frames[animation_frame_id] = animation_image.copy()
        for i in range(frame):
            animation_frame_data.append(animation_frame_id)
        n += 1
    return animation_frame_data

def change_action(action_var, frame, new_value):
    if action_var != new_value:
        action_var = new_value
    return action_var, frame

animation_database = {}

animation_database["idle"] = load_animation("assets", "darkpurpleknight_idle", [8, 8, 8, 8], player.main_char_width,
                                            player.main_char_height)
animation_database["run_top"] = load_animation("assets", "darkpurpleknight_run_up", [8, 8, 8, 8, 8, 8],
                                               player.main_char_width,
                                               player.main_char_height)
animation_database["run_bot"] = load_animation("assets", "darkpurpleknight_run_down", [8, 8, 8, 8, 8, 8],
                                               player.main_char_width, player.main_char_height)
animation_database["run"] = load_animation("assets", "darkpurpleknight_run", [8, 8, 8, 8, 8, 8], player.main_char_width,
                                           player.main_char_height)
scroll = [0, 0]

blob_height = 17
blob_width = 25
blob_frame = 0

enemy_blob = Enemy(50, 10, 5, 1000 , 1000, 17, 25, "blob_idle",0,0)

animation_database["blob_idle"] = load_animation("assets", "blob_idle", [30, 30], blob_width * 4, blob_height * 4)

clock = pygame.time.Clock()

def collision(rectA, rectB):
    if rectB.right < rectA.left:
        # rectB est à gauche
        return False
    if rectB.bottom < rectA.top:
        # rectB est au-dessus
        return False
    if rectB.left > rectA.right:
        # rectB est à droite
        return False
    if rectB.top > rectA.bottom:
        # rectB est en-dessous
        return False

    elif rectB.right > rectA.left:
        # rectB est en collision avec la gauche
        return True
    elif rectB.bottom < rectA.top:
        # rectB est en collision avec le haut
        return True
    elif rectB.left > rectA.right:
        # rectB est en collision avec la droite
        return True
    elif rectB.top > rectA.bottom:
        # rectB est en collision avec le bas
        return True

bg_img = pygame.transform.scale(pygame.image.load("assets/DUNGEON_PISKEL_REMAKE_WITH_WALL_PROPS-1.png"),
                                (WIDTH * 2, HEIGHT * 2)).convert()

def animate_sprite(frame_counter, action):
    global animation_database
    frame_counter += 1
    if frame_counter >= len(animation_database[action]):
        frame_counter = 0
    img_id = animation_database[action][frame_counter]
    animated_image = animation_frames[img_id]
    return animated_image, frame_counter

#def calculate_enemy_movement(enemy):
    #calculate what x value needs to be added each frame(x velocity), and what y value needs to be added each frame(y velocity) to the enemy.x and enemy.y to walk
    #towards the player.x and player.y.
    #return x velocity and return y velocity

def draw_window(window):
    global enemy_blob
    window = window
    global main_char_x, main_char_y
    global player_frame, bg_img, blob_frame
    global scroll
    global slide

    #add x velocity and y velocity to x and y pos of enemy each frame
    #enemy_blob.x,enemy_blob.y += calculate_enemy_movement(enemy_blob)

    window.blit(bg_img, (-WIDTH / 2 - scroll[0], -HEIGHT / 2 - scroll[1]))

    blob_img, blob_frame = animate_sprite(blob_frame, enemy_blob.action)
    window.blit(blob_img, (enemy_blob.x - scroll[0], enemy_blob.y - scroll[1]))

    player_img, player_frame = animate_sprite(player_frame, player_action)

    window.blit(pygame.transform.flip(player_img, player.player_flip, False), (player.player_rect.x, player.player_rect.y))

    pygame.display.update()

borders = []
coliding = False

def set_borders(x, y, width, height):
    border = pygame.Rect((x, y), (width, height))
    return border

def check_colision(target, list):
    is_coliding = []
    for i in list:
        is_coliding.append(collision(i, target))
    return is_coliding

def main():
    global scroll
    global player_frame
    global player_action
    global player_flip
    global player_rect, rect1, coliding
    global VEL
    global top_wall
    global borders

    global main_char_x, main_char_y

    global moving_left, moving_right, moving_up, moving_down, idle
    game_running = True

    while game_running:
        clock.tick(FPS)

        for event in pygame.event.get():

            if event.type == pygame.KEYDOWN:
                if event.key == K_ESCAPE:
                    game_running = False
                    from start_menu import Open_launcher
                    Open_launcher()
                    sys.exit()

        top_map_border = set_borders(-WIDTH / 2 - scroll[0], -HEIGHT / 2 + 250 - scroll[1], 5000, 1)
        bot_map_border = set_borders(-WIDTH / 2 - scroll[0], HEIGHT + 355 - scroll[1], 5000, 1)
        left_map_border = set_borders(-WIDTH / 2 + 160 - scroll[0], -HEIGHT / 2 - scroll[1], 1, 5000)
        right_map_border = set_borders(WIDTH + 785 - scroll[0], -HEIGHT / 2 + 150 - scroll[1], 1, 5000)

        borders = [top_map_border, bot_map_border, left_map_border, right_map_border]
        enemies = []

        is_coliding_with_borders = check_colision(player.player_rect, borders)
        is_coliding_with_enemy = check_colision(player.player_rect, enemies)

        top_coliding = is_coliding_with_borders[0]
        bot_coliding = is_coliding_with_borders[1]

        left_coliding = is_coliding_with_borders[2]
        right_coliding = is_coliding_with_borders[3]

        keys = pygame.key.get_pressed()

        # EVERY MOVEMENT THAT USES "A"

        if keys[pygame.K_a]:

            # DIAGONAL TOP LEFT
            if keys[pygame.K_w] and not keys[pygame.K_d]:

                if not left_coliding and not top_coliding:
                    player.player_flip = True
                    player_action, player_frame = change_action(player_action, player_frame, "run")
                    scroll[0] -= diagonal_x_vel(player.VEL)
                    scroll[1] -= diagonal_y_vel(player.VEL)

                elif left_coliding and not top_coliding:
                    player.player_flip = True
                    scroll[1] -= player.VEL
                    player_action, player_frame = change_action(player_action, player_frame, "run_top")

                elif top_coliding and not left_coliding:
                    player.player_flip = True
                    scroll[0] -= player.VEL
                    player_action, player_frame = change_action(player_action, player_frame, "run")

                elif left_coliding and top_coliding:
                    player.player_flip = False
                    player_action, player_frame = change_action(player_action, player_frame, "idle")


            # DIAGONAL BOT LEFT
            elif keys[pygame.K_s] and not keys[pygame.K_d]:

                if not left_coliding and not bot_coliding:
                    player.player_flip = True
                    player_action, player_frame = change_action(player_action, player_frame, "run")
                    scroll[0] -= diagonal_x_vel(player.VEL)
                    scroll[1] += diagonal_y_vel(player.VEL)

                elif left_coliding and not bot_coliding:
   

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Compute the vector from the the enemy position to the the player position:

dx = player_x - enemy_x
dy = player_y - enemy_y

Compute the length of the vector (Euclidean distance):

dist = math.sqrt(dx*dx + dy*dy)

or

dist = math.hypot(dx, dy)

Normalize the vector (Unit vector). A normalized vector has a length of 1:

if dist > 0:
    dx /= dist
    dy /= dist

Move the enemy a certain distance in the direction of the vector. Make sure the moving distance is no greater than the remaining distance of the enemy to the player:

move_dist = min(enemy_vel, dist)

enemy_x += move_dist * dx
enemy_y += move_dist * dy 

For a more sophisticated solution see How to make smooth movement in pygame

See also Follow target or mouse


Minimal example:

import pygame, math

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
player_x, player_y, player_vel = 100, 100, 5
enemy_x, enemy_y, enemy_vel = 300, 300, 3

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False          
        
    keys = pygame.key.get_pressed()
    player_x = max(10, min(390, player_x + player_vel * (keys[pygame.K_d] - keys[pygame.K_a])))
    player_y = max(10, min(390, player_y + player_vel * (keys[pygame.K_s] - keys[pygame.K_w])))

    dx = player_x - enemy_x
    dy = player_y - enemy_y
    dist = math.hypot(dx, dy)
    if dist > 0:
        enemy_x += min(enemy_vel, dist) * dx / dist
        enemy_y += min(enemy_vel, dist) * dy / dist

    window.fill(0)
    pygame.draw.circle(window, (0, 128, 255), (player_x, player_y), 10)
    pygame.draw.circle(window, (255, 32, 32), (enemy_x, enemy_y), 10)
    pygame.display.flip()

pygame.quit()
exit()

For your particular code, the calculate_enemy_movement function might look like this:

def calculate_enemy_movement(enemy_blob):
    dx = player.player_rect.x - enemy_blob.x
    dy = player.player_rect.y - enemy_blob.y
    dist = math.hypot(dx, dy)
    if dist > 0:
        move_x = min(enemy_blob.speed, dist) * dx / dist
        move_y = min(enemy_blob.speed, dist) * dy / dist
        return move_x, move_y
    return 0, 0
move_x, move_y = calculate_enemy_movement(enemy_blob)
enemy_blob.x += move_x
enemy_blob.y += move_y

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...