Search code examples
c++game-physicssfml

how to parent object to another object and affect its position through rotation (make object rotate around other object)


For context, I'm making a top down shooter game where the player always rotates/faces itself to the mouse cursor. That can be easily done, but now I'm stuck in positioning the weapon that the player hold (I separate the weapon entity and the player entity because I want the player to be able to switch weapons). I have to make the weapon also rotates to the same angle as the player (which is also easily done by just getting the player's rotation angle and applying that to the weapon as well). Then the part where I'm really stuck is to always position the weapon like it's revolving around the player (with a bit offset). With no further ado, here's the code:

class Player
{
public:
    Player(string skin)
    {
        this->skin.loadFromFile("gfx/skins/" + skin + ".png");
        player.setTexture(this->skin);
        player.setOrigin(Vector2f(7, 6.5f));
    }

    void SetScale(float x, float y)
    {
        player.setScale(x, y);
    }

    void SetPosition(float x, float y)
    {
        x_pos = x;
        y_pos = y;
    }

    Vector2f GetScale()
    {
        return player.getScale();
    }

    Vector2f GetPosition()
    {
        return Vector2f(x_pos, y_pos);
    }

    float GetRotation()
    {
        return rotate_angle;
    }

    void Update(float delta_time, Vector2f mouse_pos)
    {
        if (Keyboard::isKeyPressed(Keyboard::A) || Keyboard::isKeyPressed(Keyboard::D))
        {
            if (Keyboard::isKeyPressed(Keyboard::A))
            {
                vel_x = smoothMotion(-185.f, vel_x, delta_time);
            }
            if (Keyboard::isKeyPressed(Keyboard::D))
            {
                vel_x = smoothMotion(185.f, vel_x, delta_time);
            }
        }
        else
            vel_x = smoothMotion(0.f, vel_x, delta_time);

        if (Keyboard::isKeyPressed(Keyboard::W) || Keyboard::isKeyPressed(Keyboard::S))
        {
            if (Keyboard::isKeyPressed(Keyboard::W))
            {
                vel_y = smoothMotion(-185.f, vel_y, delta_time);
            }
            if (Keyboard::isKeyPressed(Keyboard::S))
            {
                vel_y = smoothMotion(185.f, vel_y, delta_time);
            }
        }
        else
            vel_y = smoothMotion(0.f, vel_y, delta_time);

        x_pos += vel_x * delta_time;
        y_pos += vel_y * delta_time;
        player.setPosition(x_pos, y_pos);

        player_mouse_distance = Vector2f(mouse_pos.x - x_pos, mouse_pos.y - y_pos);
        rotate_angle = radToDeg(atan2(player_mouse_distance.y, player_mouse_distance.x));

        player.setRotation(rotate_angle);
    }

    void Draw(RenderWindow& window)
    {
        window.draw(player);
    }

public:
    Vector2f player_mouse_distance;

private:
    Sprite player;
    Texture skin;
    float x_pos, y_pos;
    float vel_x = 0.f, vel_y = 0.f;
    float rotate_angle;
};

class Weapon
{
public:
    Weapon(string weapon_name)
    {
        weapon_texture.loadFromFile("gfx/weapons/" + weapon_name + ".png");
        weapon.setTexture(weapon_texture);
    }

    void SetScale(float x, float y)
    {
        weapon.setScale(x, y);
    }

    void SetPosition(float x, float y)
    {
        x_pos = x;
        y_pos = y;
    }

    void Update(Player player, float delta_time)
    {
        SetPosition((player.GetScale().x * (9 - 7)) /* <- offset */ * cos(player.GetRotation()) + player.GetPosition().x, (player.GetScale().y * (6.5 - 5)) * sin(player.GetRotation()) + player.GetPosition().y);
        weapon.setPosition(x_pos, y_pos);
        weapon.setRotation(player.GetRotation());
    }

    void Draw(RenderWindow& window)
    {
        window.draw(weapon);
    }

private:
    Sprite weapon;
    Texture weapon_texture;
    float x_pos, y_pos;
    float vel_x = 0.f, vel_y = 0.f;
    float rotate_angle;
};

I'm using C++ and SFML 2.5.1 by the way, but any answer using other language or other graphics library (like Pygame, etc) can be accepted too (since the physics uses the same math formulas anyways). I watched tutorials about this, but most of them uses game engines like Unity and Godot. They simply just parents the player entity to the weapon entity so that the weapon can also change position when player is rotating. I figured out that cosine and sine function must be the key formula to implement that, but if I'm wrong please correct me. Any help is appreciated :]


Solution

  • First, in Player.Update(), the formula for rotation angle should be atan2(y,x), do not convert it to degrees as sin and cos take radians as input. If other parts of your project rely on Player.rotate_angle to be in degrees, you should convert them back to radians in Weapon.Update(). However, I recommend using radians as all of the C++ base trig functions take radians as input.

    In Weapon.Update(), you are applying different offset multipliers to the x and y arguments for SetPosition: (9 - 7) to the x coordinate and (6.5 - 5) to the y coordinates. These should be singular constants instead of expressions like that, and they have to be the same unless you want the Weapon to have an elliptical orbit. Replace those expressions with a constant variable defined somewhere in the Weapon class.

    Additionally, player.GetScale() could have different x and y values, so you can replace player.GetScale().x and player.GetScale().y with some new method like Player.GetScaleMagnitude() that returns the length of the vector from player.GetScale() as a float. However, player.GetScale() contributing to an elliptical orbit could be visually beneficial depending on how you want the game to look.