子弹系统和粒子系统比较类似,为了创建和五花八门的子弹,例如追踪,连续继承,散弹等,需要一个拥有众多参数的子弹生成器,这里叫它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 }
这里的伤害和碰撞类型不在此篇讨论范围内。