1

我想要达到的目标:

移动玩家时,它以 1 的步长移动,因此在瓷砖中。当对着盒子行走时,只要在您想要移动的方向上没有碰撞器,它就可以移动这些盒子。(现在你可以推多少个盒子没有限制)。当一个盒子(或玩家)被推入一个空中区域时,它将向空中区域的相同方向移动,直到他们离开空中区域。如果它们与空气区末端的盒子发生碰撞,它们会推动这些盒子。

到目前为止我所拥有的:

到目前为止,空气区正在工作,它将箱子推向所需的方向,并在到达终点时停止移动。玩家还可以推动所有的盒子,一旦它们撞到墙上就不能再推动它们了。

问题:

然而,问题主要在于碰撞检测。推箱子时,有可能不再推箱子并作为玩家逐步穿过箱子。我不知道如何解决这个问题。

我尝试过的事情:

我已经尝试了几件事,我什至不知道我已经尝试了什么。

一些动图:

想要同时在同一个位置时进行角点检测(工作......足够好)

推多个盒子,有时逐步通过(根本不工作)

将箱子推入空气区,有时逐步通过。(不起作用,但前一个更重要)

主要代码:

    public abstract class Movement : MonoBehaviour
{
//=========================================================================================
//                                     > Variables <
//=========================================================================================

//------------------------ public ------------------------

public Vector3 toBePosition;     //position it will be in after lerp

public Vector3 leftDirection;
public Vector3 rightDirection;

//----------------------- private ------------------------

private int terrainLayer = 6;
private int movableLayer = 7;

protected Vector3 _currentPosition;
private Vector3 _targetPosition;

protected float _travelTime = 0.1f;
private float timer;

public bool canMove = true;

//=========================================================================================
//                                   > Start/Update <
//=========================================================================================
protected virtual void Start()
{
    toBePosition = transform.position;
    _currentPosition = transform.position;
    _targetPosition = transform.position;
}

protected virtual void Update()
{
    //lerp position
    timer += Time.deltaTime;
    float ratio = timer / _travelTime;
    transform.position = Vector3.Lerp(_currentPosition, _targetPosition, ratio);

    //gravity
    checkForFalling();
    //inputs
    checkForMovement();

}


public void checkForFalling()
{
    if (canMove)
    {
        if (Physics.Raycast(_currentPosition, -Vector3.up, out RaycastHit hit, 1f))
        {
            //if we hit something that isnt a airchannel nor a terrain, it will move down.
            if (hit.collider.gameObject.tag != "AirChannel" && hit.collider.gameObject.layer != terrainLayer)
            {
                moveToTile(-Vector3.up);
            }
        }
        //didnt detect anything, thus we need to fall
        else
        {
            moveToTile(-Vector3.up);
        }
    }
}

//=========================================================================================
//                              > Public Tool Functions <
//=========================================================================================

public void moveToTile(Vector3 pDirection)
{
    if (canMove)
    {
        //get normalized direction just makes sure the direction on the xyz is always either 0 or 1. (sometimes it would be 0.0000001)
        pDirection = getNormalizedDirection(pDirection);
        //if there isnt a wall update our target position to where we want to go.
        if (!wallCheck(_currentPosition + pDirection, _currentPosition))
        {
            _targetPosition = pDirection + _currentPosition;
            toBePosition = _targetPosition;
            timer = 0f;
        }
    }
}

protected void checkForMovement()
{
    //makes sure we dont move multiple tiles within the same amount of time
    //because when holding the button id would stack if we wouldnt do this.
    if ((_targetPosition - transform.position).magnitude < 0.001f)
    {
        canMove = true;
        transform.position = _targetPosition;
        _currentPosition = transform.position;
    }
    else
    {
        canMove = false;
    }
}
//=========================================================================================
//                             > Private Tool Functions <
//=========================================================================================


virtual public bool wallCheck(Vector3 pTargetPosition, Vector3 pCurrentPosition)
{
    //get the direction and make sure they are either 0 or 1 again.
    Vector3 moveDirection = (pTargetPosition - pCurrentPosition).normalized;
    moveDirection = getNormalizedDirection(moveDirection);

    //calculate the left and right tile of the forward tile.
     leftDirection = getLeftFromDirection(moveDirection);
     rightDirection = getRightFromDirection(moveDirection);

    //debug rays to visualize the raycasts.
    Debug.DrawRay(pCurrentPosition - moveDirection * 0.1f, moveDirection * 1.4f, Color.green , 5);
    Debug.DrawRay(pCurrentPosition - moveDirection * 0.1f, leftDirection * 1.4f, Color.green , 5);
    Debug.DrawRay(pCurrentPosition - moveDirection * 0.1f, rightDirection * 1.4f, Color.green, 5);

    
    //============================== Collision Checks ===================================


    //first we check right in front of us.
    if (Physics.Raycast(pCurrentPosition, moveDirection, out RaycastHit frontHit, 1.4f))
    {
        //if we hit terrain we return true because it means we hit a wall
        if (frontHit.collider.gameObject.layer == terrainLayer)
        {
            return true;
        }
        //if we hit a something on a movable layer, we will start a recursive loop to check if there is a empty spot to move into.
        if (frontHit.collider.gameObject.layer == movableLayer)
        {
            return frontHit.collider.gameObject.GetComponent<Movement>().wallCheck(pTargetPosition + moveDirection, pTargetPosition);
        }
        else { return false; }
    }


    //now we check on the left side (for the corner collision)
    if (Physics.Raycast(pCurrentPosition, leftDirection, out RaycastHit leftHit, 1.45f))
    {
        if (leftHit.collider.gameObject.layer == movableLayer)
        {
            //if we hit a movable layer and its also moving into the same position as we are, then move us back 1 tile.
            //even though i return true, and the code should stop and not move anymore, this would still happen, thus needed to move 1 back
            //a fix for this would be appreciated.
            if (pTargetPosition == leftHit.collider.gameObject.GetComponent<Movement>().toBePosition)
            {
                moveToTile(moveDirection *= -1f);
                return true;
            }
            else { return false; }
        }
        else { return false; }
    }



    //We do the same for the right side as we did with the left side.
    if (Physics.Raycast(pCurrentPosition, rightDirection, out RaycastHit rightHit, 1.45f))
    {
        if (rightHit.collider.gameObject.layer == movableLayer)
        {
            if (pTargetPosition == rightHit.collider.gameObject.GetComponent<Movement>().toBePosition)
            {
                moveToTile(moveDirection *= -1f);
                return true;
            }
            else { return false; }
        }
        else { return false; }
    }

    else { return false; }
}

protected Vector3 getNormalizedDirection(Vector3 oldDirection)
{
    //makes sure everything is either 0 or 1.
    Vector3 newDirection = oldDirection;
    if (newDirection.x > 0.1f)
    {
        newDirection.x = 1;
    }
    else if (newDirection.x < -0.1f)
    {
        newDirection.x = -1;
    }
    else
    {
        newDirection.x = 0;
    }

    if (newDirection.z > 0.1f)
    {
        newDirection.z = 1;
    }
    else if (newDirection.z < -0.1f)
    {
        newDirection.z = -1;
    }
    else
    {
        newDirection.z = 0;
    }
    return newDirection;
}

private Vector3 getLeftFromDirection(Vector3 pDirection)
{
    //calulcates the tile on the left side from given direction
    Vector3 left = pDirection;
    if(left.x == 0)
    {
        left.x -= 1;
    }
    if(left.z == 0)
    {
        left.z -= 1;
    }
    return left;
}

private Vector3 getRightFromDirection(Vector3 pDirection)
{
    //calculates the tile on the right side from the given direction.
    Vector3 left = pDirection;
    if (left.x == 0)
    {
        left.x += 1;
    }
    if (left.z == 0)
    {
        left.z += 1;
    }
    return left;
}

玩家移动代码:

    public class PlayerMovement : Movement
{
    [SerializeField] private float _moveSpeed;
    private InputManager _inputManager;
    public int playerPushWeight;


    private void Awake()
    {
        serviceLocator.AddToList("Player1", this.gameObject);
    }


    protected override void Start()
    {
        base.Start();
        _inputManager = serviceLocator.GetFromList("InputManager").GetComponent<InputManager>();
    }
    // Update is called once per frame

    protected override void Update()
    {
        base.Update();
        if (_inputManager.GetAction(InputManager.Action.HORIZONTAL))
        {
            moveToTile(new Vector3(_inputManager.getHorizontalInput(),0,0));
        }

        if (_inputManager.GetAction(InputManager.Action.VERTICAL))
        {
            moveToTile(new Vector3(0, 0, _inputManager.getVerticalInput()));
        }
        //this allows other objects to move this object. (Boxes now can move the player, useful for airchannels)
        if (wallCheckCalled)
        {
            moveToTile(_direction);
        }
        wallCheckCalled = false;

    }

    private Vector3 _direction;
    private bool wallCheckCalled;

    override public bool wallCheck(Vector3 pTargetPosition, Vector3 pCurrentPosition)
    {
        bool isWall = base.wallCheck(pTargetPosition, pCurrentPosition);
        if (!isWall)
        {
            wallCheckCalled = true;
            _direction = pTargetPosition - pCurrentPosition;
        }
        return isWall;
    }

盒子运动:

public class WeightMovement : Movement
{
    //=========================================================================================
    //                                     > Variables <
    //=========================================================================================

    //------------------------ public ------------------------


    //----------------------- private ------------------------


    private Vector3 _direction;
    private bool wallCheckCalled;

    //=========================================================================================
    //                                   > Start/Update <
    //=========================================================================================

    //=========================================================================================
    //                              > Public Tool Functions <
    //=========================================================================================

    override public bool wallCheck(Vector3 pTargetPosition, Vector3 pCurrentPosition)
    {
        bool isWall = base.wallCheck(pTargetPosition, pCurrentPosition);

        if (!isWall)
        {
            wallCheckCalled = true;
            _direction = pTargetPosition - pCurrentPosition;
        }
        return isWall;
    }

    protected override void Update()
    {
        if (wallCheckCalled)
        {
            moveToTile(_direction);
        }
        base.Update();
        wallCheckCalled = false;
    }
4

1 回答 1

0

我没有为此使用平铺地图,而是使用精灵。

系统

您应该使用类似光线投射的系统。基本上,您将进行光线投射并检测玩家前方距离为 1 的物体。然后你应该再次做同样的事情。如果它撞到另一个块,(在相同的方向),那么它会再次做同样的事情。一旦它撞到墙壁或什么都没有,它就会开始移动。如果它撞到墙上,它不会。

运动

因为我们想要一个所有变换都没有小数的运动系统。我将通过每次移动它来做到这一点。


播放器脚本

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

...

RaycastHit hit;
List<Transform> blocks;


void Update()
{
   if (Input.GetKeyDown(KeyCode.W))
   {
      Move(Vector3.forward);
   }
   else if (Input.GetKeyDown(KeyCode.A))
   {
      Move(-Vector3.right);
   }
   else if (Input.GetKeyDown(KeyCode.S))
   {
      Move(-Vector3.forward);
   }
   else if (Input.GetKeyDown(KeyCode.D))
   {
      Move(Vector3.right);
   }
}
void Move(Vector3 direction)
{
   blocks.Clear();
   if (Raycheck(direction))
   {
      Debug.Log(“The player has moved because there was no objects in front of them.”);

      for (int i = 0; i < blocks.Length; i++)
      {
         blocks[i].Translate(direction);
      }
      transform.Translate(direction);
   }
   else
   {
      Debug.Log(“There are objects in the player’s way, and they are trying to move.”);
   }
}
public static void Raycheck(Vector3 direction)
{
   if (Physics.Raycast(transform.position, direction, out hit, 1)
   {
      string tag = hit.collider.gameObject.tag;
      if (tag == “block”)
      {
         Debug.Log(“Raycasting: hit a block”);

         blocks.Add(hit.collider.gameObject.transform);
         return Raycheck(direction);
      }
      if (tag == “wall”)
      {
         Debug.Log(“Raycasting: hit a wall”);
         return false;
      }
   }
   else
   {
      Debug.Log(“Raycasting: finished. Hit the air”);
      return true;
   }
}

在这里,我们得到输入(更新函数),并调用移动函数输入他们移动的方向。我们通过光线投射(光线检查功能)检测玩家面前的方块。它将玩家方式中的块添加到列表中。如果块行的末端有空气,该函数将返回 true,如果末端有墙,则返回 false。有了这个块列表,它会按照他们按下的方向移动它。


这没有经过测试,可能非常有问题。今晚或明天晚上我将能够对此进行测试。如果您遇到任何简单的错误(例如缺少分号),请尽力解决。如果你发现了它们,请在这篇文章的评论中列出错误。

我会在今晚或明天晚上尝试修复它,这取决于我有多少工作。

于 2021-05-17T17:37:50.197 回答