Enrique Torres
Enrique Torres

Reputation: 289

Move 2D object at constant speed and turn towards touch point

I've been trying for a while to get a 2D player to work kind of like a bullet that is always moving forward (forward being in this case the local X axis for the GameObject, as that's the way that the character is facing) and only changes direction when you touch a point on the screen, in which case it should smoothly start turning towards that point.

One problem I have is that I can't manage to keep the character moving smoothly at a constant speed in the last direction it was facing before, and the other problem that I'm finding is that the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis, which makes the sprite become invisible to the camera.

Here's the code that I have right now:

Vector3 lastTouchPoint;
private void Start()
{
    lastTouchPoint = transform.position;
}

void Update()
{
    if (Input.touchCount > 0)
    {
        // The screen has been touched so store the touch
        Touch touch = Input.GetTouch(0);

        if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
        {
            // If the finger is on the screen, move the object smoothly to the touch position
            lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));

        }
    }
    transform.position = Vector3.Lerp(transform.position, lastTouchPoint, Time.deltaTime);
    //Rotate towards point
    Vector3 targetDir = lastTouchPoint - transform.position;
    transform.LookAt(lastTouchPoint);

}

Thanks in advance!

Upvotes: 0

Views: 1110

Answers (2)

Enrique Torres
Enrique Torres

Reputation: 289

I ended up finding the answer to my own problem using code to rotate smoothly from another post. Here's the code:

    Vector3 lastTouchPoint;
    Vector3 direction;
    Vector3 vectorToTarget;

    //Character controller variables
    public float moveSpeed = 5f;
    public float angularSpeed = 3f;

    private void Start()
    {
        lastTouchPoint = transform.position;
    }

    void Update()
    {
        if (Input.touchCount > 0)
        {
            // The screen has been touched so store the touch
            Touch touch = Input.GetTouch(0);

            if (touch.phase == TouchPhase.Began)
            {
                // If the finger is on the screen, move the object smoothly to the touch position
                lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
                direction = lastTouchPoint - transform.position;
                vectorToTarget = lastTouchPoint - transform.position;
            }
        }
        transform.position += direction.normalized * moveSpeed * Time.deltaTime;

        float angle = Mathf.Atan2(vectorToTarget.y, vectorToTarget.x) * Mathf.Rad2Deg;
        Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
        transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * angularSpeed);
    }

Upvotes: 0

derHugo
derHugo

Reputation: 90872

keep the character moving smoothly at a constant speed

You probably didn't understand what Lerp actually is: This interpolates between the two positions on the given factor where 0 means fully the first position, 1 means fully the last position and e.g. 0.5f would mean in the center between both positions.

This results in faster speeds if the positions are further apart and becomes slower and slower the smaller the distance between both positions becomes. In some cases especially with a factor that small as in your case the object might even never actually reach the target position.

Using this with a dynamic factor of Time.deltaTime makes no sense as this value changes every frame and jitters somewhere around 0,017 (assumin 60 FPS).

You could rather use Vector3.MoveTowards with a fixed constant speed

// set via the Inspector
public float speedInUnitsPerSecond = 1;

...

transform.position = Vector3.MoveTowards(transform.position, lastTouchPoint, Time.deltaTime * speedInUnitsPerSecond);

if you want to keep moving but stop once the touched position is reached.

If you rather wanted to continue moving in the according direction no matter what you could rather store the direction instead of a position and use a straight forward Transform.Translate

// set via the Inspector
public float speedInUnitsPerSecond = 1;
private Vector2 lastDirection;

privtae void Update()
{
    ...

    // If the finger is on the screen, move the object smoothly to the touch position
    var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
    lastDirection = (touchPosition - transform.position).normalized;

    ...

    // move with constant speed in the last direction
    transform.Translate(lastDirection * Time.deltaTime * speedInUnitsPerSecond);
    ...
}

the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis

Note that Transform.LookAt has an optional second parameterworldUp which by default is Vector3.up so a rotation around the global Y axis!

Since you rather want a rotation around the Z axis you should pass

transform.LookAt(lastTouchPoint, Vector3.forward);

I don't know your setup ofcourse but also note that

LookAt

Rotates the transform so the forward vector points at worldPosition.

As you describe it it is also possible that you don't want the objects forward vector to point towards the target position but actually rather the objects right (X) vector!

You can do this by rather simply directly setting the transform.right like e.g.

transform.right = (lastTouchPoint - transform.position).normalized;

or

transform.right = lastDirection;

Btw it would actually be enough to set this rotation only once, namely the moment it changes so in

if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
    // If the finger is on the screen, move the object smoothly to the touch position
    lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));

    transform.right = (lastTouchPoint - transform.position).normalized;
}

or

if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
    // If the finger is on the screen, move the object smoothly to the touch position
    var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
    lastDirection = (touchPosition - transform.position).normalized;


    transform.right = lastDirection;
}

Upvotes: 0

Related Questions