我正在Unity中的一个项目中工作,我需要知道玩家的位置,无论玩家是站在平台上还是地面上。播放器可以在这两者之间传送。我编写的这段代码大多数时候都可以工作,除了在某些时候,它返回错误的响应。当我要么从地面传送到平台,反之亦然,或者我几乎站在平台的边缘时,就会发生这种情况。有人可以建议如何处理吗?
private float distance = 0.5f;
void Update(){
RaycastHit hit;
Ray footstepRay = new Ray (transform.position, Vector3.down);
if(Physics.Raycast(footstepRay, out hit, distance)){
if(hit.collider.tag == "Ground"){
Debug.Log ("Player is standing on the ground");
}
else if(hit.collider.tag == "Platform"){
Debug.Log ("Player is standing on the platform");
}
}
}
答案 0 :(得分:2)
请确保为Ray的起始位置添加偏移量,以防止Ray从搜索对象内部开始时出现问题(例如,如果Player停在您的平台上,并穿透了很小的一部分,则可能会发生这种情况)。 / p>
这会增加射线的偏移量,以确保即使地面/平台略微穿透也能检测到:
private float distance = 0.5f;
private float offset = 0.5f;
void Update()
{
RaycastHit hit;
Ray footstepRay = new Ray(transform.position + (Vector3.up * offset), Vector3.down); // FIX: added an offset
if(Physics.Raycast(footstepRay, out hit, distance + offset, LayerMask.GetMask("Ground", "Platform"))) // FIX: added a LayerMask
{
if(hit.collider.tag == "Ground")
{
Debug.Log ("Player is standing on the ground");
}
else if(hit.collider.tag == "Platform")
{
Debug.Log ("Player is standing on the platform");
}
}
}
当从搜索到的对象内部意外启动射线时,偏移量可防止错过检测。
使用显式的LayerMask可确保您不会意外检测到Player或其他不需要的对象。
完全站在另一个边缘时,没有检测到平台是完全不同的问题。发生的情况是,一旦您的播放器的中心不再位于平台上方,您的Ray就将从播放器的中心开始经过平台。您可以通过在不同位置(例如,围绕播放器中心的圆上)发送多条光线来解决此问题。
这在边缘情况下(字面上)使用了多条射线来改善地面/平台检测:
private float distance = 0.5f;
private float yOffset = 0.5f;
private float playerRadius = 0.3f;
void Update()
{
string hitTag = DetectGround(Vector3.zero);
if (hitTag != null)
{
OnFound(hitTag);
return;
}
const int rays = 10;
for (int i = 0; i < rays; ++i)
{
float angle = (360.0f / rays) * i;
Vector3 posOffset = Quaternion.AngleAxis(angle, Vector3.up) * (Vector3.forward * playerRadius);
hitTag = DetectGround(posOffset);
if (hitTag != null)
{
OnFound(hitTag);
return;
}
}
}
void OnFound(string tag)
{
if(tag == "Ground")
{
Debug.Log ("Player is standing on the ground");
}
else if(tag == "Platform")
{
Debug.Log ("Player is standing on the platform");
}
}
string DetectGround(Vector3 posOffset)
{
RaycastHit hit;
Ray footstepRay = new Ray(transform.position + posOffset + (Vector3.up * yOffset), Vector3.down); // FIX: added an offset
if(Physics.Raycast(footstepRay, out hit, distance + yOffset, LayerMask.GetMask("Ground", "Platform"))) // FIX: added a LayerMask
{
return hit.collider.tag;
}
return null;
}
以上代码假定除标签外,您还具有称为“地面”和“平台”的图层。您可以根据需要进行修改。 LayerMask的目的是确保Raycast不考虑除地面和平台以外的任何对象。您可以将它们放置在单独的图层中,也可以放置在某种类型的单个“世界”图层中,也可以放置在您选择的任何内容中,只要Player与地面或平台不在同一图层中即可。
编辑:在某些情况下,站立在平台(边缘)上时有时会检测到地面。如果将distance
字段设置为大于最小地面到平台距离的值,则会发生这种情况。如果该距离恒定,则可以通过相应地调整distance
字段来解决此问题。但是,如果平台在移动,则该方法可能行不通。在这种情况下,使用距离玩家最近的物体会产生更好的效果。
此示例收集所有光线的所有命中,并按距离对它们进行排序。最接近的匹配被认为是理想的结果:
using System.Linq;
private float distance = 0.5f;
private float yOffset = 0.5f;
private float playerRadius = 0.3f;
void Update()
{
List<RaycastHit> allHits = new List<RaycastHit>();
DetectGround(allHits, Vector3.zero);
const int rays = 10;
for (int i = 0; i < rays; ++i)
{
float angle = (360.0f / rays) * i;
Vector3 posOffset = Quaternion.AngleAxis(angle, Vector3.up) * (Vector3.forward * playerRadius);
DetectGround(allHits, posOffset);
}
if (allHits.Any())
{
RaycastHit closestHit = allHits.OrderBy(hit => hit.distance).First();
OnFound(closestHit.collider.tag);
}
}
void OnFound(string tag)
{
if(tag == "Ground")
{
Debug.Log ("Player is standing on the ground");
}
else if(tag == "Platform")
{
Debug.Log ("Player is standing on the platform");
}
}
void DetectGround(List<RaycastHit> hits, Vector3 posOffset)
{
Ray footstepRay = new Ray(transform.position + posOffset + (Vector3.up * yOffset), Vector3.down); // FIX: added an offset
Debug.DrawLine(footstepRay.origin, footstepRay.origin + (footstepRay.direction * (distance + yOffset)), Color.red);
hits.AddRange(Physics.RaycastAll(footstepRay, distance + yOffset, LayerMask.GetMask("Ground", "Platform")));
}
注意:此示例不需要LayerMask才能可靠地工作。如果您的游戏在光线投射附近有许多对撞机,则出于性能原因,使用“图层”来过滤考虑的对象可能仍然有意义。