【发布时间】: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)
主要代码:
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<Vector3Int, Obj>也适用于任意大小的地图。我说无论如何,数据对于分析棋盘的状态都是有用的(例如,如果您需要检查玩家是否对齐了 4 个板条箱)