前言
在 IndieGameCamp2017 中,我的小组使用 2ddl 这一插件制作了《面向火焰》。用于实现篝火的光影渲染以及进出光线的检测。
问题
2ddl有一个默认的设定区域,大约是
Rect(-10, -10, 20, 20)。所有发生在这个区域外的光线进出检测,都不能生效。
当时的解决方案
更改整个场景的位置及大小,保证所有的进出检测都发生在这个区域内。(好low)
分析
我这里使用的光线检测是事件InsideFieldOfViewEvent,转到定义后,不难找出,其第一个参数(光线范围内的所有物体数组)传入的是类DynamicLight的成员变量objReached。
那么,objReached是在什么时候被赋值的呢?
查找引用后,发现一共有两处:
1. 调用内部方法GetVerts(Line:235),将objReached作为ref参数传入,修改其内存。
2. 在私有方法GenerateColliderVerts内(Line:1592),通过方法addingObjectToObjectReachedList,插入元素。
这里我们发现一个问题:
两处中总计三种插入元素的情况,其中两种都会检测是否存在父物体,如果存在,则将父物体插入数组objReached。
if(360 != Mathf.RoundToInt(RangeAngle)){
if (Vector3.Angle(transform.InverseTransformPoint(hitp), Vector3.up) < RangeAngle*.5f) { // Light angle restriction
if(_ray.collider.gameObject.transform.parent){
addingObjectToObjectReachedList(objReached, _ray.collider.gameObject.transform.parent.gameObject);
}else{
addingObjectToObjectReachedList(objReached, _ray.collider.gameObject);
}
}
}else{
if(_ray.collider.gameObject.transform.parent){
addingObjectToObjectReachedList(objReached, _ray.collider.gameObject.transform.parent.gameObject);
}else{
addingObjectToObjectReachedList(objReached, _ray.collider.gameObject);
}
}
if(360 != Mathf.RoundToInt(RangeLightAngle)){
if (Vector3.Angle(lightObjectTM.InverseTransformPoint(pos), Vector3.up) < RangeLightAngle*.5f) { // Light angle restriction
if(_ray.collider.gameObject.transform.parent){
if(tmpGOWithinFOV == null) tmpGOWithinFOV = _ray.collider.gameObject.transform.parent.gameObject;
}else{
if(tmpGOWithinFOV == null) tmpGOWithinFOV = _ray.collider.gameObject;
}
}
}else{
if(_ray.collider.gameObject.transform.parent){
if(tmpGOWithinFOV == null) tmpGOWithinFOV = _ray.collider.gameObject.transform.parent.gameObject;
}else{
if(tmpGOWithinFOV == null) tmpGOWithinFOV = _ray.collider.gameObject;
}
}
这里
tmpGOWithinFOV是准备插入objReached的临时变量。
而剩下一种情况是:
由于Unity的RayCast是low accurate,部分物体没有发生射线碰撞。插件中对于这种情况做了修正(DynamicLight::GetVerts() line:316)
if(notifyReachedEventUsed && this.collider.type == CasterCollider.CasterType.PolygonCollider2d || this.collider.type == CasterCollider.CasterType.BoxCollider2d)
{
//foreach(Vector2 objPoint in this.collider.points)
for(int k = 0; k < this.collider.points.Length; k++)
{
if(360 != Mathf.RoundToInt(RangeLightAngle)){
if (Vector3.Angle(lightObjectTM.InverseTransformPoint(pos), Vector3.up) < RangeLightAngle*.5f) { // Light angle restriction
if(((Vector2)pos - this.collider.points[k]).sqrMagnitude < 500f)
{ tmpGOWithinFOV = this.collider.transform.gameObject;
//Debug.Log("FOUND !! " + this.collider.transform.gameObject.name);
break;
}
}
}else
{
if(((Vector2)pos - this.collider.points[k]).sqrMagnitude < 500f)
{ tmpGOWithinFOV = this.collider.transform.gameObject;
//Debug.Log("FOUND !! " + this.collider.transform.gameObject.name);
break;
}
}
}
}
这里
tmpGOWithinFOV是准备插入objReached的临时变量。
总结
objReached记录的是无父级时碰撞体本体,有父级时碰撞体父级。
相对应的,事件OnEnterFieldOfView,OnExitFieldOfView以及InsideFieldOfViewEvent中的参数也是如此。
所以,如果想检测“某特定碰撞体是否触发了事件”,需要根据该碰撞体有没有父级,而分别处理。
正确的解决方案
由于我需要检测的碰撞体是有父级的,所以在检测是否进出光线的时候,需要将判定改为父级与参数的判定。
具体如下:
原错误代码
if (one.GetInstanceID() == gameObject.GetInstanceID())
{
...
}
修改后代码
if (one.GetInstanceID() == transform.parent.gameObject.GetInstanceID())
{
...
}
one为方法InsideFieldOfViewDelegate(GameObject[] go, DynamicLight Light)中第一个参数的单个元素的临时变量。