游戏开发论坛

 找回密码
 立即注册
搜索
查看: 4684|回复: 0

糍粑大叔的独游之旅-战斗!之弹道实现(中)

[复制链接]

1万

主题

1万

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
32152
发表于 2016-8-24 13:17:28 | 显示全部楼层 |阅读模式
  文/糍粑大叔

  上文说明在弹道实现中,关于u3d的物理引擎的一些相关要点,并跟给了实体性子弹的关键实现代码。

  本篇中,将继续说明射线型弹道的实现的。

  四、射线型子弹

  本章先讲子弹的碰撞逻辑实现,由于射线型子弹用u3d的sprite是绘制不出来的,所有需要特殊的技巧,绘制方法在下一章中说明。

  使用Physic2D库,进行非自动碰撞检测

  区别于实体子弹,在游戏中,我需要实现类似于激光射线、范围伤害(AOE)的攻击类型。

  这种情况下,就不能依靠rigidbody来实现碰撞的检测,前文中又说了,u3d的物理引擎不支持两个collider的碰撞检测。

  所以,没有办法“自动”做碰撞检测(这里所谓自动,就是去实现一个函数,然后等着u3d在碰撞发生时自动调用)

  我依然使用u3d的2D物理库,在每一帧(每个Update)中,对目标collider进行检测。

  例如,AOE伤害:
  1. RaycastHit2D[] hits = Physics2D.CircleCastAll(transform.position, m_Info.aoeRadius, Vector2.zero,
  2.      LayerManager.GetLayer(m_Faction).oppUnitMask );
复制代码

  使用Physics2D的CircleCastAll、RaycastAll、BoxCastAll,对指定layer中的所有collider,做圆形、射线、长方形的碰撞检测。

  即,这里不再用collider和collider,而是判断指定的圆形、射线、长方形,和哪些collider碰撞(Cast),包括相交和包含。

  这些函数有一个All和非All的版本,返回所有检测到collider或者最近的collider。

  返回值RaycastHit包含collider、point(碰撞点)、normal(碰撞法线)等,具体请参考u3d api,这里不再累述。

  和上文说的Collider的碰撞检测一样,碰撞的代码实现放在Update里,可能出现“嵌入过多”或“穿过”的情况。

  但由于,需求本身是针对非实体子弹的,其面积、范围比子弹大很多,所有没有太严重的影响。

  (如果放在FixedUpdate会精确很多,但消耗太大)。

  几种弹道类型的攻击逻辑实现

  穿透激光

55.png

  穿透激光,可以对射线上的单位造成伤害。

  其攻击的实现代码:
  1. IEnumerator _TakeAttack ()
  2. {
  3.         yield return new WaitForSeconds (RAY_TAKE_ATTACK);

  4.         Vector3 v = Utils.Up (transform);
  5.         Vector3 worldCenter = transform.position + v * m_Info.attackDistance / 2;
  6.         Vector2 size = new Vector2 (width, m_Info.attackDistance);

  7.         RaycastHit2D[] hits =
  8.             Physics2D.BoxCastAll (worldCenter, size,
  9.                 Utils.DirToAngle(v), new Vector2 (0, 0),
  10.                 Mathf.Infinity,LayerManager.GetLayer(m_Faction).oppUnitMask);  

  11.       m_Filter.Clear();
  12.                       for (int s = 0; s < hits.Length; ++s)
  13.                       {
  14.                                 TargetPick pick = TargetPick.From (ref hits [s]);
  15.                                 pick.ToShield();
  16.                                 if( pick )
  17.                                           if( m_Filter.Test(ref pick) )
  18.                                         m_Info.AttackOn (pick, v, m_myUnit,hitEffectType,null);
  19.                       }
  20.         }
复制代码

  注意,攻击使用StartCoroutine做一个延时,这是因为,为了动画效果更真实,在做激光的绘制时,有一个很快的激光射线变长的过程,所以攻击的实际效果要和增长时间配合。

  代码里面有很多游戏逻辑相关的东西,不用太关注,关键是要获取到一个box的形状描述,需要知道Up,Center,Size。

  这里把激光看成一个很长的box,长度是激光的最大攻击距离。

  最后,由于有些单位有多个Collider,所有需要过滤一下(即m_Filter),原理很简单:

  1、找hit或collider对应的单位(TargetPick.From)

  2、检查单位在过滤器中是否已经存在。存在就不在处理,不存在就继续,并添加到过滤器中(m_Filter.test)

  3、进行伤害的计算逻辑(m_Info.Attack)

  定向激光(持续)

56.png

  定向激光对指定的单位进行攻击。

  定向激光不需要通过碰撞检测去“探测”激光与哪些collider相交的,因为定向激光是对已指定的单位进行持续攻击。

  需要解决的问题,激光与单位的碰撞点到底在哪。根据这个碰撞点,绘制激光的形状。

  其攻击的实现代码:
  1. public bool _TakeAttack (out Vector3 point)
  2.     {
  3.         point = Vector3.zero;
  4.         Vector3 v = Utils.Up (transform);
  5.         // The colliders in the array are sorted in order of distance from the origin point
  6.         RaycastHit2D[] hits = Physics2D.RaycastAll (transform.position, v, m_Info.attackDistance,
  7.             LayerManager.GetLayer(m_Faction).oppUnitMask);

  8.         // 是否有target的hit
  9.         TargetPick pick = TargetPick.none;
  10.         for (int s = 0; s < hits.Length; ++s)
  11.         {
  12.             pick = TargetPick.IsTarget (target, ref hits [s]);
  13.             if (pick)
  14.                 break;
  15.         }

  16.         if (pick)
  17.         {
  18.             point = pick.point;
  19.             m_Info.AttackOn (pick, v, m_myUnit, Const.NONE_EFFECT, this);
  20.             if (pick.unit && pick.unit.curHp <= 0)
  21.                 return false;

  22.             if (pick.part && pick.part.unit.curHp <= 0)
  23.                 return false;

  24.             return true;
  25.         }
  26.         return false;
  27.     }
复制代码

  攻击函数返回是否攻击到对象单位(target),并返回攻击点。和穿透激光的区别,使用RayCastAll目的只是找到要攻击的对象是否在其中,并确定碰撞点。

  定向激光(单次攻击)

57.png

  类似于图中的闪电效果。原理持续的定向激光基本一致,区别是单次攻击时播放闪电(或其他效果)动画。

  同持续定向激光一样,在攻击过程中不停的用RayCast判断闪电是否“打”到了目标上,

  如果没有需要立即中断攻击和攻击动画,否则攻击单位在出现突然转身的是否,闪电会随着攻击攻击单位移动,出现bug。

  不再给出代码。

  范围攻击

  典型的是喷火器或者爆炸,在一定范围内所有单位收到伤害。
  1. bool _TakeAttack ()
  2.     {
  3.         Vector3 dir = Utils.Up (transform);

  4.         m_Filter.Clear ();
  5.         RaycastHit2D[] hits = Physics2D.CircleCastAll (transform.position, m_Info.attackDistance, Vector2.zero,
  6.                                   Mathf.Infinity, LayerManager.GetLayer(m_Faction).oppUnitMask);
  7.         
  8.         bool f = false;
  9.         // 是否有target的hit
  10.         for (int s = 0; s < hits.Length; ++s)
  11.         {
  12.             Vector3 v = Utils.V2toV3 (hits [s].point) - transform.position;
  13.             if (Mathf.Abs (Vector3.Angle (dir, v)) < m_Info.attackArc / 2)
  14.             {
  15.                 f = true;
  16.                 TargetPick pick = TargetPick.From (ref hits [s]);
  17.                 // AOE CircleCastAll 可能选不到shield
  18.                 pick.ToShield ();
  19.                 if (pick)
  20.                 if (m_Filter.Test (ref pick))
  21.                     m_Info.AttackOn (pick, Vector3.zero, null, Const.NONE_EFFECT ,this);
  22.             }

  23.         }

  24.         return f;
  25.     }
复制代码

  原理很简单,先找到圆形范围内的所有collider,再判断是否在喷火器的扇形角度内。

  攻击、弹道这块内容游戏逻辑是游戏的一个重点,其实很难写一辆篇文章说清,其实我很想把从最下层的u3d的物理、绘制到最上层代码逻辑架构 全部说清楚,

  但发现写一篇文博耗费的时间比我想象长,长到我写代码实现的一个功能的时间还没写一篇文章长。

  所以,我考虑了下,不能指望所有的东西全部说清楚,最要还是讲原理,不同于网上大部分的教程讲的是最基础的内容,甚至是解释api,

  而是建立读者有一定基础上,讲原理、讲结构,讲自认为的难点,有助于自己梳理游戏代码,也是对关键的技术点做一个备忘。

  下篇预告:弹道的图形效果实现、攻击逻辑的结构和要点(比如本文中展示代码中定义的类的意义)

  (本篇完,下篇待续)

相关阅读:糍粑大叔的独游之旅-战斗!之弹道实现(上)

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-7-28 04:55

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表