查看: 756|回复: 0
打印 上一主题 下一主题

[其他] Unity武器系统的优化

[复制链接]
may    

8830

主题

81

听众

7万

积分

首席设计师

Rank: 8Rank: 8

纳金币
52336
精华
343

最佳新人 热心会员 灌水之王 活跃会员 突出贡献 荣誉管理 论坛元老

跳转到指定楼层
楼主
发表于 2015-11-30 00:51:52 |只看该作者 |倒序浏览
      射击游戏子弹是最基本的游戏对象,当然使用unity开发的话,做一个子弹并不是很难的事,从发射到子弹的飞行,到销毁,基本上入门的程序员都能写出来。
  然而这个看似简单的东西,有着很大的优化空间。这个优化分两部分,一是,子弹的发射优化,另一个是子弹的碰撞检测优化。
     对于发射时的优化,主要考虑到子弹这个游戏对象使用频率比较高,一个关卡里,要产生和销毁大量的游戏对象,从内存加载到实例化,然后执行销毁动作,速度,性能太差,而且因为unity的内存管理并不好,因此为产生大量的内存垃圾,如果游戏时间一长,或者在安卓等低端平台运行时就会造成卡顿现象。关于解决这个[color=rgb(85, 85, 85) !important]问题,网上有很多方案,也有现成的插件,PoolManager等,管理对象的实例化和销毁。当然可以自己写一个简单点的东西来优化这一过程。怎么做呢?
     原理就是重复利用子弹。用一个数组来保存子弹的引用,初始化的时候全部隐藏,射击的时候按照数组下标依次激活。一个子弹打出去后,到了回收的时候,不要销毁,而是将其“隐藏”,并做初始化处理。这个过程还可以优化,例如一个弹夹的子弹数量是30,实际上你只需要预加载15发子弹就可以了。依次激活,重复利用。
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public class GunShot : MonoBehaviour
  5. {
  6.     public string weaponName;//武器的名称,使用Resource加载
  7.     private List<BullectOfWeapon> weapons;//用List集合保存子弹对象数组
  8.     public int shellCount = 20;//一个弹夹的子弹数量
  9.     public int maxShell=10;//预加载子弹数量,因为可以循环利用,可以小于等于弹夹的子弹数量
  10.     private int currentShell = 0;//当前激活的子弹
  11.    
  12.     public float fireRote=0.2f;//开火时间间隔,一次射击到下一次的时间
  13.     private float currentFireTime;//当前时间

  14.     public float loadTime = 1.5f;//上子弹的时间。
  15.     private float currentLoadTime = 0;
  16.     private int currentCount;//一个弹夹打出去的子弹数


  17.     public int chargerCount=5;//弹夹的数量
  18.     void Start() {
  19.         InitGun();//初始化武器
  20.     }
  21.     void Update() {
  22.         if (!isLoading()&&checkFire()&&  Input.GetButton("Fire1"))
  23. {//注意条件顺序,写反就不对了


  24.             Shot();//开火代码
  25.         }
  26.     }
  27.     public void InitGun()//初始化武器
  28.     {
  29.         //武器的名称,使用Resource加载
  30.         BullectOfWeapon _weaponModel = Resources.Load<BullectOfWeapon>(weaponName);
  31.         
  32.         if (_weaponModel)
  33.         {
  34.             weapons = new List<BullectOfWeapon>();
  35.             for (int i = 0; i < maxShell; i++)
  36.             {
  37.                 //实例化子弹对象
  38.                 BullectOfWeapon _weapon = Instantiate(_weaponModel);
  39.                 //将其隐藏
  40.                 _weapon.gameObject.SetActive(false);
  41.                 //初始化子弹,将子弹放在“弹夹里”,将生成的子弹作为子对象
  42.                 //这么做可以方便管理
  43.                 _weapon.InitWeapon(transform);
  44.                 //将子弹对象的引用添加至集合
  45.                 weapons.Add(_weapon);
  46.             }
  47.         }
  48.         _weaponModel = null;//释放子弹对象,这个可以让系统能更好多份对内存进行回收
  49.     }
  50.     public bool checkFire() {//根据上一次开火的时间判断是否可以开火
  51.         currentFireTime += Time.deltaTime;
  52.         if (currentFireTime >= fireRote) {
  53.             currentFireTime = 0;
  54.             return true;
  55.         }
  56.         return false;
  57.     }
  58.     public bool isLoading() {
  59.         if (chargerCount == 0) return true;//弹夹使用完毕
  60.         if (currentCount < shellCount) { //判断是否已经
  61.             return false;
  62.         }
  63.         currentLoadTime += Time.deltaTime;
  64.         if (currentLoadTime >= loadTime) {
  65.             currentLoadTime = 0;
  66.             chargerCount--;//减少弹夹的数量
  67.             currentCount = 0;//下一个弹夹从零计数
  68.             return false;
  69.         }
  70.         return true;
  71.     }
  72.     public void Shot() {
  73.         if (weapons!=null && weapons.Count > 0) {//先判断子弹数组防止为空
  74.             if (!weapons[currentShell].gameObject.activeSelf)//检查当前子弹是否正在使用
  75.             {
  76.                 weapons[currentShell].ActiveWeapon();//将子弹激活
  77.                 currentShell = (currentShell + 1) % maxShell;//移动下标
  78.                 currentCount++;
  79.             }
  80.             
  81.         }
  82.     }
  83. }
复制代码
  1. public class BullectOfWeapon : MonoBehaviour
  2. {
  3.     private Transform charger;//弹夹,用来回收时将子弹对象放在父物体里,方便资源的管理
  4.    
  5.     public float lifeTime = 3;//子弹的生存时间
  6.     public float maxSpeed;//子弹移动的最大速度


  7.     private Vector3 velocity;//子弹的实时速度
  8.     private Vector3 currentPos;//子弹的当前位置
  9.     private Vector3 nextPos;//子弹的上一个位置
  10.     private bool isHit;//是否碰撞
  11.     private float distence;
  12.     private RaycastHit hit;
  13.     public void InitWeapon(Transform parent) {//初始化子弹
  14.         if (!charger) this.charger = parent;//将子弹作为子对象放在“弹夹里”
  15.         transform.parent = charger;
  16.         //位置,角度归零
  17.         transform.localPosition = Vector3.zero;
  18.         transform.localRotation = Quaternion.identity;
  19.         
  20.     }
  21.     public void ActiveWeapon() {//激活子弹
  22.       
  23.         currentPos = nextPos = transform.position;//初始化当前位置和下一个位置
  24.         isHit = false;//
  25.         velocity = maxSpeed * transform.forward.normalized;//初始化速度
  26.         transform.parent = null;//将子弹从弹夹分离出去
  27.         gameObject.SetActive(true);
  28.         //开启协程,lifeTime之后将物体再次隐藏
  29.         StartCoroutine(DestroyWeapon(gameObject,lifeTime));
  30.     }
  31.     public IEnumerator DestroyWeapon(GameObject desObj, float desTime) {
  32.         yield return new WaitForSeconds(desTime>0?desTime:0);
  33.         if (desObj.activeSelf)
  34.         {
  35.             desObj.SetActive(false);
  36.             //隐藏子弹后重新初始化,为下一次使用做准备
  37.             InitWeapon(charger);
  38.         }
  39.         
  40.     }
  41.     void Update() {
  42.         Fire();
  43.     }
  44.     public void Fire() { //这个是子弹的被射出后的飞行与碰撞检测代码
  45.         if (!isHit)//没有发生碰撞保持飞行
  46.         {
  47.             Flying();

  48.         }
  49.       
  50.     }
  51.     private void Flying() {//子弹的飞行
  52.       
  53.     }
  54.    
  55. }
复制代码
以上就是枪和子弹的部分代码。关于特效,力什么的我就省略了。接下来来探讨一下子弹的飞行与碰撞的检测。
一般新手做子弹,使用transform+碰撞器+刚体,这样的子弹性能不高,而且极容易发生穿墙事件。Unity官方的士兵射击样例就是这么搞的,为了防止穿墙,子弹的飞行速度很慢,看起来很假。
学过射线的,估计会使用射线的碰撞检测,判断子弹是否击中目标。然后在碰撞点生成火花特效等。这样的子弹不会穿墙,但是少了子弹飞行的效果。然后就有人用发射一个粒子来展示子弹的飞行。

对于上面的两种方案,第一种是要果断放弃的,性能差。第二种性能比较好,我来将其优化一下,让子弹看起来更像子弹,就是射线检测+子弹飞行效果。通过修改子弹的位移,来模拟飞行效果,使用射线检测碰撞。这样性能和效果都有了。好不多说看实现代码。
  1. private void Flying() {//子弹的飞行
  2.         nextPos += velocity * Time.deltaTime;//使用平滑过渡不用担心穿墙问题
  3.         
  4.         distence = (nextPos-currentPos).magnitude;
  5.         if (distence > 0) {
  6.             if (Physics.Raycast(currentPos,transform.forward,out hit,distence))
  7.             {
  8.                
  9.                 isHit = true;

  10.                 nextPos = hit.point;//将下一个目标点设置为碰撞点
  11.             }
  12.         }
  13.         currentPos = transform.position;//获取当前位置
  14.         transform.position = nextPos;//将当前位置置于下一个点
  15.         if (isHit)//碰撞后将子弹隐藏回收
  16.         {
  17.             StartCoroutine(DestroyWeapon(gameObject,0));
  18.         }
  19.     }
复制代码
关于特效我就不多说了。使用TrailRenderer可以模拟子弹的轨迹,简单实用。力效果什么的大家自行加上。

上面就是优化后的武器系统,设计原理尽可能的节省内存,并提升自身性能,同时保证系统的正常使用。上面的方法可以同样使用在特效上,用数组保存火花粒子,弹壳,或者子弹贴图,这样在不失真的情况下还能保证系统性能。例如规定弹孔贴图的数量,然后就是动态的,每生成一个弹孔后,不是销毁,而。当弹孔数量达到最大值时,将以前的弹孔贴图放在新的碰撞点即可。而不是重复的执行加载实例和销毁,那样对系统的内存和效率影响更大。因为一个游戏对象的初始化过程和对应脚本的加载是很耗时的,尽管这对CPU来说是小事。

分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

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

手机版|纳金网 ( 闽ICP备2021016425号-2/3

GMT+8, 2024-11-27 15:53 , Processed in 0.118800 second(s), 29 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部