Unity LineRenderer 射线检测 激光攻击

在进行激光攻击的脚本编写前,我们先进行一定程度的想象,思考激光和普通的远程攻击有哪些不太一样的地方。

正常的远程攻击例如子弹,箭矢,技能波等,都有明确的弹道,且无法同时命中多个敌人,只要命中敌人后就会被销毁。(特殊技能除外)

但激光可以认为是一种持续性的范围伤害,只是它的范围(长度)是不固定的,在激光的发射阶段,它会在第一个被命中的目标或障碍物处截断。

激光成型后,一般来说,它将不再被运动的生命体目标截断,反而是依靠它已经生成的光柱将该目标弹开并造成伤害。

当然,如果之前被命中的目标从激光的光柱范围内移开,这时激光会自动延长至下一被命中的目标或障碍物位置。

激光一旦发出,在它的生命周期内,只延长不缩短,直至最后衰减消失。

 

激光发射的过程如下:

1.从起始的发射点射出一条不断向前运动的射线,到达目标点的速度非常快,一般肉眼很难捕捉。直到遇到障碍物截断,不然持续向前延伸。

2.激光一开始是以极小的宽度开始扩散它的能量,它的宽度在发射过程中是由细到宽最终到达极限宽度的。而不是恒定不变的。

3.激光由于快速运动势必会与空气产生摩擦,一部分电光会在激光运动的轨迹周围闪现。

4.激光有生命周期,也可以是停止持续供能后衰减。但激光衰减的过程中长度不会发生变化,而是通过类似于能量迅速收束的方式使整个光柱逐渐变细直至消失,周围的电光也在此衰减过程中逐渐消失。

上面想象模拟了一束激光从生成到凋亡的整个过程,基于此,先定义几种状态:

 1 public enum EmissionRayState
 2 {
 3     Off,
 4     On
 5 }
 6 
 7 public enum EmissionLifeSate
 8 {
 9     None,
10     //创建阶段
11     Creat,
12     //生命周期阶段
13     Keep,
14     //衰减阶段
15     Attenuate
16 }

主循环的状态切换:

 1     void Update()
 2     {
 3         switch (State)
 4         {
 5             case EmissionRayState.On:
 6                 switch (LifeSate)
 7                 {
 8                     case EmissionLifeSate.Creat:
 9                         ShootLine();
10                         break;
11                     case EmissionLifeSate.Keep:
12                         ExtendLineWidth();
13                         break;
14                     case EmissionLifeSate.Attenuate:
15                         CutDownRayLine();
16                         break;
17                 }
18                 break;
19         }
20     }

属性列表:

 1     //发射位置
 2     public Transform FirePos;
 3     //激光颜色
 4     public Color EmissionColor = Color.blue;
 5     //电光颜色
 6     public Color EleLightColor = Color.blue;
 7     //发射速度
 8     public float FireSpeed = 30f;
 9     //生命周期
10     public float LifeTime = .3f;
11     //最大到达宽度
12     public float MaxRayWidth = .1f;
13     //宽度扩展速度
14     public float WidthExtendSpeed = .5f;
15     //渐隐速度
16     public float FadeOutSpeed = 1f;
17     //单位电光的距离
18     public float EachEleLightDistance = 2f;
19     //电光左右偏移值
20     public float EleLightOffse = .5f;
21     //击中伤害
22     public int Damage = 121;
23     //接收伤害角色类型
24     public ObjectType TargetDamageType = ObjectType.Player;

每次发射激光时创建一个附带LineRenderer组件的物体,在发射前对其中的一些属性赋值:

 1     public void FireBegin()
 2     {
 3         switch (State)
 4         {
 5             //只有在状态关闭时才可以开启激光
 6             case EmissionRayState.Off:
 7                 //实例化激光组件
 8                 LineRayInstance = ObjectPool.Instance.GetObj(LineRayPrefab.gameObject, FirePos).GetComponent();
 9                 EleLightningInstance = ObjectPool.Instance.GetObj(EleLightningPerfab.gameObject, FirePos).GetComponent();
10                 //设置状态
11                 State = EmissionRayState.On;
12                 LifeSate = EmissionLifeSate.Creat;
13                 //初始化属性
14                 RayCurrentPos = FirePos.position;
15                 LineRayInstance.GetComponent().Damage = Damage;
16                 LineRayInstance.positionCount = 2;
17                 RayOriginWidth = LineRayInstance.startWidth;
18                 LineRayInstance.material.SetColor("_Color", EmissionColor);
19                 EleLightningInstance.material.SetColor("_Color", EleLightColor);
20                 break;
21         }
22     }

该方法外部调用后将自动切换到激光的生命周期循环,其中用到的对象池可详见:

https://www.cnblogs.com/koshio0219/p/11572567.html

生成射线阶段:

 1     //生成射线
 2     private void ShootLine()
 3     {
 4         //设置激光起点
 5         LineRayInstance.SetPosition(0, FirePos.position);
 6         var dt = Time.deltaTime;
 7 
 8         //激光的终点按发射速度进行延伸
 9         RayCurrentPos += FirePos.forward * FireSpeed * dt;
10 
11         //在激光运动过程中创建短射线用来检测碰撞
12         Ray ray = new Ray(RayCurrentPos, FirePos.forward);
13         RaycastHit hit;
14         //射线长度稍大于一帧的运动距离,保证不会因为运动过快而丢失
15         if (Physics.Raycast(ray, out hit, 1.2f * dt * FireSpeed))
16         {
17             RayCurrentPos = hit.point;
18             //向命中物体发送被击信号,被击方向为激光发射方向
19             SendActorHit(hit.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
20 
21             //激光接触到目标后自动切换至下一生命周期状态
22             LifeSate = EmissionLifeSate.Keep;
23             //保存当前激光的长度
24             RayLength = (RayCurrentPos - FirePos.position).magnitude;
25 
26             RayCurrentWidth = RayOriginWidth;
27             //创建激光周围电光
28             CreatKeepEleLightning();
29             //开始计算生命周期
30             LifeTimer = 0f;
31         }
32         //设置当前帧终点位置
33         LineRayInstance.SetPosition(1, RayCurrentPos);
34     }
 1     //发送受击信号
 2     private void SendActorHit(GameObject HitObject,Vector2 dir)
 3     {
 4         //判断激光击中目标是否是指定的目标类型
 5         if (HitObject.GetTagType() == TargetDamageType)
 6         {
 7             var actor = HitObject.GetComponent();
 8             if (actor != null)
 9             {
10                 actor.OnHit(LineRayInstance.gameObject);
11                 actor.OnHitReAction(LineRayInstance.gameObject, dir);
12             }
13         }
14     }

这里写了一个GameObject的扩展方法,将物体的标签转为自定义的枚举类型,以防在代码中或编辑器中经常要输入标签的字符串,很是繁琐:

 1     public static ObjectType GetTagType(this GameObject gameObject)
 2     {
 3         switch (gameObject.tag)
 4         {
 5             case "Player":
 6                 return ObjectType.Player;
 7             case "Enemy":
 8                 return ObjectType.Enemy;
 9             case "Bullets":
10                 return ObjectType.Bullet;
11             case "Emission":
12                 return ObjectType.Emission;
13             case "Collider":
14                 return ObjectType.Collider;
15             default:
16                 return ObjectType.Undefined;
17         }
18     }
1 public enum ObjectType
2 {
3     Player,
4     Enemy,
5     Bullet,
6     Emission,
7     Collider,
8     Undefined
9 }

创建激光周围的电光:

 1 private void CreatKeepEleLightning()
 2     {
 3         var EleLightCount = (int)(RayLength / EachEleLightDistance);
 4         EleLightningInstance.positionCount = EleLightCount;
 5         for (int i = 0; i < EleLightCount; i++)
 6         {
 7             //计算偏移值
 8             var offse = RayCurrentWidth *.5f + EleLightOffse;
 9             //计算未偏移时的线段中轴位置
10             var eleo = FirePos.position + (RayCurrentPos - FirePos.position) * (i + 1) / EleLightCount;
11             //在射线的左右间隔分布,按向量运算进行偏移
12             var pos = i % 2 == 0 ? eleo - offse * FirePos.right : eleo + offse * FirePos.right;
13             EleLightningInstance.SetPosition(i, pos);
14         }
15     }

注意本例中不用任何碰撞体来检测碰撞,而是单纯用射线检测。

真实生命周期阶段:

 1     private void ExtendLineWidth()
 2     {
 3         //每帧检测射线碰撞
 4         CheckRayHit();
 5         var dt = Time.deltaTime;
 6         //按速度扩展宽度直到最大宽度
 7         if (RayCurrentWidth < MaxRayWidth)
 8         {
 9             RayCurrentWidth += dt * WidthExtendSpeed;
10             LineRayInstance.startWidth = RayCurrentWidth;
11             LineRayInstance.endWidth = RayCurrentWidth;
12         }
13         //生命周期结束后切换为衰减状态
14         LifeTimer += dt;
15         if (LifeTimer > LifeTime)
16         {
17             LifeSate = EmissionLifeSate.Attenuate;
18         }
19     }

在真实生命周期阶段需要每帧检测激光的射线范围内是否有目标靠近,激光是否因为阻碍物移除而需要延长等:

 1     private void CheckRayHit()
 2     {
 3         var offse = (RayCurrentWidth  + EleLightOffse)*.5f;
 4         //向量运算出左右的起始位置
 5         var startL = FirePos.position - FirePos.right * offse;
 6         var startR = FirePos.position + FirePos.right * offse;
 7         //创建基于当前激光宽度的左右两条检测射线
 8         Ray rayL = new Ray(startL, FirePos.forward);
 9         Ray rayR = new Ray(startR, FirePos.forward);
10         RaycastHit hitL;
11         RaycastHit hitR;
12 
13         bool bHitObject = false;
14         //按当前激光长度检测,若没有碰到任何物体,则延长激光
15         if (Physics.Raycast(rayL, out hitL, RayLength))
16         {
17             //左右击中目标是击中方向为该角色运动前向的反方向
18             var hitDir = (-hitL.transform.forward).GetVector3XZ().normalized;
19             SendActorHit(hitL.transform.gameObject,hitDir);
20             bHitObject = true;
21         }
22 
23         if (Physics.Raycast(rayR, out hitR, RayLength))
24         {
25             var hitDir = (-hitL.transform.forward).GetVector3XZ().normalized;
26             SendActorHit(hitR.transform.gameObject,hitDir);
27             bHitObject = true;
28         }
29         //如果两条射线都未有击中任何目标,则代表激光需要继续延长
30         if (!bHitObject)
31             ExtendLine();
32     }
 1     //延长激光
 2     private void ExtendLine()
 3     {
 4         var dt = Time.deltaTime;
 5         RayCurrentPos += FirePos.forward * FireSpeed * dt;
 6 
 7         Ray ray = new Ray(RayCurrentPos, FirePos.forward);
 8         RaycastHit hit;
 9         if (Physics.Raycast(ray, out hit, 1.2f * dt * FireSpeed))
10         {
11             RayCurrentPos = hit.point;
12             SendActorHit(hit.transform.gameObject,FirePos.forward.GetVector3XZ().normalized);
13             RayLength = (RayCurrentPos - FirePos.position).magnitude;
14             CreatKeepEleLightning();
15         }
16         //更新当前帧终点位置,延长不用再设置起点位置
17         LineRayInstance.SetPosition(1, RayCurrentPos);
18     }

激光衰减阶段:

 1     private void CutDownRayLine()
 2     {
 3         var dt = Time.deltaTime;
 4         //宽度衰减为零后意味着整个激光关闭完成
 5         if (RayCurrentWidth > 0)
 6         {
 7             RayCurrentWidth -= dt * FadeOutSpeed;
 8             LineRayInstance.startWidth = RayCurrentWidth;
 9             LineRayInstance.endWidth = RayCurrentWidth;
10         }
11         else
12             FireShut();
13     }

关闭激光并还原设置:

 1     public void FireShut()
 2     {
 3         switch (State)
 4         {
 5             case EmissionRayState.On:
 6                 EleLightningInstance.positionCount = 0;
 7                 LineRayInstance.positionCount = 0;
 8                 LineRayInstance.startWidth = RayOriginWidth;
 9                 LineRayInstance.endWidth = RayOriginWidth;
10                 //回收实例化个体
11                 ObjectPool.Instance.RecycleObj(LineRayInstance.gameObject);
12                 ObjectPool.Instance.RecycleObj(EleLightningInstance.gameObject);
13                 State = EmissionRayState.Off;
14                 //发送当前物体激光已关闭的事件
15                 EventManager.QueueEvent(new EmissionShutEvent(gameObject));
16                 break;
17         }
18     }

这里用到的事件系统可以详见:

https://www.cnblogs.com/koshio0219/p/11209191.html

完整脚本:

  1 using UnityEngine;
  2 
  3 public enum EmissionRayState
  4 {
  5     Off,
  6     On
  7 }
  8 
  9 public enum EmissionLifeSate
 10 {
 11     None,
 12     //创建阶段
 13     Creat,
 14     //生命周期阶段
 15     Keep,
 16     //衰减阶段
 17     Attenuate
 18 }
 19 
 20 public class EmissionRayCtrl : MonoBehaviour
 21 {
 22     public LineRenderer LineRayPrefab;
 23     public LineRenderer EleLightningPerfab;
 24 
 25     private LineRenderer LineRayInstance;
 26     private LineRenderer EleLightningInstance;
 27 
 28     //发射位置
 29     public Transform FirePos;
 30     //激光颜色
 31     public Color EmissionColor = Color.blue;
 32     //电光颜色
 33     public Color EleLightColor = Color.blue;
 34     //发射速度
 35     public float FireSpeed = 30f;
 36     //生命周期
 37     public float LifeTime = .3f;
 38     //最大到达宽度
 39     public float MaxRayWidth = .1f;
 40     //宽度扩展速度
 41     public float WidthExtendSpeed = .5f;
 42     //渐隐速度
 43     public float FadeOutSpeed = 1f;
 44     //单位电光的距离
 45     public float EachEleLightDistance = 2f;
 46     //电光左右偏移值
 47     public float EleLightOffse = .5f;
 48     //击中伤害
 49     public int Damage = 121;
 50     //接收伤害角色类型
 51     public ObjectType TargetDamageType = ObjectType.Player;
 52 
 53     private EmissionRayState State;
 54     private EmissionLifeSate LifeSate;
 55 
 56     private Vector3 RayCurrentPos;
 57     private float RayOriginWidth;
 58     private float RayCurrentWidth;
 59     private float LifeTimer;
 60     private float RayLength;
 61     void Start()
 62     {
 63         State = EmissionRayState.Off;
 64         LifeSate = EmissionLifeSate.None;
 65     }
 66 
 67     public void FireBegin()
 68     {
 69         switch (State)
 70         {
 71             //只有在状态关闭时才可以开启激光
 72             case EmissionRayState.Off:
 73                 //实例化激光组件
 74                 LineRayInstance = ObjectPool.Instance.GetObj(LineRayPrefab.gameObject, FirePos).GetComponent();
 75                 EleLightningInstance = ObjectPool.Instance.GetObj(EleLightningPerfab.gameObject, FirePos).GetComponent();
 76                 //设置状态
 77                 State = EmissionRayState.On;
 78                 LifeSate = EmissionLifeSate.Creat;
 79                 //初始化属性
 80                 RayCurrentPos = FirePos.position;
 81                 LineRayInstance.GetComponent().Damage = Damage;
 82                 LineRayInstance.positionCount = 2;
 83                 RayOriginWidth = LineRayInstance.startWidth;
 84                 LineRayInstance.material.SetColor("_Color", EmissionColor);
 85                 EleLightningInstance.material.SetColor("_Color", EleLightColor);
 86                 break;
 87         }
 88     }
 89 
 90     void Update()
 91     {
 92         switch (State)
 93         {
 94             case EmissionRayState.On:
 95                 switch (LifeSate)
 96                 {
 97                     case EmissionLifeSate.Creat:
 98                         ShootLine();
 99                         break;
100                     case EmissionLifeSate.Keep:
101                         ExtendLineWidth();
102                         break;
103                     case EmissionLifeSate.Attenuate:
104                         CutDownRayLine();
105                         break;
106                 }
107                 break;
108         }
109     }
110 
111     //生成射线
112     private void ShootLine()
113     {
114         //设置激光起点
115         LineRayInstance.SetPosition(0, FirePos.position);
116         var dt = Time.deltaTime;
117 
118         //激光的终点按发射速度进行延伸
119         RayCurrentPos += FirePos.forward * FireSpeed * dt;
120 
121         //在激光运动过程中创建短射线用来检测碰撞
122         Ray ray = new Ray(RayCurrentPos, FirePos.forward);
123         RaycastHit hit;
124         //射线长度稍大于一帧的运动距离,保证不会因为运动过快而丢失
125         if (Physics.Raycast(ray, out hit, 1.2f * dt * FireSpeed))
126         {
127             RayCurrentPos = hit.point;
128             //向命中物体发送被击信号,被击方向为激光发射方向
129             SendActorHit(hit.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
130 
131             //激光接触到目标后自动切换至下一生命周期状态
132             LifeSate = EmissionLifeSate.Keep;
133             //保存当前激光的长度
134             RayLength = (RayCurrentPos - FirePos.position).magnitude;
135 
136             RayCurrentWidth = RayOriginWidth;
137             //创建激光周围电光
138             CreatKeepEleLightning();
139             //开始计算生命周期
140             LifeTimer = 0f;
141         }
142         //设置当前帧终点位置
143         LineRayInstance.SetPosition(1, RayCurrentPos);
144     }
145 
146     //发送受击信号
147     private void SendActorHit(GameObject HitObject, Vector2 dir)
148     {
149         //判断激光击中目标是否是指定的目标类型
150         if (HitObject.GetTagType() == TargetDamageType)
151         {
152             var actor = HitObject.GetComponent();
153             if (actor != null)
154             {
155                 actor.OnHit(LineRayInstance.gameObject);
156                 actor.OnHitReAction(LineRayInstance.gameObject, dir);
157             }
158         }
159     }
160 
161     private void CheckRayHit()
162     {
163         var offse = (RayCurrentWidth + EleLightOffse) * .5f;
164         //向量运算出左右的起始位置
165         var startL = FirePos.position - FirePos.right * offse;
166         var startR = FirePos.position + FirePos.right * offse;
167         //创建基于当前激光宽度的左右两条检测射线
168         Ray rayL = new Ray(startL, FirePos.forward);
169         Ray rayR = new Ray(startR, FirePos.forward);
170         RaycastHit hitL;
171         RaycastHit hitR;
172 
173         bool bHitObject = false;
174         //按当前激光长度检测,若没有碰到任何物体,则延长激光
175         if (Physics.Raycast(rayL, out hitL, RayLength))
176         {
177             //左右击中目标是击中方向为该角色运动前向的反方向
178             var hitDir = (-hitL.transform.forward).GetVector3XZ().normalized;
179             SendActorHit(hitL.transform.gameObject, hitDir);
180             bHitObject = true;
181         }
182 
183         if (Physics.Raycast(rayR, out hitR, RayLength))
184         {
185             var hitDir = (-hitL.transform.forward).GetVector3XZ().normalized;
186             SendActorHit(hitR.transform.gameObject, hitDir);
187             bHitObject = true;
188         }
189         //如果两条射线都未有击中任何目标,则代表激光需要继续延长
190         if (!bHitObject)
191             ExtendLine();
192     }
193 
194     //延长激光
195     private void ExtendLine()
196     {
197         var dt = Time.deltaTime;
198         RayCurrentPos += FirePos.forward * FireSpeed * dt;
199 
200         Ray ray = new Ray(RayCurrentPos, FirePos.forward);
201         RaycastHit hit;
202         if (Physics.Raycast(ray, out hit, 1.2f * dt * FireSpeed))
203         {
204             RayCurrentPos = hit.point;
205             SendActorHit(hit.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
206             RayLength = (RayCurrentPos - FirePos.position).magnitude;
207             CreatKeepEleLightning();
208         }
209         //更新当前帧终点位置,延长不用再设置起点位置
210         LineRayInstance.SetPosition(1, RayCurrentPos);
211     }
212 
213     private void ExtendLineWidth()
214     {
215         //每帧检测射线碰撞
216         CheckRayHit();
217         var dt = Time.deltaTime;
218         //按速度扩展宽度直到最大宽度
219         if (RayCurrentWidth < MaxRayWidth)
220         {
221             RayCurrentWidth += dt * WidthExtendSpeed;
222             LineRayInstance.startWidth = RayCurrentWidth;
223             LineRayInstance.endWidth = RayCurrentWidth;
224         }
225         //生命周期结束后切换为衰减状态
226         LifeTimer += dt;
227         if (LifeTimer > LifeTime)
228         {
229             LifeSate = EmissionLifeSate.Attenuate;
230         }
231     }
232 
233     //生成电光
234     private void CreatKeepEleLightning()
235     {
236         var EleLightCount = (int)(RayLength / EachEleLightDistance);
237         EleLightningInstance.positionCount = EleLightCount;
238         for (int i = 0; i < EleLightCount; i++)
239         {
240             //计算偏移值
241             var offse = RayCurrentWidth * .5f + EleLightOffse;
242             //计算未偏移时每个电光的线段中轴位置
243             var eleo = FirePos.position + (RayCurrentPos - FirePos.position) * (i + 1) / EleLightCount;
244             //在射线的左右间隔分布,按向量运算进行偏移
245             var pos = i % 2 == 0 ? eleo - offse * FirePos.right : eleo + offse * FirePos.right;
246             EleLightningInstance.SetPosition(i, pos);
247         }
248     }
249 
250     private void CutDownRayLine()
251     {
252         var dt = Time.deltaTime;
253         //宽度衰减为零后意味着整个激光关闭完成
254         if (RayCurrentWidth > 0)
255         {
256             RayCurrentWidth -= dt * FadeOutSpeed;
257             LineRayInstance.startWidth = RayCurrentWidth;
258             LineRayInstance.endWidth = RayCurrentWidth;
259         }
260         else
261             FireShut();
262     }
263 
264     public void FireShut()
265     {
266         switch (State)
267         {
268             case EmissionRayState.On:
269                 EleLightningInstance.positionCount = 0;
270                 LineRayInstance.positionCount = 0;
271                 LineRayInstance.startWidth = RayOriginWidth;
272                 LineRayInstance.endWidth = RayOriginWidth;
273                 //回收实例化个体
274                 ObjectPool.Instance.RecycleObj(LineRayInstance.gameObject);
275                 ObjectPool.Instance.RecycleObj(EleLightningInstance.gameObject);
276                 State = EmissionRayState.Off;
277                 //发送射线已关闭的事件
278                 EventManager.QueueEvent(new EmissionShutEvent(gameObject));
279                 break;
280         }
281     }
282 }
View Code

你可能感兴趣的:(Unity LineRenderer 射线检测 激光攻击)