【问题标题】:How to fix collision detection with tiled movement如何通过平铺移动修复碰撞检测
【发布时间】:2021-05-17 13:48:19
【问题描述】:

我想要达到的目标:

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

我目前所拥有的:

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

问题:

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

我尝试过的事情:

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

一些 gif:

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

【问题讨论】:

  • 您是否考虑过基于网格的碰撞而不是依赖光线投射?如果您愿意,这可能会容易得多。
  • 我会创建一个包含所有可用位置的数组,然后根据它移动它们吗?并检查是否有东西已经存储在那个位置?应该是可能的,但它仍然是一个 3D 空间,会有重力和一切。不知道我是否喜欢这样一个大数组的想法。
  • 如果对象没有在逻辑上限制在网格中(由于重力或其他原因),那么我的建议可能根本不是一个选项。但是,如果关注的是地图相对于对象数量而言确实很大,那么您可以使用比稀疏数组更紧凑的结构。
  • 好吧,即使有重力,它仍然会被限制在网格中,但我不知道地图会有多大,每个级别都会有所不同。这就是为什么我试图围绕数组找到解决方案的原因,不过我仍然可以尝试使用数组来解决。
  • 可能有更合适的数据结构,但即使是Dictionary&lt;Vector3Int, Obj&gt; 也适用于任意大小的地图。我说无论如何,数据对于分析棋盘的状态都是有用的(例如,如果您需要检查玩家是否对齐了 4 个板条箱)

标签: c# unity3d tiled


【解决方案1】:

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

系统

您应该使用类似光线投射的系统。基本上,您将进行光线投射并检测玩家前方距离为 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。有了这个方块列表,它就会按照它们按下的方向移动它。


这未经测试,可能有很多错误。今晚或明天晚上我将能够对此进行测试。如果您遇到任何简单的错误(例如缺少分号),请尽力解决。如果您发现了它们,请在本文的 cmets 中列出错误。

我会在今晚或明天晚上尝试修复它,具体取决于我的工作量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多