SFML/C++ Why does my method to move the enemy towards the player stop working when the player is moving?

37 Views Asked by At

the only other code used in this from anywhere else is Player::getPosition(), which is just returning playerSprite.getPosition() and Game::update which passes Player::getPosition() to this function

sf::Vector2f enemyLocation{0.f, 0.f}; starting value of enemyLocation for debugging

///<summary>
/// gets the amount enemy should move
/// called from Game.update()
///</summary>
/// <param name="t_playerPos"> player window position </param>
/// <returns> enemy move amount per frame (normalised) </returns>
sf::Vector2f Enemy::attemptMove(sf::Vector2f t_playerPos)
{
    sf::Vector2f movement = { 0.f, 0.f }; // movement towards player each frame
    sf::Vector2f direction = t_playerPos - enemyLocation; // delta playerPos and enemyPos
    float angle = atan2f(direction.y, direction.x); // angle to player (rad)
    angle = angle * 180.f / M_PI; // convert angle to degrees
    float hyp = 1.f; // length of line to player (used for normalisation of vector)

        // check if enemy is horizontally in line with player
    if (direction.x == 0.f) { 
        if (direction.y > 0.f) { movement.y = hyp; } // move straight down
        else { movement = -hyp; } // move straight up
    }
        // check if enemy is vertically in line with player
    else if (direction.y == 0.f) {
        if (direction.x > 0.f) { movement.x = hyp; } // move right
        else { movement.x = -hyp; } // move left
    }
        // if enemy is not in line with player
    else {
                // ratio of sides y:x = opp:adj
        movement.y = sinf(angle);
        movement.x = cosf(angle); 

                // normalising the vector
        hyp = sqrtf(abs(movement.x * movement.x) + abs(movement.y * movement.y));
        hyp = abs(1.f / hyp); // inverse of pythagoras theorem hypothenuse

        movement.x = hyp * movement.x;
        movement.y = hyp * movement.y; // sqrt(x^2 + y^2) should equal 1
        move(movement);
    }
    return movement; // return to Game::update() (not currently assigned to anything there)
}

///<summary>
/// moves the enemy by the amount it should each frame
/// in final code will be called from Game.update()
/// for now only called from Enemy::attemptMove()
///</summary>
///<param name="t_movement"> amount to move each frame towards player </param>
void Enemy::move(sf::Vector2f t_movement)
{
    enemyLocation += t_movement; // update enemy location
    enemySprite.setPosition(enemyLocation); // set enemy position to updated location
}

my includes are

#include <SFML/Graphics.hpp>
#include <iostream>
#define _USE_MATH_DEFINES
#include <math.h>

I expect the enemy to move in a straight line to the player, which does happen if the player is stationary If the player moves diagonally away from the enemy, this also works, but if the player moves any other direction, the enemy spins in a circle (sometimes spiral) until the player stops moving or starts moving diagonally away from the enemy, or sometimes moves in a straight line in a completely wrong direction. I have tried:

  • assigning attemptMove() to a variable in Game::update and passing that to Enemy::move()
  • including an alpha and beta angle and calculating the ratios of the angle by Sine rule
  • including breakpoints to verify the angle is correct (it always seems to be)
  • removing the checks for "in line" with player and always running the calculation
  • changing if (movement.x/y == 0.f) to if (abs(movement.x/y) < 0.01f) and < epsilon
  • changing where the functions are called from and what args are passed to them
  • a few other changes, none of which seemed to have any promise
  • rewriting large sections of the code outside of the block shown, which naturally had no effect
  • cleaning up my code, (this is actually even cleaner than the one in my actual project, though I did run it to make sure it definitely didn't work before posting it)

nothing seems to have any effect, and a lot of things make the problem worse or more severe, I really don't know what else there is to do

EDIT: Below is working code

void Enemy::attemptMove(sf::Vector2f t_playerPos)
{
    sf::Vector2f direction = t_playerPos - enemyLocation; // playerPos-enemyPos (direction to player)
    float hyp;
     // normalising the vector
    hyp = sqrtf(abs(direction.x * direction.x) + abs(direction.y * direction.y));
    hyp = abs(1.f / hyp); // inverse of pythagoras theorem hypothenuse
    direction.x = hyp * direction.x;
    direction.y = hyp * direction.y; // sqrt(x^2 + y^2) should equal 1
    
move(direction);
}

void Enemy::move(sf::Vector2f t_movement)
{
    enemyLocation += t_movement; // update enemy location
    enemySprite.setPosition(enemyLocation); // set enemy position to updated location
}
1

There are 1 best solutions below

0
Amp On

Thank you to user @trojanfoe again, who said "You are overthinking the issue. To move A towards B at a given speed you need to do nothing more than auto dir = normalize(B - A); A += dir * (speed * deltaTime);"

Below is my code modified to incorporate this much simpler solution (this code already has a deltaTime function elsewhere which fixes the intervals between frame rendering.)

void Enemy::attemptMove(sf::Vector2f t_playerPos)
{
    sf::Vector2f direction = t_playerPos - enemyLocation; // playerPos-enemyPos (direction to player)
    float hyp;
     // normalising the vector
    hyp = sqrtf(abs(direction.x * direction.x) + abs(direction.y * direction.y));
    hyp = abs(1.f / hyp); // inverse of pythagoras theorem hypothenuse
    direction.x = hyp * direction.x;
    direction.y = hyp * direction.y; // sqrt(x^2 + y^2) should equal 1
    
move(direction);
}

void Enemy::move(sf::Vector2f t_movement)
{
    enemyLocation += t_movement; // update enemy location
    enemySprite.setPosition(enemyLocation); // set enemy position to updated location
}