Unity子弹生成系统
子弹系统和粒子系统比较类似,为了创建出五花八门的子弹,例如追踪,连续继承,散弹等,需要一个拥有众多参数的子弹生成器,这里叫它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子弹生成系统的更多相关文章
- Spine学习七 - spine动画资源+ Unity Mecanim动画系统
前面已经讲过 Spine自己动画状态机的动画融合,但是万一有哥们就是想要使用Unity的动画系统,那有没有办法呢?答案是肯定的,接下来,就说说如何实现: 1. 在project面板找打你导入的Spin ...
- CCF真题之模板生成系统
问题描述 成成最近在搭建一个网站,其中一些页面的部分内容来自数据库中不同的数据记录,但是页面的基本结构是相同的.例如,对于展示用户信息的页面,当用户为 Tom 时,网页的源代码是 而当用户为 Jerr ...
- Unity 梯子生成算法
Unity之生成梯子算法的实现. 1.通过预制物体动态生成角度可设置的梯子形状. 1.1 主要涉及到的数学知识点,角度与弧度的转化. 弧度=角度乘以π后再除以180 角度=弧度除以π再乘以180 1. ...
- CCF系列之模板生成系统( 201509-3 )
试题名称: 模板生成系统 试题编号: 201509-3 时间限制: 1.0s 内存限制: 256.0MB 问题描述 成成最近在搭建一个网站,其中一些页面的部分内容来自数据库中不同的数据记录,但是页面的 ...
- 极速创建 IOS APP !涛舅舅苹果 IOS APP自助生成系统正式上线
经过大量的测试和开发工作,涛舅舅苹果 IOS APP自助生成系统正式上线! 本系统主要功能: 1.用最最简单的方式将H5网站打包生成一个苹果APP 2.只需要提供APP标题,H5网站首页url地址,一 ...
- 极速创建 IOS APP !涛舅舅苹果 IOS APP自助生成系统!不用证书、不用越狱、永久可用
不用签名将网页封装成苹果APP,无需苹果企业签名,IPA签名,ios签名,免越狱安装 (本方法只支持网站封装app,原生的用不了,详细请咨询客服) 近期很多朋友问我把网站变成app的方法,原因很多种, ...
- 企业信息化-Excel快速生成系统
企业信息化,主要是指对企业生产运营过程所形成的信息数字化,最终形成了数字资产.大型企业为了节约成本,提高协同工作效率,都会定制ERP.办公OA.流程审批等系统做信息化支撑.但是中小企业精力投入到生成中 ...
- Leaf——美团点评分布式ID生成系统 UUID & 类snowflake
Leaf——美团点评分布式ID生成系统 https://tech.meituan.com/MT_Leaf.html
- 分布式ID生成系统 UUID与雪花(snowflake)算法
Leaf——美团点评分布式ID生成系统 -https://tech.meituan.com/MT_Leaf.html 网游服务器中的GUID(唯一标识码)实现-基于snowflake算法-云栖社区-阿 ...
随机推荐
- Linux - 通过expect工具实现脚本的自动交互
目录 1 安装expect工具 2 expect的常用命令 3 作用原理简介 3.1 示例脚本 3.2 脚本功能解读 4 其他脚本使用示例 4.1 直接通过expect执行多条命令 4.2 通过she ...
- java学习之- 线程继承Thread类
标签(空格分隔): 线程 在java.lang包中有个Thread子类,大家可以自行查阅文档,及范例: 如何在自定义的代码中,自定义一个线程呢? 1.通过对api的查找,java已经提供了对线程这类事 ...
- 分布式配置中心Apollo——QuickStart
分布式配置中心 剥离配置文件,实现动态修改,自动更新. [假设没有分布式配置中心,修改配置文件后都需要重启服务,对于数量庞多的微服务开发来说,就会非常繁琐] 分布式配置中心有哪些 disconf(依赖 ...
- 良许 | 命令的输出不会保存?居然连 tee 命令都不会用!
很多情况下,我们需要保存程序/命令的输出到本地,常用的一种方法是重定向,这也是一种很好的方法.但有个问题,如果你想要做后续操作,比如要统计输出的行数等,重定向就有困难了. 这时候,tee 命令就派上用 ...
- 给 asp.net core 写个中间件来记录接口耗时
给 asp.net core 写个中间件来记录接口耗时 Intro 写接口的难免会遇到别人说接口比较慢,到底慢多少,一个接口服务器处理究竟花了多长时间,如果能有具体的数字来记录每个接口耗时多少,别人再 ...
- python学习——列表和元组
一.列表 1)列表介绍 列表是Python内置的一种数据类型. >一组有序项目的集合(从第一个成员序号为0开始依次递增排序) >可变的数据类型(可进行增删改查) >列表中可以包含任何 ...
- 简易数据分析 11 | Web Scraper 抓取表格数据
这是简易数据分析系列的第 11 篇文章. 今天我们讲讲如何抓取网页表格里的数据.首先我们分析一下,网页里的经典表格是怎么构成的. First Name 所在的行比较特殊,是一个表格的表头,表示信息分类 ...
- XMLHttpRequest的概述
XMLHttpRequest的概述 一.前言 XMLHttpRequest 最早是在IE5中以ActiveX组件的形式实现的.非 W3C 标准. 创建XMLHttpRequest对象(由于非标准所以实 ...
- awrcrt更新到2.1(重大更新)
awrcrt更新到了2.1 awrcrt迎来了最近一年的最大一次更新,从2.03直接跳跃了2.1版本.本次更新,给awrcrt带了全面的改变. 最主要的更新内容是什么呢?请看 更新了图表javascr ...
- CF - 1117 F Crisp String
题目传送门 题解: 枚举非法对. 如果 ‘a' 和 ’b' 不能相邻的话,那么删除 'a' 'b'之间的字符就是非法操作了. 假设题目给定的字符串为 "acdbe",所以删除cd ...