本文提供了一个基于射线检测的SpotLight光照事件判断的代码,先上一个Debug图大家就明白了。
下面是代码实现:
FlashLight.cs
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public enum LightEventType { Enter, Stay, Exit }
public class LightEventData
{
public string name;
public Action[] methods;
public LightEventData(string na, Action[] meths)
{
name = na;
methods = meths;
}
}
public class RayData
{
public Vector3 position;
public Vector3 direction;
public float distance;
public RayData(Vector3 dir,Light light)
{
position = light.transform.position + direction * distance;
direction = dir;
distance = light.range;
}
public RayData(float x,float y,Light light)
{
float radius = Mathf.Tan(Mathf.Deg2Rad * light.spotAngle / 2) * light.range;
position =light.transform.position +light.transform.forward
* light.range +light.transform.rotation
* new Vector3(x,y) * radius;
direction = Vector3.Normalize(position -light.transform.position);
distance=Vector3.Distance(position,light.transform.position);
}
}
public class HitInfoList:List
{
public HitInfoList() { }
public HitInfoList(HitInfoList copy)
{
foreach (var elem in copy)
this.Add(elem);
}
public bool AddElement(RaycastHit info)
{
RaycastHit result;
if (!ExistElement(info,out result))
{
this.Add(info);
return true;
}
return false;
}
public bool RemoveElement(RaycastHit info)
{
RaycastHit result;
if (ExistElement(info, out result))
{
this.Remove(result);
return true;
}
return false;
}
public HitInfoList GetSubList(HitInfoList another)
{
HitInfoList list = new HitInfoList(this);
foreach(var elem in another)
{
RaycastHit result;
if (ExistElement(elem, out result))
list.Remove(result);
}
return list;
}
public bool ExistElement(RaycastHit info,out RaycastHit result)
{
foreach (var elem in this)
{
string iName = info.collider.name;
string eName = elem.collider.name;
if (iName.Equals(eName))
{
result = elem;
return true;
}
}
result = new RaycastHit();
return false;
}
}
public class FlashLight : MonoBehaviour {
public float dampSpeed=0.5f;
public float rangeOffset=1f;
public bool useMultipleRay=true;
[Range(0.05f,1f)]
public float sectionOfMultipleRay=0.05f;
Light light;
float dampTmp;
float maxLightDistance;
static HitInfoList InfoList = new HitInfoList();
void Start()
{
light = GetComponent();
maxLightDistance = light.range;
}
void Update()
{
ControlLightRange();
DetectObject();
}
public void Switch(bool switchOn) { light.enabled = switchOn; }
void ControlLightRange()
{
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit info;
float distance;
if (Physics.Raycast(ray, out info))
{
distance = Mathf.Clamp(info.distance + rangeOffset, 0, maxLightDistance);
Debug.DrawLine(transform.position, info.point);
}
else
distance = maxLightDistance;
light.range = Mathf.SmoothDamp(light.range, distance, ref dampTmp, dampSpeed);
}
void DetectObject()
{
HitInfoList list = new HitInfoList();
foreach (var data in GetRayData())
{
Ray ray = new Ray(transform.position, data.direction);
RaycastHit info;
if (Physics.Raycast(ray, out info,data.distance))
list.AddElement(info);
Debug.DrawLine(transform.position, data.position, Color.red);
}
int operate = (list.Count == InfoList.Count) ? 0
: ((list.Count > InfoList.Count) ? 1 : -1);
HandleEvent(list, operate);
}
void HandleEvent(HitInfoList current,int operate)
{
foreach (var elem in current)
{
if (operate != -1 && InfoList.AddElement(elem))
LightEventListener.EventHandler(elem, LightEventType.Enter);
}
foreach(var elem in InfoList.GetSubList(current))
{
if (operate != 1 && InfoList.RemoveElement(elem))
LightEventListener.EventHandler(elem, LightEventType.Exit);
}
foreach (var elem in InfoList)
LightEventListener.EventHandler(elem, LightEventType.Stay);
}
List GetRayData()
{
List list = new List();
if (useMultipleRay)
list.AddRange(GetFullCircleRay(new int[] { 1, -1 },
new int[] { 1, -1 },sectionOfMultipleRay,light));
else
list.Add(new RayData(transform.forward,light));
return list;
}
List GetFullCircleRay(int[] xParams, int[] yParams, float section, Light light)
{
List list = new List();
foreach (var x in xParams)
foreach (var y in yParams)
list.AddRange(GetQuarterCircleRay(x, y, section,light));
return list;
}
List GetQuarterCircleRay(int xParam, int yParam,float section,Light light)
{
float x = 0, y = 0;
List list = new List();
while ((yParam > 0) ? y <= 1 : y >= -1)
{
while (x * x + y * y <= 1)
{
list.Add(new RayData(x,y,light));
x += xParam*section;
}
x = 0;
y += yParam * section;
}
return list;
}
}
public abstract class LightEventListener : MonoBehaviour
{
private static List ListenerList = new List();
public static void EventHandler(RaycastHit info, LightEventType type)
{
foreach (var listener in ListenerList)
{
string lName = listener.name;
string iName = info.collider.name;
Action Method = listener.methods[(int)type];
if (lName.Equals(iName) && Method != null)
{
Method(info);
}
}
}
protected abstract void OnLightEnter(RaycastHit info);
protected abstract void OnLightStay(RaycastHit info);
protected abstract void OnLightExit(RaycastHit info);
void OnEnable()
{
LightEventData data = new LightEventData(name,
new Action[] { OnLightEnter, OnLightStay, OnLightExit });
ListenerList.Add(data);
}
}
Test.cs
using UnityEngine;
using System.Collections;
public class Test:LightEventListener{
protected override void OnLightEnter(RaycastHit info)
{
Debug.Log(info.collider.name + "Enter");
}
protected override void OnLightStay(RaycastHit info)
{
Debug.Log(info.collider.name + "Stay");
}
protected override void OnLightExit(RaycastHit info)
{
Debug.Log(info.collider.name + "Exit");
}
}
最后是代码的分析:
首先需要提出的是代码中使用了较多的遍历,所以对性能可能会有一定的影响。在FlashLight的Update函数中做了两件事,一件是在光照点中心发出一条射线,根据射线检测到的物体的距离对SpotLight的Range进行调节。另一件就是发射事件检测的射线并将结果发送给LightEventListener进行事件的分发。Test.cs继承了Listener,并重写事件代码,使用时将FlashLight.cs添加到SpotLight上,将Test.cs添加到具有Collider的物体上即可。