问题描述
我想要达到的目标:
当移动玩家时,它以 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)
主要代码:
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。有了这个方块列表,它就会向它们按下的方向移动。
这没有经过测试,可能有很多问题。今晚或明晚我将能够对此进行测试。如果您遇到任何简单的错误(例如缺少分号),请尽力解决。如果你发现它们,请在这篇文章的评论中列出错误。
我会在今晚或明晚尝试修复它,具体取决于我有多少工作。