Search code examples
javalibgdxrotationautorotateshouldautorotate

Libgdx - Rotate an object using its movement


I'm trying to implement an autorotation method for a SpriteAnimation class (trying to port SpriteAnimation).

Everything is working, the sprite draws correctly (it uses the correct animations) it just doesn't rotate properly if at all, normally it seems to point to the origin (0, 0) only after its target matches its position while I want its rotation to update as it moves, i've tried both degrees and radians, neither of which work. It should be rotating to match the current direction it's going. I've been struggling with this for about a week now, but still have not gotten the desired result.

Full Code Here

Relevant code:

From SpriteAnimation

// The x position of the sprite's upper left corner pixel.
public int getX() { return (int)position.x; }
public void setX(int value)
{
    prevPosition.x = position.x;
    position.x = value;
    updateRotation();
}

// The y position of the sprite's upper left corner pixel.
public int getY() { return (int)position.y; }
public void setY(int value)
{
    prevPosition.y = position.y;
    position.y = value;
    updateRotation();
}

void updateRotation()
{
    if (rotateByPosition)
    {
        Vector2 rotationVector = new Vector2(position.x - prevPosition.x, position.y - prevPosition.y);
        rotationRad = rotationVector.angle();
        rotationDeg = rotationRad * MathUtils.radiansToDegrees;
    }
}

public void MoveBy(int x, int y)
{
    prevPosition = new Vector2(position);
    position.x += x;
    position.y += y;
    updateRotation();
}

public void Update(float deltaTime)
{
    // Don't do anything if the sprite is not animating
    if (animating)
    {
        // If there is not a currently active animation
        if (getCurrentFrameAnimation() == null)
        {
            // Make sure we have an animation associated with this sprite
            if (animations.size() > 0)
            {
                // Set the active animation to the first animation
                // associated with this sprite
                String[] sKeys = new String[animations.size()];
                int index = 0;
                for (Entry<String, FrameAnimation> mapEntry : animations.entrySet()) {
                    sKeys[index] = mapEntry.getKey();
                    index++;
                }
                setCurrentAnimation(sKeys[0]);
            }
            else
            {
                return;
            }
        }

        // Run the Animation's update method
        getCurrentFrameAnimation().Update(deltaTime);

        // Check to see if there is a "follow-up" animation named for this animation
        if (getCurrentFrameAnimation().getNextAnimation() != null && !getCurrentFrameAnimation().getNextAnimation().isEmpty())
        {
            // If there is, see if the currently playing animation has
            // completed a full animation loop
            if (getCurrentFrameAnimation().getPlayCount() > 0)
            {
                // If it has, set up the next animation
                setCurrentAnimation(getCurrentFrameAnimation().getNextAnimation());
            }
        }
    }
}

public void Draw(SpriteBatch spriteBatch, int xOffset, int yOffset)
{
    updateRotation();   // Calling this while testing to make sure that it is being called
    spriteBatch.draw(getCurrentTextureRegion(), getPosition().x + xOffset - center.x, getPosition().y + yOffset - center.y, center.x, center.y, getCurrentFrameAnimation().getFrameWidth(), getCurrentFrameAnimation().getFrameHeight(), 1.0f, 1.0f, rotationRad);
}

TestScreen class

package com.darkstudio.darkisle.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.darkstudio.darkisle.DarkIsle;
import com.delib.engine.sprite.MobileSprite;

/**
 * Created by DarkEnder on 2017/06/27.
 */

public class TestScreen implements Screen {
    final DarkIsle game;
    OrthographicCamera camera;
    Texture tankTexture;

    MobileSprite mouseTank;

    public TestScreen(final DarkIsle game)
    {
        this.game = game;

        camera = new OrthographicCamera();
        configureCamera();

        tankTexture = new Texture(Gdx.files.internal("MulticolorTanks.png"));

        mouseTank = new MobileSprite(tankTexture, 32, 32);
        mouseTank.getSprite().AddAnimation("red", 0, 32, 32, 32, 8, 0.1f);
        mouseTank.getSprite().AddAnimation("purple", 0, 128, 32, 32, 8, 0.1f, "red");
        mouseTank.getSprite().AddAnimation("yellow", 0, 64, 32, 32, 8, 0.1f);
        mouseTank.getSprite().setAutoRotate(true);
        mouseTank.setPosition(new Vector2(100, 100));
        mouseTank.setTarget(new Vector2(mouseTank.getPosition()));
        mouseTank.setIsPathing(true);
        mouseTank.setEndPathAnimation("yellow");
        mouseTank.setLoopPath(false);
        mouseTank.setSpeed(2);
    }

    @Override
    public void show() {

    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

        update();

        game.batch.begin();

        mouseTank.draw(game.batch);

        game.batch.end();
    }

    @Override
    public void resize(int width, int height) {

    }

    @Override
    public void pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void hide() {

    }

    @Override
    public void dispose() {

    }

    private void configureCamera()
    {
        Vector3 camPos = new Vector3(camera.position);

        float size = 800;
        float cameraWidth = 0;
        float cameraHeight = 0;

        if (Gdx.graphics.getHeight() < Gdx.graphics.getWidth())
        {
            cameraWidth = size;
            cameraHeight = size * Gdx.graphics.getHeight() / Gdx.graphics.getWidth();
        }
        else
        {
            cameraWidth = size * Gdx.graphics.getWidth() / Gdx.graphics.getHeight();
            cameraHeight = size;
        }

        camera.setToOrtho(true, cameraWidth, cameraHeight);
        camera.position.set(camPos.x + camera.viewportWidth / 2f, camPos.y + camera.viewportHeight / 2f, 0);
    }

    private void update() {
        int xTouch = Gdx.input.getX(0);
        int yTouch = Gdx.input.getY(0);
        mouseTank.setTarget(xTouch, yTouch);
        mouseTank.update(Gdx.graphics.getDeltaTime());
    }
}

Edit: Just saying now that I've already tried using atan2 in MathUtils and it gave the same results.

The MobileSprite update method

public void update(float deltaTime)
{
    if (active && movingTowardsTarget)
    {
        if ((target != null))
        {
            // Get a vector pointing from the current location of the sprite
            // to the destination.
            Vector2 Delta = new Vector2(target.x - sprite.getX(), target.y - sprite.getY());

            if (Delta.len() > getSpeed())
            {
                Delta.nor();
                Delta.scl(getSpeed());
                getPosition().add(Delta);
            }
            else
            {
                if (target == sprite.getPosition())
                {
                    if (pathing)
                    {
                        if (queuePath.size() > 0)
                        {
                            target = queuePath.remove();
                            if (loopPath)
                            {
                                queuePath.remove(target);
                            }
                        }
                        else
                        {
                            if (!(endPathAnimation == null))
                            {
                                if (!(getSprite().getCurrentAnimation() == endPathAnimation))
                                {
                                    getSprite().setCurrentAnimation(endPathAnimation);
                                }
                            }

                            if (deactivateAtEndOfPath)
                            {
                                setIsActive(false);
                            }

                            if (hideAtEndOfPath)
                            {
                                setIsVisible(false);
                            }
                        }
                    }
                }
                else
                {
                    sprite.setPosition(target);
                }
            }
        }
    }
    if (active)
        sprite.Update(deltaTime);
}

Solution

  • To get the radians angle:

    void updateRotation()
    {
        if (rotateByPosition)
        {
            Vector2 rotationVector = new Vector2(position.x - prevPosition.x, position.y - prevPosition.y);
            rotationRad = rotationVector.angleRad();
            rotationDeg = rotationRad * MathUtils.radiansToDegrees;
        }
    }
    

    and the draw method needs a degrees angle:

    spriteBatch.draw(
                getCurrentTextureRegion(),
                getPosition().x + xOffset - center.x,
                getPosition().y + yOffset - center.y,
                center.x, center.y,
                getCurrentFrameAnimation().getFrameWidth(), getCurrentFrameAnimation().getFrameHeight(),
                1.0f, 1.0f,
                rotationDeg
        );
    

    The screen mouse position is from the upper left corner the renderer needs from the bottom left corner:

    int xTouch = Gdx.input.getX(0);
    int yTouch = Gdx.graphics.getHeight() - Gdx.input.getY(0);