在进行激光攻击的脚本编写前,我们需要先进行一定程度的想象,激光和普通的远程攻击有哪些不太一样的地方。
正常的远程攻击例如子弹,箭矢,技能波等,都有明确的弹道,不可能同时命中多个敌人,而且一般只要命中敌人后就会被销毁。(特殊技能除外)
但激光可以认为是一种持续性的范围伤害,只是它的范围(长度)是不固定的,在激光的发射阶段,它会在第一个被命中的目标或障碍物处截断。
一旦整个激光成型后,一般来说,它将不再被运动的目标截断,反而是依靠它已经生成的光柱将目标弹开并造成伤害。
当然,如果之前被命中的目标或障碍物从激光的光柱范围内移开,这时激光会自动延长至下一被命中的目标或障碍物位置。
也就是说,激光一旦发出,在它的生命周期内,只延长不缩短,直至最后消失。
激光发射的过程如下:
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 int EleLightCount = 8; 19 //电光偏移值 20 public float EleLightOffse = .5f; 21 //击中伤害 22 public int Damage = 121;
每次发射时创建一个附带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<LineRenderer>(); 9 EleLightningInstance = ObjectPool.Instance.GetObj(EleLightningPerfab.gameObject, FirePos).GetComponent<LineRenderer>(); 10 //设置状态 11 State = EmissionRayState.On; 12 LifeSate = EmissionLifeSate.Creat; 13 //初始化属性 14 RayCurrentPos = FirePos.position; 15 LineRayInstance.GetComponent<EmissionRay>().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, dt * FireSpeed)) 16 { 17 RayCurrentPos = hit.point; 18 //向命中物体发送被击信号 19 SendActorHit(hit.transform.gameObject); 20 21 //切换状态 22 LifeSate = EmissionLifeSate.Keep; 23 RayCurrentWidth = RayOriginWidth; 24 RayLength = (RayCurrentPos - FirePos.position).magnitude; 25 //创建射线周围电光 26 CreatKeepEleLightning(); 27 //开始计算生命周期 28 LifeTimer = 0f; 29 } 30 //设置当前帧终点位置 31 LineRayInstance.SetPosition(1, RayCurrentPos); 32 }
1 //发送受击信号 2 private void SendActorHit(GameObject HitObject) 3 { 4 var actor = HitObject.GetComponent<Actor>(); 5 if (actor != null) 6 { 7 actor.OnHit(LineRayInstance.gameObject); 8 actor.OnHitReAction(LineRayInstance.gameObject, FirePos.forward.GetVector3XZ().normalized); 9 } 10 }
1 //生成电光 2 private void CreatKeepEleLightning() 3 { 4 EleLightningInstance.positionCount = EleLightCount; 5 for (int i = 0; i < EleLightCount; i++) 6 { 7 //计算偏移值 8 var offse = RayCurrentWidth / 2 + EleLightOffse; 9 var eleo = FirePos.position + (RayCurrentPos - FirePos.position) * (i + 1) / EleLightCount; 10 //在射线的左右间隔分布 11 var pos = i % 2 == 0 ? new Vector3(eleo.x + offse, eleo.y, eleo.z) : new Vector3(eleo.x - offse, eleo.y, eleo.z); 12 EleLightningInstance.SetPosition(i, pos); 13 } 14 }
注意这里本例中不用任何碰撞体来检测碰撞,而是单纯用射线检测,这里只用了一条,更好的做法是在激光的两条边缘发射两条射线检测。
生命周期阶段:
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 Ray ray = new Ray(FirePos.position, FirePos.forward); 4 RaycastHit hit; 5 //按当前激光长度检测 6 if (Physics.Raycast(ray, out hit, RayLength)) 7 { 8 SendActorHit(hit.transform.gameObject); 9 } 10 }
衰减阶段:
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()); 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 int EleLightCount = 8; 46 //电光偏移值 47 public float EleLightOffse = .5f; 48 //击中伤害 49 public int Damage = 121; 50 51 private EmissionRayState State; 52 private EmissionLifeSate LifeSate; 53 54 private Vector3 RayCurrentPos; 55 private float RayOriginWidth; 56 private float RayCurrentWidth; 57 private float LifeTimer; 58 private float RayLength; 59 void Start() 60 { 61 State = EmissionRayState.Off; 62 LifeSate = EmissionLifeSate.None; 63 } 64 65 public void FireBegin() 66 { 67 switch (State) 68 { 69 //只有在状态关闭时才可以开启激光 70 case EmissionRayState.Off: 71 //实例化激光组件 72 LineRayInstance = ObjectPool.Instance.GetObj(LineRayPrefab.gameObject, FirePos).GetComponent<LineRenderer>(); 73 EleLightningInstance = ObjectPool.Instance.GetObj(EleLightningPerfab.gameObject, FirePos).GetComponent<LineRenderer>(); 74 //设置状态 75 State = EmissionRayState.On; 76 LifeSate = EmissionLifeSate.Creat; 77 //初始化属性 78 RayCurrentPos = FirePos.position; 79 LineRayInstance.GetComponent<EmissionRay>().Damage = Damage; 80 LineRayInstance.positionCount = 2; 81 RayOriginWidth = LineRayInstance.startWidth; 82 LineRayInstance.material.SetColor("_Color", EmissionColor); 83 EleLightningInstance.material.SetColor("_Color", EleLightColor); 84 break; 85 } 86 } 87 88 void Update() 89 { 90 switch (State) 91 { 92 case EmissionRayState.On: 93 switch (LifeSate) 94 { 95 case EmissionLifeSate.Creat: 96 ShootLine(); 97 break; 98 case EmissionLifeSate.Keep: 99 ExtendLineWidth(); 100 break; 101 case EmissionLifeSate.Attenuate: 102 CutDownRayLine(); 103 break; 104 } 105 break; 106 } 107 } 108 109 //生成射线 110 private void ShootLine() 111 { 112 //设置激光起点 113 LineRayInstance.SetPosition(0, FirePos.position); 114 var dt = Time.deltaTime; 115 116 //射线的终点按发射速度进行延伸 117 RayCurrentPos += FirePos.forward * FireSpeed * dt; 118 119 //在射线运动过程中创建一个单位长度的射线用来检测碰撞 120 Ray ray = new Ray(RayCurrentPos, FirePos.forward); 121 RaycastHit hit; 122 //射线长度为一帧的运动距离,保证不会因为运动过快而丢失 123 if (Physics.Raycast(ray, out hit, dt * FireSpeed)) 124 { 125 RayCurrentPos = hit.point; 126 //向命中物体发送被击信号 127 SendActorHit(hit.transform.gameObject); 128 129 //切换状态 130 LifeSate = EmissionLifeSate.Keep; 131 RayCurrentWidth = RayOriginWidth; 132 RayLength = (RayCurrentPos - FirePos.position).magnitude; 133 //创建射线周围电光 134 CreatKeepEleLightning(); 135 //开始计算生命周期 136 LifeTimer = 0f; 137 } 138 //设置当前帧终点位置 139 LineRayInstance.SetPosition(1, RayCurrentPos); 140 } 141 142 //发送受击信号 143 private void SendActorHit(GameObject HitObject) 144 { 145 var actor = HitObject.GetComponent<Actor>(); 146 if (actor != null) 147 { 148 actor.OnHit(LineRayInstance.gameObject); 149 actor.OnHitReAction(LineRayInstance.gameObject, FirePos.forward.GetVector3XZ().normalized); 150 } 151 } 152 153 private void CheckRayHit() 154 { 155 Ray ray = new Ray(FirePos.position, FirePos.forward); 156 RaycastHit hit; 157 //按当前激光长度检测 158 if (Physics.Raycast(ray, out hit, RayLength)) 159 { 160 SendActorHit(hit.transform.gameObject); 161 } 162 } 163 164 private void ExtendLineWidth() 165 { 166 //每帧检测射线碰撞 167 CheckRayHit(); 168 var dt = Time.deltaTime; 169 //按速度扩展宽度直到最大宽度 170 if (RayCurrentWidth < MaxRayWidth) 171 { 172 RayCurrentWidth += dt * WidthExtendSpeed; 173 LineRayInstance.startWidth = RayCurrentWidth; 174 LineRayInstance.endWidth = RayCurrentWidth; 175 } 176 //生命周期结束后切换为衰减状态 177 LifeTimer += dt; 178 if (LifeTimer > LifeTime) 179 { 180 LifeSate = EmissionLifeSate.Attenuate; 181 } 182 } 183 184 //生成电光 185 private void CreatKeepEleLightning() 186 { 187 EleLightningInstance.positionCount = EleLightCount; 188 for (int i = 0; i < EleLightCount; i++) 189 { 190 //计算偏移值 191 var offse = RayCurrentWidth / 2 + EleLightOffse; 192 var eleo = FirePos.position + (RayCurrentPos - FirePos.position) * (i + 1) / EleLightCount; 193 //在射线的左右间隔分布 194 var pos = i % 2 == 0 ? new Vector3(eleo.x + offse, eleo.y, eleo.z) : new Vector3(eleo.x - offse, eleo.y, eleo.z); 195 EleLightningInstance.SetPosition(i, pos); 196 } 197 } 198 199 private void CutDownRayLine() 200 { 201 var dt = Time.deltaTime; 202 //宽度衰减为零后意味着整个激光关闭完成 203 if (RayCurrentWidth > 0) 204 { 205 RayCurrentWidth -= dt * FadeOutSpeed; 206 LineRayInstance.startWidth = RayCurrentWidth; 207 LineRayInstance.endWidth = RayCurrentWidth; 208 } 209 else 210 FireShut(); 211 } 212 213 public void FireShut() 214 { 215 switch (State) 216 { 217 case EmissionRayState.On: 218 EleLightningInstance.positionCount = 0; 219 LineRayInstance.positionCount = 0; 220 LineRayInstance.startWidth = RayOriginWidth; 221 LineRayInstance.endWidth = RayOriginWidth; 222 //回收实例化个体 223 ObjectPool.Instance.RecycleObj(LineRayInstance.gameObject); 224 ObjectPool.Instance.RecycleObj(EleLightningInstance.gameObject); 225 State = EmissionRayState.Off; 226 //发送射线已关闭的事件 227 EventManager.QueueEvent(new EmissionShutEvent()); 228 break; 229 } 230 } 231 }
原文地址:https://www.cnblogs.com/koshio0219/p/12102577.html