Unity2D — перемещение 2D-объекта по сетке, по времени

Я пытался сделать эту работу некоторое время, и я терплю неудачу.

У меня есть Rigidbody2D на 2D-уровне сверху вниз, и я пытаюсь просто переместить его по координатам (уровни похожи на сетку), поэтому одно нажатие кнопки/шага равно ровно одному пройденному квадрату. Должна быть возможность идти только в одном из четырех направлений, и независимо от того, когда пользователь останавливает движение ходьбы, оно должно заканчиваться квадратом. Хороший игровой эквивалент того, чего я хочу добиться, это луфия/дыхание огня/любая подобная серия РПГ. Я пытался использовать сопрограммы, чтобы заставить функцию обновления ждать одну секунду после каждого шага, но это, похоже, не работает. Самое близкое, к чему я пришел, это код ниже. Спасибо, парни!

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;
    }
}

person M. Vogel    schedule 03.03.2017    source источник
comment
Дайте нам знать, если вы что-то пробовали, или примите ответ, который решил вашу проблему.   -  person Fredrik Schön    schedule 04.03.2017


Ответы (3)


Я думаю, что то, что вы пытаетесь запрограммировать, похоже на игру RogueLike, которая является частью коллекции руководств на веб-сайте Unity. Сначала проверьте вступительное видео, чтобы убедиться, что это то, чего вы планируете достичь.

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

person Ignacio Alorre    schedule 03.03.2017
comment
Эй, спасибо за предложение! Мой коллега предложил это сразу после того, как я написал здесь, поэтому я все равно собирался проверить это. Я смог исправить свой код с помощью другого комментария, но я обязательно прочитаю/посмотрю и это. Спасибо! - person M. Vogel; 06.03.2017

Вы можете использовать метод Vector2.Lerp в сочетании с системой Coroutine Unity.

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.

person Mateusz    schedule 03.03.2017
comment
Благодарю вас! Я думаю, что у меня были некоторые ошибки с моими сопрограммами, эта работает прекрасно. Как вы сказали, мне все еще нужно сгладить коллизии, поскольку предыдущий код, просто использующий коллайдеры и твердые тела, казалось, работал, теперь мой персонаж может проходить сквозь стены. - person M. Vogel; 06.03.2017

Я думаю, вы хотите сделать движение в сопрограмме, которая, пока она активна, отключает дальнейшее движение, пока оно не будет выполнено.

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;
}

Дайте мне знать, если вы не понимаете, вам нужны дополнительные разъяснения или если этот подход не работает!

person Fredrik Schön    schedule 03.03.2017
comment
Привет, Фредрик, извини, что не ответил быстрее. Я попробовал ваш код, и сам по себе он разбил Unity/застрял в цикле while. Я попытался использовать просто бит сопрограммы и изменить его, чтобы он соответствовал моему коду, но это не сработало так же хорошо, как решение там. В любом случае спасибо за вашу помощь! - person M. Vogel; 06.03.2017