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<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, 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<Actor>();
 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         }
21 
22         if (Physics.Raycast(rayR, out hitR, RayLength))
23         {
24             var hitDir = (-hitR.transform.forward).GetVector3XZ().normalized;
25             SendActorHit(hitR.transform.gameObject, hitDir);
26         }
27         ChangeLine();
28     }
 1     private void ChangeLine()
 2     {
 3         RaycastHit info;
 4         if (Physics.Raycast(new Ray(FirePos.position, FirePos.forward), out info))
 5         {
 6             RayCurrentPos = info.point;
 7             SendActorHit(info.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
 8             RayLength = (RayCurrentPos - FirePos.position).magnitude;
 9             LineRayInstance.SetPosition(1, RayCurrentPos);
10             CreatKeepEleLightning();
11         }
12     }

激光衰减阶段:

 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 EmissionLifeSate
  4 {
  5     None,
  6     //创建阶段
  7     Creat,
  8     //生命周期阶段
  9     Keep,
 10     //衰减阶段
 11     Attenuate
 12 }
 13 
 14 public class EmissionRayCtrl : FireBase
 15 {
 16     public LineRenderer LineRayPrefab;
 17     public LineRenderer EleLightningPerfab;
 18 
 19     private LineRenderer LineRayInstance;
 20     private LineRenderer EleLightningInstance;
 21 
 22     public GameObject FirePrefab;
 23     public GameObject HitPrefab;
 24 
 25     private GameObject FireInstance;
 26     private GameObject HitInstance;
 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 float DamageCD = .1f;
 52     //冷却时间
 53     public float CD = 0f;
 54     //接收伤害角色类型
 55     public ObjectType TargetDamageType = ObjectType.Player;
 56 
 57     public bool bHaveEleLight = false;
 58 
 59     private FireState State;
 60     private EmissionLifeSate LifeSate;
 61 
 62     private Vector3 RayCurrentPos;
 63     private float RayOriginWidth;
 64     private float RayCurrentWidth;
 65     private float LifeTimer;
 66     private float CDTimer;
 67     private float DamageCDTimer;
 68     private float RayLength;
 69 
 70     void Start()
 71     {
 72         State = FireState.Off;
 73         LifeSate = EmissionLifeSate.None;
 74         CDTimer = 0f;
 75         DamageCDTimer = 0f;
 76     }
 77 
 78     public override void FireBegin()
 79     {
 80         switch (State)
 81         {
 82             //只有在状态关闭时才可以开启激光
 83             case FireState.Off:
 84                 if (CDTimer <= 0)
 85                 {
 86                     //实例化激光组件
 87                     LineRayInstance = ObjectPool.Instance.GetObj(LineRayPrefab.gameObject, FirePos).GetComponent<LineRenderer>();
 88                     EleLightningInstance = ObjectPool.Instance.GetObj(EleLightningPerfab.gameObject, FirePos).GetComponent<LineRenderer>();
 89                     FireInstance = ObjectPool.Instance.GetObj(FirePrefab, FirePos);
 90                     HitInstance = ObjectPool.Instance.GetObj(HitPrefab, FirePos);
 91                     //设置状态
 92                     State = FireState.On;
 93                     LifeSate = EmissionLifeSate.Creat;
 94                     HitInstance.SetActive(false);
 95                     //初始化属性
 96                     RayCurrentPos = FirePos.position;
 97                     LineRayInstance.GetComponent<EmissionRay>().Damage = Damage;
 98                     LineRayInstance.positionCount = 2;
 99                     RayOriginWidth = LineRayInstance.startWidth;
100                     LineRayInstance.material.SetColor("_Color", EmissionColor);
101                     EleLightningInstance.material.SetColor("_Color", EleLightColor);
102                     CDTimer = CD;
103                 }
104                 break;
105         }
106     }
107 
108     void FixedUpdate()
109     {
110         switch (State)
111         {
112             case FireState.On:
113                 switch (LifeSate)
114                 {
115                     case EmissionLifeSate.Creat:
116                         ShootLine();
117                         break;
118                     case EmissionLifeSate.Keep:
119                         ExtendLineWidth();
120                         break;
121                     case EmissionLifeSate.Attenuate:
122                         CutDownRayLine();
123                         break;
124                 }
125                 break;
126             case FireState.Off:
127                 CDTimer -= Time.fixedDeltaTime;
128                 break;
129         }
130     }
131 
132     //生成射线
133     private void ShootLine()
134     {
135         //设置激光起点
136         LineRayInstance.SetPosition(0, FirePos.position);
137         var dt = Time.fixedDeltaTime;
138 
139         //激光的终点按发射速度进行延伸
140         RayCurrentPos += FirePos.forward * FireSpeed * dt;
141 
142         //在激光运动过程中创建短射线用来检测碰撞
143         Ray ray = new Ray(RayCurrentPos, FirePos.forward);
144         RaycastHit hit;
145         //射线长度稍大于一帧的运动距离,保证不会因为运动过快而丢失
146         if (Physics.Raycast(ray, out hit, 1.2f * dt * FireSpeed))
147         {
148             RayCurrentPos = hit.point;
149             //向命中物体发送被击信号,被击方向为激光发射方向
150             SendActorHit(hit.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
151 
152             //激光接触到目标后自动切换至下一生命周期状态
153             LifeSate = EmissionLifeSate.Keep;
154             //保存当前激光的长度
155             RayLength = (RayCurrentPos - FirePos.position).magnitude;
156 
157             RayCurrentWidth = RayOriginWidth;
158             HitInstance.SetActive(true);
159             //开始计算生命周期
160             LifeTimer = 0f;
161         }
162         //设置当前帧终点位置
163         LineRayInstance.SetPosition(1, RayCurrentPos);
164     }
165 
166     //发送受击信号
167     private void SendActorHit(GameObject HitObject, Vector2 dir)
168     {
169         //判断激光击中目标是否是指定的目标类型
170         if (HitObject.GetTagType() == TargetDamageType)
171         {
172             var actor = HitObject.GetComponent<Actor>();
173             if (actor != null)
174             {
175                 if (DamageCDTimer <= 0)
176                 {
177                     actor.OnHit(LineRayInstance.gameObject);
178                     actor.OnHitReAction(LineRayInstance.gameObject, dir);
179                     DamageCDTimer = DamageCD;
180                 }
181                 DamageCDTimer -= Time.deltaTime;
182             }
183         }
184     }
185 
186     private void CheckRayHit()
187     {
188         var offse = (RayCurrentWidth + EleLightOffse) * .5f;
189         //向量运算出左右的起始位置
190         var startL = FirePos.position - FirePos.right * offse;
191         var startR = FirePos.position + FirePos.right * offse;
192         //创建基于当前激光宽度的左右两条检测射线
193         Ray rayL = new Ray(startL, FirePos.forward);
194         Ray rayR = new Ray(startR, FirePos.forward);
195         RaycastHit hitL;
196         RaycastHit hitR;
197 
198         //bool bHitObject = false;
199         //按当前激光长度检测,若没有碰到任何物体,则延长激光
200         if (Physics.Raycast(rayL, out hitL, RayLength))
201         {
202             //左右击中目标是击中方向为该角色运动前向的反方向
203             var hitDir = (-hitL.transform.forward).GetVector3XZ().normalized;
204             SendActorHit(hitL.transform.gameObject, hitDir);
205         }
206 
207         if (Physics.Raycast(rayR, out hitR, RayLength))
208         {
209             var hitDir = (-hitR.transform.forward).GetVector3XZ().normalized;
210             SendActorHit(hitR.transform.gameObject, hitDir);
211         }
212         ChangeLine();
213     }
214 
215     private void ChangeLine()
216     {
217         RaycastHit info;
218         if (Physics.Raycast(new Ray(FirePos.position, FirePos.forward), out info))
219         {
220             RayCurrentPos = info.point;
221             SendActorHit(info.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
222             RayLength = (RayCurrentPos - FirePos.position).magnitude;
223             LineRayInstance.SetPosition(1, RayCurrentPos);
224             CreatKeepEleLightning();
225         }
226     }
227 
228     //延长激光
229     private void ExtendLine()
230     {
231         var dt = Time.fixedDeltaTime;
232         RayCurrentPos += FirePos.forward * FireSpeed * dt;
233 
234         Ray ray = new Ray(RayCurrentPos, FirePos.forward);
235         RaycastHit hit;
236         if (Physics.Raycast(ray, out hit, 1.2f * dt * FireSpeed))
237         {
238             RayCurrentPos = hit.point;
239             SendActorHit(hit.transform.gameObject, FirePos.forward.GetVector3XZ().normalized);
240             RayLength = (RayCurrentPos - FirePos.position).magnitude;
241             CreatKeepEleLightning();
242         }
243         //更新当前帧终点位置,延长不用再设置起点位置
244         LineRayInstance.SetPosition(1, RayCurrentPos);
245     }
246 
247     private void ExtendLineWidth()
248     {
249         var dt = Time.fixedDeltaTime;
250         //按速度扩展宽度直到最大宽度
251         if (RayCurrentWidth < MaxRayWidth)
252         {
253             RayCurrentWidth += dt * WidthExtendSpeed;
254             LineRayInstance.startWidth = RayCurrentWidth;
255             LineRayInstance.endWidth = RayCurrentWidth;
256         }
257         //每帧检测射线碰撞
258         CheckRayHit();
259         //生命周期结束后切换为衰减状态
260         LifeTimer += dt;
261         if (LifeTimer > LifeTime)
262         {
263             LifeSate = EmissionLifeSate.Attenuate;
264         }
265         ReBuildLine();
266     }
267 
268     //刷新激光位置,用于动态旋转的发射源
269     private void ReBuildLine()
270     {
271         LineRayInstance.SetPosition(0, FirePos.position);
272         LineRayInstance.SetPosition(1, FirePos.position + FirePos.forward * RayLength);
273         HitInstance.transform.position = FirePos.position + FirePos.forward * RayLength;
274         CreatKeepEleLightning();
275     }
276 
277     //生成电光
278     private void CreatKeepEleLightning()
279     {
280         if (bHaveEleLight)
281         {
282             var EleLightCount = (int)(RayLength / EachEleLightDistance);
283             EleLightningInstance.positionCount = EleLightCount;
284             for (int i = 0; i < EleLightCount; i++)
285             {
286                 //计算偏移值
287                 var offse = RayCurrentWidth * .5f + EleLightOffse;
288                 //计算未偏移时每个电光的线段中轴位置
289                 var eleo = FirePos.position + (RayCurrentPos - FirePos.position) * (i + 1) / EleLightCount;
290                 //在射线的左右间隔分布,按向量运算进行偏移
291                 var pos = i % 2 == 0 ? eleo - offse * FirePos.right : eleo + offse * FirePos.right;
292                 EleLightningInstance.SetPosition(i, pos);
293             }
294         }
295     }
296 
297     private void CutDownRayLine()
298     {
299         ReBuildLine();
300         var dt = Time.fixedDeltaTime;
301         //宽度衰减为零后意味着整个激光关闭完成
302         if (RayCurrentWidth > 0)
303         {
304             RayCurrentWidth -= dt * FadeOutSpeed;
305             LineRayInstance.startWidth = RayCurrentWidth;
306             LineRayInstance.endWidth = RayCurrentWidth;
307         }
308         else
309             FireShut();
310     }
311 
312     public override void FireShut()
313     {
314         switch (State)
315         {
316             case FireState.On:
317                 EleLightningInstance.positionCount = 0;
318                 LineRayInstance.positionCount = 0;
319                 LineRayInstance.startWidth = RayOriginWidth;
320                 LineRayInstance.endWidth = RayOriginWidth;
321                 //回收实例化个体
322                 ObjectPool.Instance.RecycleObj(LineRayInstance.gameObject);
323                 ObjectPool.Instance.RecycleObj(EleLightningInstance.gameObject);
324                 ObjectPool.Instance.RecycleObj(FireInstance);
325                 ObjectPool.Instance.RecycleObj(HitInstance);
326                 State = FireState.Off;
327                 //发送射线已关闭的事件
328                 EventManager.QueueEvent(new EmissionShutEvent(gameObject));
329                 break;
330         }
331     }
332 
333     public override void SetDamage(int damage)
334     {
335         Damage = damage;
336     }
337 
338     public override void SetFirePos(Transform pos)
339     {
340         FirePos = pos;
341     }
342 
343     public override void SetCD(float cd)
344     {
345         CD = cd;
346     }
347 
348     public override string GetAniName()
349     {
350         return "ANI_Aim_01";
351     }
352 
353     public override FireState GetFireState()
354     {
355         return State;
356     }
357 }
View Code
原文地址:https://www.cnblogs.com/koshio0219/p/12102577.html