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()&&checkFire()&& Input.GetButton("Fire1"))
{//注意條件順序,寫反就不對了
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。轉載請申明出處,謝謝!