Smooth turn - ended up with a hack

I have spend quite a bit of time today trying to find a way to calculate the rotation to get the ram to make smooth turn.
Eventually I ended up with something that is much easier to understand (for me at least), all boiled down to left or right turn relative to the current heading.

It would be great to have a example of how this should be done in a proper way.

smoothturn

I have pasted the code below if anyone is interested.
I’m sure the code can be improved, but for now I have spend enough time on this little problem and will move on

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMover : MonoBehaviour {
    [SerializeField] List<Waypoint> _path = new List<Waypoint>();
    [SerializeField] float _enemySpeed = 1f;

    void Start() {
        StartCoroutine(SmoothFollowPath());
    }

    enum Heading { north, east, south, west }
    enum Turn { none, left, right }

    private IEnumerator SmoothFollowPath() {
        Heading currentHeading = 0f;
        foreach (var nextPoint in _path) {
            Vector3 startPosition = transform.position;
            Vector3 endPosition = nextPoint.transform.position;
            Vector3 headingDirection = (endPosition - startPosition).normalized;
            Heading heading = getHeading(new Vector2(headingDirection.x, headingDirection.z));
            if (heading != currentHeading) {
                // heading has changed, so turn the object
                yield return SmoothTurnObject(currentHeading, heading);
                currentHeading = heading;
            }
            float travelPercent = 0;
            // make sure we are really looking in the right direction
            transform.LookAt(nextPoint.transform);
            while (travelPercent < 1f) {
                travelPercent += Time.deltaTime * _enemySpeed;
                this.transform.position = Vector3.Lerp(startPosition, endPosition, travelPercent);
                yield return new WaitForEndOfFrame();
            }
        }
    }
    private IEnumerator SmoothTurnObject(Heading from, Heading to) {
        var turn = this.relativeTurn(from, to);
        // Debug.Log($"old heading: {from}, hew heading: {to} ");
        // Debug.Log($" turn: {turn} ");

        float rotatePercent = 0;
        float rotationSpeed = 2f;
        float rotation = turn == Turn.left ? -90f : 90f;
        while (rotatePercent < 1) {
            float f = rotation * (Time.deltaTime * rotationSpeed);
            rotatePercent += Time.deltaTime * rotationSpeed;
            transform.Rotate(Vector3.up, f);
            yield return new WaitForEndOfFrame();
        }
        yield return new WaitForSeconds(0.1f);
    }

    private Heading getHeading(Vector2 direction) {
        if (direction.y > 0.1f) {
            return Heading.north;
        }
        if (direction.y < -0.1f) {
            return Heading.south;
        }
        if (direction.x > 0.1f) {
            return Heading.east;
        }
        return Heading.west;
    }
    private Turn relativeTurn(Heading from, Heading to) {
        // turn left or right relative to current heading
        if (from == Heading.north) {
            switch (to) {
                case Heading.north: return Turn.none;
                case Heading.east: return Turn.right;
                case Heading.west: return Turn.left;
                case Heading.south: return Turn.none;
            }
        }
        if (from == Heading.east) {
            switch (to) {
                case Heading.north: return Turn.left;
                case Heading.east: return Turn.none;
                case Heading.west: return Turn.none;
                case Heading.south: return Turn.right;
            }
        }
        if (from == Heading.south) {
            switch (to) {
                case Heading.north: return Turn.left;
                case Heading.east: return Turn.left;
                case Heading.west: return Turn.right;
                case Heading.south: return Turn.none;
            }
        }
        if (from == Heading.west) {
            switch (to) {
                case Heading.north: return Turn.right;
                case Heading.east: return Turn.none;
                case Heading.west: return Turn.none;
                case Heading.south: return Turn.left;
            }
        }
        return Turn.none;
    }
}

It’s not a hack, it’s your solution. And if it works it’s good.

There is no ‘proper’ way, only a way that works and one that doesn’t. Yours work, so it’s fine, and here’s how I would’ve done it (it’s quick-and-dirty and not perfect).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMover : MonoBehaviour
{
    [SerializeField] List<Waypoint> _path = new List<Waypoint>();
    [SerializeField] float _moveSpeed = 1f;
    [SerializeField] float _rotateSpeed = 1f;

    private void Start()
    {
        StartCoroutine(FollowPath());
    }

    private IEnumerator FollowPath()
    {
        foreach(var waypoint in _path)
        {
            var timer = 0f;
            var currentDirection = transform.forward;
            var directionToWaypoint = (waypoint.transform.position - transform.position).normalized;
            while (!Mathf.Approximately(Vector3.Dot(transform.forward, directionToWaypoint), 1f))
            {
                transform.forward = Vector3.Slerp(currentDirection, directionToWaypoint, timer / _rotateSpeed);
                timer += Time.deltaTime;
                yield return null;
            }

            timer = 0f;
            var startPosition = transform.position;
            while (!Mathf.Approximately(Vector3.Distance(transform.position, waypoint.transform.position), 0f))
            {
                transform.position = Vector3.Lerp(startPosition, waypoint.transform.position, timer / _moveSpeed);
                timer += Time.deltaTime;
                yield return null;
            }
        }
    }
}
1 Like

ahh, thank you. I can see I was close, just missing the .forward part. I will give this a try.

Yeah, Vector3.forward is the world forward axis (Z) and will always be (0,0,1). transform.forward is the game object’s forward axis which is the local Z (0,0,1) but relative to the world it could be anything. Using that to turn, say, 90° left will always be a good 90° no matter which direction your game object is facing relative to the world

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms