1

I have been trying to make this work for a while and I am failing.

I have a Rigidbody2D in a top down 2D level and I am trying to simply move it along the coordinates (the levels are gridlike) so one step / button press equals exactly one square walked. It should only be possible to walk in one of the four directions, and no matter when the user stops the walking motion, it should end on a square. A good game equivalent of what I want to achieve is the lufia / breath of fire / any similar RPG series. I've tried using coroutines to get the update function to wait one second after every step, but that does not seem to work. The closest I've come to is the code down below. Thanks guys!

public class PlayerMovement2D : MonoBehaviour
{    
Rigidbody2D rbody;
Animator anim;    
float speed = 1.25f;
Vector2 pos;
void Start()
{
    rbody = GetComponent<Rigidbody2D>();
    anim = GetComponent<Animator>();
    pos = rbody.position;
}
void FixedUpdate()
{
    Vector2 movement_vector = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
    if (movement_vector.x != 0 || movement_vector.y != 0)
    {
        if (movement_vector.x > 0)
        {
            pos += Vector2.right;
            pos.y = 0;
        }          
        else if (movement_vector.x < 0)
        {
            pos += Vector2.left;
            pos.y = 0;
        }
        else if (movement_vector.y > 0)
        {
            pos += Vector2.up;
            pos.x = 0;
        }
        else if (movement_vector.y < 0)
        {
            pos += Vector2.down;
            pos.x = 0;
        }
        anim.SetBool("IsWalking", true);
        anim.SetFloat("Input_x", movement_vector.x);
        anim.SetFloat("Input_y", movement_vector.y);
    }
    else
    {
        anim.SetBool("IsWalking", false);
    }
    rbody.position = Vector2.MoveTowards(rbody.position, pos, speed * Time.deltaTime);
    //transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
    pos = rbody.position;
    }
}
4

3 回答 3

2

您可以Vector2.Lerp结合Unity'sCoroutine系统使用方法。

public class Movement
    : MonoBehaviour
{
    IEnumerator m_MoveCoroutine;
    float m_SpeedFactor;

    void Update()
    {
        // if character is already moving, just return
        if ( m_MoveCoroutine != null )
            return;

        // placeholder for the direction
        Vector2 direction; // between { -1, -1 } and { 1, 1 }
        // calculate your direction based on the input
        // and set that direction to the direction variable
        // eg. direction = new Vector2(Input.GetAxisRaw("Horizontal") > 0 ? 1 : -1,...)
        // then check if direction is not { 0, 0 }
        if( direction != Vector2.zero )
        {
            // start moving your character
            m_MoveCoroutine = Move(direction);
            StartCoroutine(m_MoveCoroutine);
        }
    }

    IEnumerator Move(Vector2 direction)
    {
        // Lerp(Vector2 a, Vector2 b, float t);
        Vector2 orgPos = transform.Position; // original position
        Vector2 newPos = orgPos + direction; // new position after move is done
        float t = 0; // placeholder to check if we're on the right spot
        while( t < 1.0f ) // loop while player is not in the right spot
        {
            // calculate and set new position based on the deltaTime's value
            transform.position = Vector2.Lerp(orgPos, newPos, (t += Time.deltaTime * m_SpeedFactor));
            // wait for new frame
            yield return new WaitForEndFrame();
        }
        // stop coroutine
        StopCoroutine(m_MoveCoroutine);
        // get rid of the reference to enable further movements
        m_MoveCoroutine = null;
    }
}

此方法假定您可以沿指定方向移动。但是您仍然应该在开始之前检查您的新位置是否“可步行” Move Coroutine

于 2017-03-03T14:04:31.107 回答
2

我认为您想在协程中进行运动,当它处于活动状态时,会在完成之前禁用进一步的运动。

bool isIdle = true;

void Update() {
    if(isIdle) {
        // Your movement code that gives pos
        StartCoroutine(Move(pos));
    }
}


IEnumerator Move(Vector2 destination) {
    isIdle = false;
    do {
        transform.position = Vector2.MoveTowards(transform.position, destination, speed * Time.deltaTime);
        yield return new WaitForEndOfFrame();
    } while (transform.position != destination);
    isIdle = true;
}

如果您不明白,需要进一步澄清或者这种方法不起作用,请告诉我!

于 2017-03-03T13:53:57.880 回答
2

我认为您尝试编程的内容类似于 Unity 网站上的教程集合中的 RogueLike 游戏。首先查看介绍视频,以确认这是您计划实现的目标

https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/project-introduction?playlist=17150

如果是这样,这就是他们的处理方式:

using UnityEngine;
using System.Collections;

    //The abstract keyword enables you to create classes and class members that are incomplete and must be implemented in a derived class.
    public abstract class MovingObject : MonoBehaviour
    {
        public float moveTime = 0.1f;           //Time it will take object to move, in seconds.
        public LayerMask blockingLayer;         //Layer on which collision will be checked.


        private BoxCollider2D boxCollider;      //The BoxCollider2D component attached to this object.
        private Rigidbody2D rb2D;               //The Rigidbody2D component attached to this object.
        private float inverseMoveTime;          //Used to make movement more efficient.


        //Protected, virtual functions can be overridden by inheriting classes.
        protected virtual void Start ()
        {
            //Get a component reference to this object's BoxCollider2D
            boxCollider = GetComponent <BoxCollider2D> ();

            //Get a component reference to this object's Rigidbody2D
            rb2D = GetComponent <Rigidbody2D> ();

            //By storing the reciprocal of the move time we can use it by multiplying instead of dividing, this is more efficient.
            inverseMoveTime = 1f / moveTime;
        }


        //Move returns true if it is able to move and false if not. 
        //Move takes parameters for x direction, y direction and a RaycastHit2D to check collision.
        protected bool Move (int xDir, int yDir, out RaycastHit2D hit)
        {
            //Store start position to move from, based on objects current transform position.
            Vector2 start = transform.position;

            // Calculate end position based on the direction parameters passed in when calling Move.
            Vector2 end = start + new Vector2 (xDir, yDir);

            //Disable the boxCollider so that linecast doesn't hit this object's own collider.
            boxCollider.enabled = false;

            //Cast a line from start point to end point checking collision on blockingLayer.
            hit = Physics2D.Linecast (start, end, blockingLayer);

            //Re-enable boxCollider after linecast
            boxCollider.enabled = true;

            //Check if anything was hit
            if(hit.transform == null)
            {
                //If nothing was hit, start SmoothMovement co-routine passing in the Vector2 end as destination
                StartCoroutine (SmoothMovement (end));

                //Return true to say that Move was successful
                return true;
            }

            //If something was hit, return false, Move was unsuccesful.
            return false;
        }


        //Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
        protected IEnumerator SmoothMovement (Vector3 end)
        {
            //Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter. 
            //Square magnitude is used instead of magnitude because it's computationally cheaper.
            float sqrRemainingDistance = (transform.position - end).sqrMagnitude;

            //While that distance is greater than a very small amount (Epsilon, almost zero):
            while(sqrRemainingDistance > float.Epsilon)
            {
                //Find a new position proportionally closer to the end, based on the moveTime
                Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);

                //Call MovePosition on attached Rigidbody2D and move it to the calculated position.
                rb2D.MovePosition (newPostion);

                //Recalculate the remaining distance after moving.
                sqrRemainingDistance = (transform.position - end).sqrMagnitude;

                //Return and loop until sqrRemainingDistance is close enough to zero to end the function
                yield return null;
            }
        }


        //The virtual keyword means AttemptMove can be overridden by inheriting classes using the override keyword.
        //AttemptMove takes a generic parameter T to specify the type of component we expect our unit to interact with if blocked (Player for Enemies, Wall for Player).
        protected virtual void AttemptMove <T> (int xDir, int yDir)
            where T : Component
        {
            //Hit will store whatever our linecast hits when Move is called.
            RaycastHit2D hit;

            //Set canMove to true if Move was successful, false if failed.
            bool canMove = Move (xDir, yDir, out hit);

            //Check if nothing was hit by linecast
            if(hit.transform == null)
                //If nothing was hit, return and don't execute further code.
                return;

            //Get a component reference to the component of type T attached to the object that was hit
            T hitComponent = hit.transform.GetComponent <T> ();

            //If canMove is false and hitComponent is not equal to null, meaning MovingObject is blocked and has hit something it can interact with.
            if(!canMove && hitComponent != null)

                //Call the OnCantMove function and pass it hitComponent as a parameter.
                OnCantMove (hitComponent);
        }


        //The abstract modifier indicates that the thing being modified has a missing or incomplete implementation.
        //OnCantMove will be overriden by functions in the inheriting classes.
        protected abstract void OnCantMove <T> (T component)
            where T : Component;
    }

链接到教程的这一部分: https ://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/moving-object-script?playlist=17150

于 2017-03-03T13:54:39.550 回答