如何使用平铺移动修复碰撞检测

问题描述

我想要达到的目标:

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

我目前拥有的:

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

问题:

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

我尝试过的事情:

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

一些动图:

Corner detection when wanting to be on the same spot at the same time (works... good enough)

Pushing multiple boxes,sometimes phasing through (not working at all)

Pushing boxes into air zone,sometimes phasing through. (not working,but previous one is more important)

主要代码

    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,rightDirection * 1.4f,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));
        }

        if (_inputManager.GetAction(InputManager.Action.VERTICAL))
        {
            movetoTile(new Vector3(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,pCurrentPosition);

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

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

解决方法

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

系统

您应该使用类似光线投射的系统。基本上,您可以对玩家前方距离为 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;
   }
}

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


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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...