子弹系统和粒子系统比较类似,为了创建和五花八门的子弹,例如追踪,连续继承,散弹等,需要一个拥有众多参数的子弹生成器,这里叫它Shooter好了。
Shooter负责把玩各类子弹造型和参数,创建出子弹,创建完了之后接下来就交给子弹自己来管理自己了。
所以,一个子弹系统包含:
1.ShooterSystem类
一个能生成各种类型子弹的发射器。
2.Bullet类
按照给定的初始参数不断向前飞行的子弹个体。
先思考每一个单独的子弹需要有哪些物理参数:
1 //目标 2 public GameObject Target { get; set; } 3 //瞬时速度 4 public float Velocity { get; set; } 5 //剩余生命周期 6 public float LifeTime { get; set; } 7 //角速度 8 public float Palstance { get; set; } 9 //线性加速度 10 public float Acceleration { get; set; } 11 //最大速度 12 public float MaxVelocity { get; set; }
这些参数不需要子弹自己来配置,而是交给把玩它们的Shooter来进行,但是子弹自身需要知道这些参数。
其中指得一提的是角速度,正常的子弹是没有追踪功能的,生成之后就只能自动向前飞,但一旦设置了子弹的目标后,子弹就必须根据角速度转向目标位置的向量,保证自己的前向能尽快和目标向量对齐;而这一对齐的过程,就需要用角速度来描述。
子弹在生命周期到了之后要自动销毁,因为它经常反复创建和销毁,最好使用对象池来进行这一过程:
https://www.cnblogs.com/koshio0219/p/11572567.html
调用如下:
1 public IEnumerator AutoRecycle() 2 { 3 yield return new WaitForSeconds(LifeTime); 4 ObjectPool.Instance.RecycleObj(gameObject); 5 }
子弹每一帧的状态都会有所变化,例如位置,速度的更新,向前运行的方向的更新等:
1 private void Update() 2 { 3 float deltaTime = Time.deltaTime; 4 //由当前子弹位置指向目标位置的向量,记为瞬时偏移向量 5 Vector3 offset = (Target.transform.position - transform.position).normalized; 6 //子弹的当前前进方向与瞬时偏移向量之间的夹角 7 float angle = Vector3.Angle(transform.forward, offset); 8 //夹角除以角速度计算需要转到相同方向所需要的总时间 9 float needTime = angle*1.0f / Palstance; 10 //插值运算出当前帧的前向方向向量,也即是需要偏移的角度 11 transform.forward = Vector3.Lerp(transform.forward, offset, deltaTime / needTime).normalized; 12 //处理线性加速度对于速度的增量 13 if (Velocity < MaxVelocity) 14 { 15 Velocity += deltaTime * Acceleration; 16 } 17 //按当前速度向前移动一帧的距离,赋值给当前位置 18 transform.position += transform.forward * Velocity * deltaTime; 19 }
如果不想让子弹追踪,也很简单,把角速度传为0即可,float除数为0也是没有问题的。
子弹生成器主要是创建子弹,所以需要包含子弹类的所有参数,除此之外,还需要有一些其他的参数:
1 public bool bAuto = false; 2 3 public GameObject bulletPrefab; 4 //子弹目标 5 public GameObject target; 6 //初速度 7 public float velocity = 0f; 8 //加速度 9 public float acceleration = 30f; 10 //总生命周期 11 public float lifeTime = 3f; 12 //初始方向 13 public Vector2 direction = Vector2.zero; 14 //最大速度 15 public float maxVelocity = 600; 16 //角速度 17 public float palstance = 120; 18 //角度波动范围 19 public float angelRange = 0f; 20 //延迟 21 public float delay = 1f; 22 //是否循环 23 public bool bLoop = false; 24 //时间间隔 25 public float timeCell = .1f; 26 //生成数量 27 public int count = 1; 28 //伤害 29 public float damage; 30 //碰撞类型 31 public CollisionType collisionType; 32 //是否有子系统 33 public bool bChildShooter = false; 34 //子系统是谁 35 public GameObject childShooter;
初始方向就是子弹生成后的前向方向,如果想制造散弹效果,则子弹就需要在一定的角度波动范围内生成前向方向,但生成的位置依然是统一的。
生成器还需要能循环生成子弹,能够在生成的子弹飞行过程中继续生成不一样效果的分裂子弹,所以还需要子系统,子系统和父系统可以写为同一个生成器类。需要注意的就是,子系统的生命周期需要依赖父系统生成的子弹的生命周期。
生成单个子弹的方法:
1 private void Creat(Transform parent) 2 { 3 //从对象池中取对象生成到指定物体下,复位坐标 4 var ins = ObjectPool.Instance.GetObj(bulletPrefab, parent.transform); 5 ins.transform.ResetLocal(); 6 7 //对子弹的属性赋值 8 var bullet = ins.GetComponent<Bullet>(); 9 bullet.Target = target; 10 bullet.Velocity = velocity; 11 bullet.Acceleration = acceleration; 12 bullet.LifeTime = lifeTime; 13 bullet.MaxVelocity = maxVelocity; 14 bullet.Palstance = palstance; 15 16 //确定子弹生成方向的范围 17 float x = Random.Range(direction.x - angelRange / 2, direction.x + angelRange / 2); 18 float y = Random.Range(direction.y - angelRange / 2, direction.y + angelRange / 2); 19 bullet.transform.eulerAngles = new Vector3(x, y, 0); 20 21 parent.DetachChildren(); 22 23 //开启子弹自动回收 24 StartCoroutine(bullet.AutoRecycle()); 25 26 //判断子生成器并自动运行 27 if (bChildShooter) 28 { 29 var cscs = childShooter.GetComponent<ShooterSystem>(); 30 if (lifeTime > cscs.delay) 31 StartCoroutine(cscs.AutoCreat(bullet.transform, this)); 32 else 33 Debug.Log("子发射器延迟时间设置有误!"); 34 } 35 }
对于子生成器来说,它也同样可能拥有自己的子生成器,在AutoCreat的方法中需要传递它的父生成器是谁,默认情况下为空:
1 IEnumerator AutoCreat(Transform parent, ShooterSystem parShooter = null) 2 { 3 yield return new WaitForSeconds(delay); 4 if (bLoop) 5 { 6 if (parShooter != null) 7 { 8 //子生成器需要计算循环的次数,父生成器则是无限循环 9 int loopCount = (int)(parShooter.lifeTime - delay / timeCell); 10 for (; loopCount > 0; loopCount--) 11 { 12 //每次循环生成的子弹数量 13 for (int i = 0; i < count; i++) 14 Creat(parent); 15 yield return new WaitForSeconds(timeCell); 16 } 17 } 18 else 19 { 20 for (; ; ) 21 { 22 for (int i = 0; i < count; i++) 23 Creat(parent); 24 yield return new WaitForSeconds(timeCell); 25 } 26 } 27 } 28 else 29 { 30 for (int i = 0; i < count; i++) 31 Creat(parent); 32 } 33 }
这里的伤害和碰撞类型不在此篇讨论范围内。