子弹系统和粒子系统比较类似,为了创建出五花八门的子弹,例如追踪,连续继承,散弹等,需要一个拥有众多参数的子弹生成器,这里叫它Shooter好了。

Shooter负责把玩各类子弹造型和参数,创建出子弹,创建完了之后接下来就交给子弹自己来管理自己了。

所以,一个子弹系统包含:

1.ShooterSystem类

一个能生成各种类型子弹的发射器。

2.Bullet类

按照给定的初始参数不断向前飞行的子弹个体。

先思考每一个单独的子弹需要有哪些物理参数:

     //目标
public GameObject Target { get; set; }
//瞬时速度
public float Velocity { get; set; }
//剩余生命周期
public float LifeTime { get; set; }
//角速度
public float Palstance { get; set; }
//线性加速度
public float Acceleration { get; set; }
//最大速度
public float MaxVelocity { get; set; }

这些参数不需要子弹自己来配置,而是交给把玩它们的Shooter来进行,但是子弹自身需要知道这些参数。

其中指得一提的是角速度,正常的子弹是没有追踪功能的,生成之后就只能自动向前飞,但一旦设置了子弹的目标后,子弹就必须根据角速度转向目标位置的向量,保证自己的前向能尽快和目标向量对齐;而这一对齐的过程,就需要用角速度来描述。

子弹在生命周期到了之后要自动销毁,因为它经常反复创建和销毁,最好使用对象池来进行这一过程:

https://www.cnblogs.com/koshio0219/p/11572567.html

调用如下:

     public IEnumerator AutoRecycle()
{
yield return new WaitForSeconds(LifeTime);
ObjectPool.Instance.RecycleObj(gameObject);
}

子弹每一帧的状态都会有所变化,例如位置,速度的更新,向前运行的方向的更新等:

     private void Update()
{
float deltaTime = Time.deltaTime;
//由当前子弹位置指向目标位置的向量,记为瞬时偏移向量
Vector3 offset = (Target.transform.position - transform.position).normalized;
//子弹的当前前进方向与瞬时偏移向量之间的夹角
float angle = Vector3.Angle(transform.forward, offset);
//夹角除以角速度计算需要转到相同方向所需要的总时间
float needTime = angle*1.0f / Palstance;
//插值运算出当前帧的前向方向向量,也即是需要偏移的角度
transform.forward = Vector3.Lerp(transform.forward, offset, deltaTime / needTime).normalized;
//处理线性加速度对于速度的增量
if (Velocity < MaxVelocity)
{
Velocity += deltaTime * Acceleration;
}
//按当前速度向前移动一帧的距离,赋值给当前位置
transform.position += transform.forward * Velocity * deltaTime;
}

如果不想让子弹追踪,也很简单,把角速度传为0即可,float除数为0也是没有问题的。

子弹生成器主要是创建子弹,所以需要包含子弹类的所有参数,除此之外,还需要有一些其他的参数:

     public bool bAuto = false;

     public GameObject bulletPrefab;
//子弹目标
public GameObject target;
//初速度
public float velocity = 0f;
//加速度
public float acceleration = 30f;
//总生命周期
public float lifeTime = 3f;
//初始方向
public Vector2 direction = Vector2.zero;
//最大速度
public float maxVelocity = ;
//角速度
public float palstance = ;
//角度波动范围
public float angelRange = 0f;
//延迟
public float delay = 1f;
//是否循环
public bool bLoop = false;
//时间间隔
public float timeCell = .1f;
//生成数量
public int count = ;
//伤害
public float damage;
//碰撞类型
public CollisionType collisionType;
//是否有子系统
public bool bChildShooter = false;
//子系统是谁
public GameObject childShooter;

初始方向就是子弹生成后的前向方向,如果想制造散弹效果,则子弹就需要在一定的角度波动范围内生成前向方向,但生成的位置依然是统一的。

生成器还需要能循环生成子弹,能够在生成的子弹飞行过程中继续生成不一样效果的分裂子弹,所以还需要子系统,子系统和父系统可以写为同一个生成器类。需要注意的就是,子系统的生命周期需要依赖父系统生成的子弹的生命周期。

生成单个子弹的方法:

     private void Creat(Transform parent)
{
//从对象池中取对象生成到指定物体下,复位坐标
var ins = ObjectPool.Instance.GetObj(bulletPrefab, parent.transform);
ins.transform.ResetLocal(); //对子弹的属性赋值
var bullet = ins.GetComponent<Bullet>();
bullet.Target = target;
bullet.Velocity = velocity;
bullet.Acceleration = acceleration;
bullet.LifeTime = lifeTime;
bullet.MaxVelocity = maxVelocity;
bullet.Palstance = palstance; //确定子弹生成方向的范围,默认Z轴正方向为子弹飞行方向
float x = Random.Range(direction.x - angelRange / , direction.x + angelRange / );
float y = Random.Range(direction.y - angelRange / , direction.y + angelRange / );
bullet.transform.localEulerAngles = new Vector3(x, y, ); parent.DetachChildren(); //开启子弹自动回收
bullet.StartCoroutine(bullet.AutoRecycle()); //判断子生成器并自动运行
if (bChildShooter)
{
var cscs = childShooter.GetComponent<ShooterSystem>();
if (lifeTime > cscs.delay)
StartCoroutine(cscs.AutoCreat(bullet.transform, this));
else
Debug.Log("子发射器延迟时间设置有误!");
}
}

对于子生成器来说,它也同样可能拥有自己的子生成器,在AutoCreat的方法中需要传递它的父生成器是谁,默认情况下为空:

     IEnumerator AutoCreat(Transform parent, ShooterSystem parShooter = null)
{
yield return new WaitForSeconds(delay);
if (bLoop)
{
if (parShooter != null)
{
//子生成器需要计算循环的次数,父生成器则是无限循环
int loopCount = (int)((parShooter.lifeTime - delay) / timeCell);
for (; loopCount > ; loopCount--)
{
//每次循环生成的子弹数量
for (int i = ; i < count; i++)
Creat(parent);
yield return new WaitForSeconds(timeCell);
}
}
else
{
for (; ; )
{
for (int i = ; i < count; i++)
Creat(parent);
yield return new WaitForSeconds(timeCell);
}
}
}
else
{
for (int i = ; i < count; i++)
Creat(parent);
}
}

有关伤害判断和碰撞检测不在此篇讨论范围内。

2019年12月12日更新:

增加以下几个功能:

1.可以控制子弹仅在单轴向的角度范围内散射,比如有时想让子弹只在同一个平面内散射,而不是在三维空间中。

2.可以控制子弹在散射范围内平均分布,而不是仅能随机分布。

3.可以控制子弹在非循环发射状态下按照固定时间间隔先后发射,比如追踪导弹一发发有序射击。

在此之前,先优化子弹中的一个小问题,子弹类的Update方法中,仅当存在追踪目标且角速度大于零时追踪目标:

     private void Update()
{
float deltaTime = Time.deltaTime;
if (Target != null && Palstance > )
{
//由当前子弹位置指向目标位置的向量,记为瞬时偏移向量
Vector3 offset = (Target.transform.position - transform.position).normalized;
//子弹的当前前进方向与瞬时偏移向量之间的夹角
float angle = Vector3.Angle(transform.forward, offset);
//夹角除以角速度计算需要转到相同方向所需要的总时间
float needTime = angle * 1.0f / Palstance;
//插值运算出当前帧的前向方向向量,也即是需要偏移的角度
transform.forward = Vector3.Lerp(transform.forward, offset, deltaTime / needTime).normalized;
}
//处理线性加速度对于速度的增量
if (Velocity < MaxVelocity)
{
Velocity += deltaTime * Acceleration;
}
//按当前速度向前移动一帧的距离,赋值给当前位置
transform.position += transform.forward * Velocity * deltaTime;
}

下面开始实现前面的几个功能:

定义可选轴向,理论上只要绕两个方向的轴向就可以定义三维空间中的任何一个方向,这里将Z轴作为初始的前进方向因此不对Z轴作任何操作和改变。

 public enum AngelRangeAxis
{
//仅在绕Y轴的平面上,也即是X-Z平面
RYAxis,
//仅在绕X轴的平面上,也即是Y-Z平面
RXAxis,
//三维空间中
BothXY
}

在ShooterSystem类中增加定义以下属性:

     //是否固定单位角度
public bool bFixedAngel = false;
//单数量时间间隔
public float EachCountDur = 0f;
//计算得出的固定单位角度
private float FixAngel;
//范围轴向设置
public AngelRangeAxis RangeAxis;

在Start方法中根据一次发射数量计算单位角度:

         if (bFixedAngel)
{
FixAngel = AngelRange / (Count - );
}

在Creat方法中增加参数——当前创建的子弹索引idx,默认值为-1,可以不传递该参数,当传递该参数时,用于计算每一子弹在范围内应处于的角度:

         //确定子弹生成方向的范围,默认z轴正方向为子弹飞行方向
switch (RangeAxis)
{
case AngelRangeAxis.RYAxis:
bullet.transform.localEulerAngles = new Vector3(Direction.x, GetLocalEulerAxis(Direction.y, idx), );
break;
case AngelRangeAxis.RXAxis:
bullet.transform.localEulerAngles = new Vector3(GetLocalEulerAxis(Direction.x, idx), Direction.y, );
break;
case AngelRangeAxis.Both:
bullet.transform.localEulerAngles = new Vector3(GetLocalEulerAxis(Direction.x, idx), GetLocalEulerAxis(Direction.y, idx), );
break;
}

其中方法GetLocalEulerAxis定义如下,主要用于确定轴向的最终值(无论是固定角度还是随机):

     private float GetLocalEulerAxis(float dirAxis,int idx)
{
if (bFixedAngel)
return dirAxis- AngelRange / + FixAngel * idx;
else
return Random.Range(dirAxis - AngelRange / , dirAxis + AngelRange / );
}

在AutoCreat协程中的非循环生成条件中进行如下修改:

             for (int i = ; i < Count; i++)
{
if (bFixedAngel)
Creat(parent, i);
else
Creat(parent);
yield return new WaitForSeconds(EachCountDur);
}

修改后可以发射出类似于这样的追踪导弹:

或者这样同一平面内的等间距子弹:

Unity子弹生成系统的更多相关文章

  1. Spine学习七 - spine动画资源+ Unity Mecanim动画系统

    前面已经讲过 Spine自己动画状态机的动画融合,但是万一有哥们就是想要使用Unity的动画系统,那有没有办法呢?答案是肯定的,接下来,就说说如何实现: 1. 在project面板找打你导入的Spin ...

  2. CCF真题之模板生成系统

    问题描述 成成最近在搭建一个网站,其中一些页面的部分内容来自数据库中不同的数据记录,但是页面的基本结构是相同的.例如,对于展示用户信息的页面,当用户为 Tom 时,网页的源代码是 而当用户为 Jerr ...

  3. Unity 梯子生成算法

    Unity之生成梯子算法的实现. 1.通过预制物体动态生成角度可设置的梯子形状. 1.1 主要涉及到的数学知识点,角度与弧度的转化. 弧度=角度乘以π后再除以180 角度=弧度除以π再乘以180 1. ...

  4. CCF系列之模板生成系统( 201509-3 )

    试题名称: 模板生成系统 试题编号: 201509-3 时间限制: 1.0s 内存限制: 256.0MB 问题描述 成成最近在搭建一个网站,其中一些页面的部分内容来自数据库中不同的数据记录,但是页面的 ...

  5. 极速创建 IOS APP !涛舅舅苹果 IOS APP自助生成系统正式上线

    经过大量的测试和开发工作,涛舅舅苹果 IOS APP自助生成系统正式上线! 本系统主要功能: 1.用最最简单的方式将H5网站打包生成一个苹果APP 2.只需要提供APP标题,H5网站首页url地址,一 ...

  6. 极速创建 IOS APP !涛舅舅苹果 IOS APP自助生成系统!不用证书、不用越狱、永久可用

    不用签名将网页封装成苹果APP,无需苹果企业签名,IPA签名,ios签名,免越狱安装 (本方法只支持网站封装app,原生的用不了,详细请咨询客服) 近期很多朋友问我把网站变成app的方法,原因很多种, ...

  7. 企业信息化-Excel快速生成系统

    企业信息化,主要是指对企业生产运营过程所形成的信息数字化,最终形成了数字资产.大型企业为了节约成本,提高协同工作效率,都会定制ERP.办公OA.流程审批等系统做信息化支撑.但是中小企业精力投入到生成中 ...

  8. Leaf——美团点评分布式ID生成系统 UUID & 类snowflake

    Leaf——美团点评分布式ID生成系统 https://tech.meituan.com/MT_Leaf.html

  9. 分布式ID生成系统 UUID与雪花(snowflake)算法

    Leaf——美团点评分布式ID生成系统 -https://tech.meituan.com/MT_Leaf.html 网游服务器中的GUID(唯一标识码)实现-基于snowflake算法-云栖社区-阿 ...

随机推荐

  1. python学习笔记(3)--函数、参数、变量、递归

    1.函数基本语法和特性 背景摘要 现在老板让你写一个监控程序,监控服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时即发邮件报警,你掏出了所有的知识量吗,写出了以下代码 whi ...

  2. nessus安装

    1.安装注册 (1)从https://www.tenable.com/products/nessus/select-your-operating-system上下载对应操作系统版本的nessus,结果 ...

  3. cogs 80. 石子归并 动态规划

    80. 石子归并 ★★   输入文件:shizi.in   输出文件:shizi.out   简单对比时间限制:1 s   内存限制:128 MB 设有N堆沙(shi)子排成一排,其编号为1,2,3, ...

  4. Python利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:

    这是一个最简单的自定义函数,自己调用自己,我的理解是这样的: 1.传一个s参数进行判断,如果有空字符它会切掉前后的空字符,返回一个新的s,这个新的s还有的话会继续执行这种重复的操作,类似于递归(博主不 ...

  5. 纯css的滑块开关按钮

    之前在项目中使用滑块开关按钮,纯css写的,不考虑兼容低版本浏览器,先说下原理: 使用 checkbox 的 选中 checked 属性改变css 伪类样式, 一定要使用-webkit-appeara ...

  6. xib上的控件属性为什么要使用weak

    常规中,从xib拖出一个控件时,系统会自动生成一段代码,如下: 从这个图片中,可以看到控件的属性都是用的weak,这是为什么呢? 首先,如果把weak修改成strong其实也是可以的,但是会出现一个问 ...

  7. switch语句(上)(转载)

    switch语句是C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码.switch语句可以具备多个分支,也就是说,根据参数的N种取值,可以跳转到N个代码段去运行.这不同于if语句,一条单独 ...

  8. Java 安全之:csrf防护实战分析

    上文总结了csrf攻击以及一些常用的防护方式,csrf全称Cross-site request forgery(跨站请求伪造),是一类利用信任用户已经获取的注册凭证,绕过后台用户验证,向被攻击网站发送 ...

  9. liunx安装jdk

    jdk 安装包  https://pan.baidu.com/s/1cKnUQGU2Sk2nsARAzzVAHw [root@localhost ~]# tar -zxvf jdk-8u152-lin ...

  10. P2486 [SDOI2011]染色 维护区间块数 树链剖分

    https://www.luogu.org/problemnew/show/P2486   题意 对一个树上维护两种操作,一种是把x到y间的点都染成c色,另一种是求x到y间的点有多少个颜色块,比如11 ...