標籤:
Unity武器系統的最佳化
射擊遊戲子彈是最基本的遊戲對象,當然使用unity開發的話,做一個子彈並不是很難的事,從發射到子彈的飛行,到銷毀,基本上入門的程式員都能寫出來。
然而這個看似簡單的東西,有著很大的最佳化空間。這個最佳化分兩部分,一是,子彈的發射最佳化,另一個是子彈的碰撞檢測最佳化。
對於發射時的最佳化,主要考慮到子彈這個遊戲對象使用頻率比較高,一個關卡裡,要產生和銷毀大量的遊戲對象,從記憶體載入到執行個體化,然後執行銷毀動作,速度,效能太差,而且因為unity的記憶體管理並不好,因此為產生大量的記憶體垃圾,如果遊戲時間一長,或者在安卓等低端平台運行時就會造成卡頓現象。關於解決這個問題,網上有很多方案,也有現成的外掛程式,PoolManager等,管理對象的執行個體化和銷毀。當然可以自己寫一個簡單點的東西來最佳化這一過程。怎麼做呢?
原理就是重複利用子彈。用一個數組來儲存子彈的引用,初始化的時候全部隱藏,射擊的時候按照數組下標依次啟用。一個子彈打出去後,到了回收的時候,不要銷毀,而是將其“隱藏”,並做初始化處理。這個過程還可以最佳化,例如一個彈夾的子彈數量是30,實際上你只需要預先載入15發子彈就可以了。依次啟用,重複利用。
代碼中有詳細的注釋,我就不多說了。
using UnityEngine;using System.Collections;using System.Collections.Generic;public class GunShot : MonoBehaviour{ public string weaponName;//武器的名稱,使用Resource載入 private List<BullectOfWeapon> weapons;//用List集合儲存子彈對象數組 public int shellCount = 20;//一個彈夾的子彈數量 public int maxShell=10;//預先載入子彈數量,因為可以迴圈利用,可以小於等於彈夾的子彈數量 private int currentShell = 0;//當前啟用的子彈 public float fireRote=0.2f;//開火時間間隔,一次射擊到下一次的時間 private float currentFireTime;//目前時間 public float loadTime = 1.5f;//上子彈的時間。 private float currentLoadTime = 0; private int currentCount;//一個彈夾打出去的子彈數 public int chargerCount=5;//彈夾的數量 void Start() { InitGun();//初始化武器 } void Update() { if (!isLoading()&& Input.GetButton("Fire1")&&checkFire()) {//注意條件順序,寫反就不對了 Shot();//開火代碼 } } public void InitGun()//初始化武器 { //武器的名稱,使用Resource載入 BullectOfWeapon _weaponModel = Resources.Load<BullectOfWeapon>(weaponName); if (_weaponModel) { weapons = new List<BullectOfWeapon>(); for (int i = 0; i < maxShell; i++) { //執行個體化子彈對象 BullectOfWeapon _weapon = Instantiate(_weaponModel); //將其隱藏 _weapon.gameObject.SetActive(false); //初始化子彈,將子彈放在“彈夾裡”,將產生的子彈作為子物件 //這麼做可以方便管理 _weapon.InitWeapon(transform); //將子彈對象的引用添加至集合 weapons.Add(_weapon); } } _weaponModel = null;//釋放子彈對象,這個可以讓系統能更好多份對記憶體進行回收 } public bool checkFire() {//根據上一次開火的時間判斷是否可以開火 currentFireTime += Time.deltaTime; if (currentFireTime >= fireRote) { currentFireTime = 0; return true; } return false; } public bool isLoading() { if (chargerCount == 0) return true;//彈夾使用完畢 if (currentCount < shellCount) { //判斷是否已經 return false; } currentLoadTime += Time.deltaTime; if (currentLoadTime >= loadTime) { currentLoadTime = 0; chargerCount--;//減少彈夾的數量 currentCount = 0;//下一個彈夾從零計數 return false; } return true; } public void Shot() { if (weapons!=null && weapons.Count > 0) {//先判斷子彈數組防止為空白 if (!weapons[currentShell].gameObject.activeSelf)//檢查當前子彈是否正在使用 { weapons[currentShell].ActiveWeapon();//將子彈啟用 currentShell = (currentShell + 1) % maxShell;//移動下標 currentCount++; } } }}
public class BullectOfWeapon : MonoBehaviour{ private Transform charger;//彈夾,用來回收時將子彈對象放在父物體裡,方便資源的管理 public float lifeTime = 3;//子彈的存留時間 public float maxSpeed;//子彈移動的最大速度 private Vector3 velocity;//子彈的即時速度 private Vector3 currentPos;//子彈的當前位置 private Vector3 nextPos;//子彈的上一個位置 private bool isHit;//是否碰撞 private float distence; private RaycastHit hit; public void InitWeapon(Transform parent) {//初始化子彈 if (!charger) this.charger = parent;//將子彈作為子物件放在“彈夾裡” transform.parent = charger; //位置,角度歸零 transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; } public void ActiveWeapon() {//啟用子彈 currentPos = nextPos = transform.position;//初始化當前位置和下一個位置 isHit = false;// velocity = maxSpeed * transform.forward.normalized;//初始化速度 transform.parent = null;//將子彈從彈夾分離出去 gameObject.SetActive(true); //開啟協程,lifeTime之後將物體再次隱藏 StartCoroutine(DestroyWeapon(gameObject,lifeTime)); } public IEnumerator DestroyWeapon(GameObject desObj, float desTime) { yield return new WaitForSeconds(desTime>0?desTime:0); if (desObj.activeSelf) { desObj.SetActive(false); //隱藏子彈後重新初始化,為下一次使用做準備 InitWeapon(charger); } } void Update() { Fire(); } public void Fire() { //這個是子彈的被射出後的飛行與碰撞檢測代碼 if (!isHit)//沒有發生碰撞保持飛行 { Flying(); } } private void Flying() {//子彈的飛行 } }
以上就是槍和子彈的部分代碼。關於特效,力什麼的我就省略了。接下來來探討一下子彈的飛行與碰撞的檢測。
一般新手做子彈,使用transform+碰撞器+剛體,這樣的子彈效能不高,而且極容易發生穿牆事件。Unity官方計程車兵射擊範例就是這麼搞的,為了防止穿牆,子彈的飛行速度很慢,看起來很假。
學過射線的,估計會使用射線的碰撞檢測,判斷子彈是否擊中目標。然後在碰撞點產生火花特效等。這樣的子彈不會穿牆,但是少了子彈飛行的效果。然後就有人用發射一個粒子來展示子彈的飛行。
對於上面的兩種方案,第一種是要果斷放棄的,效能差。第二種效能比較好,我來將其最佳化一下,讓子彈看起來更像子彈,就是射線檢測+子彈飛行效果。通過修改子彈的位移,來類比飛行效果,使用射線檢測碰撞。這樣效能和效果都有了。好不多說看實現代碼。
private void Flying() {//子彈的飛行 nextPos += velocity * Time.deltaTime;//使用平滑過渡不用擔心穿牆問題 distence = (nextPos-currentPos).magnitude; if (distence > 0) { if (Physics.Raycast(currentPos,transform.forward,out hit,distence)) { isHit = true; nextPos = hit.point;//將下一個目標點設定為碰撞點 } } currentPos = transform.position;//擷取當前位置 transform.position = nextPos;//將當前位置置於下一個點 if (isHit)//碰撞後將子彈隱藏回收 { StartCoroutine(DestroyWeapon(gameObject,0)); } }
關於特效我就不多說了。使用TrailRenderer可以類比子彈的軌跡,簡單實用。力效果什麼的大家自行加上。
上面就是最佳化後的武器系統,設計原理儘可能的節省記憶體,並提升自身效能,同時保證系統的正常使用。上面的方法可以同樣使用在特效上,用數組儲存火花粒子,彈殼,或者子彈貼圖,這樣在不失真的情況下還能保證系統效能。例如規定彈孔貼圖的數量,然後就是動態,每產生一個彈孔後,不是銷毀,而。當彈孔數量達到最大值時,將以前的彈孔貼圖放在新的碰撞點即可。而不是重複的執行載入執行個體和銷毀,那樣對系統的記憶體和效率影響更大。因為一個遊戲對象的初始化過程和對應指令碼的載入是很耗時的,儘管這對CPU來說是小事。
本文的連結:http://www.cnblogs.com/jqg-aliang/p/4714750.html。轉載請申明出處,謝謝!
Unity武器系統的最佳化