前篇:游戲開發設計模式之命令模式(unity3d 示例實現)
博主才學尚淺,難免會有錯誤,尤其是設計模式這種極富禪意且需要大量經驗的東西,如果哪里書寫錯誤或有遺漏,還請各位前輩指正。
原理:從一個固定的池中重用對象,來提升性能和內存的使用,而不是一個一個的分配內存在釋放它們。
當你需要創造大量重復的對象,而且經常使用這些對象,你就要考慮使用對象池了,因為反復創建銷毀就是一個內存反復分配與釋放的過程,很容易產生內存碎片。
在主機和移動端與PC相比內存稀缺,我們都希望游戲能夠更加穩定,而不能有效的管理內存,此時大量的內存碎片是致命的。
內 存碎片的意思是內存被分成一個一個的小塊而不是整個大塊,所有內存小塊的大小可能很大但並不能使用,比如你想分配16byte的內存,此時如果有 20byte的空間就可以分配成功,但是如果這20byte是內存碎片,為兩個10byte就會分配失敗。所以,如果存在大量內存碎片,理論上有足夠的可 用內存,也會分配失敗。
很多游戲公司的游戲都會進行浸泡測試,讓一個游戲跑好幾天,查看是否崩潰來檢測內存泄露等等,因為內存碎片產生毀滅性的結果是一個緩慢的過程。
內存池的原理就是預先分配一大塊內存,生成滿需要經常用的對象,然后直到不使用再全部釋,可以把內存池看做是一堆可重用對象的集合。可以一定程度上避免產生大量內存碎片。
創 建內存池時我們生成指定數量的所有對象,然后把這些對象做標記區分來是否正在使用,所以當我們想要一個對象時只需要從池中獲取一個“未使用”標記的對象就 可以,再把它標記為“使用中”,用完了再標記回“未使用”即可。就像一個租借處,需要的時候借出去,用完了再還回來。用這種方法來重用對象。注意:使用時 切記要初始化對象,對象池不需要了要立即釋放。
對象池經常用在粒子系統生成粒子,和子彈,還有生成敵人等等,或者是需要播放的聲音。
當你需要:
1. 頻繁的創建和銷毀一種對象
2. 需要空間大小差不多的對象
3. 可能產生內存碎片
4. 可重用,而且創建和銷毀都很消耗的對象
請毫不猶豫的使用對象池。
對 象池也有一些缺點,可能開始生成很多對象但是大多用不上,浪費了大量內存,所以需要根據情況控制好開始生成對象的數量,或者創建第二個池,接下來會講動態 擴充對象池,會解決這個問題。另一種可能是生成的不夠用,悲劇,一種解決辦法是強行“不使用“一個或幾個對象來拆東牆補西牆,最好拆掉的不會被玩家發現, 還有一種就是同上接下來會講動態擴充對象池會解決這個問題。一個很小的對象池也可能並無卵用,還是會產生內存碎片。而且在初始化時一瞬間分配大量內存,可 能產生問題。
我們需要把生成的對象儲存在池中,來取出增加回收,有下面幾種可以選擇
1.數組,此處不是ArrayList就是普通的Object[]這樣的數組,在內存中是連續存儲的,索引速度非常快,使用起來比較。但是這種數組不能動態擴充,也就是生成對象的數量是不變的,不小心超出了這個范圍還會產造成數據溢出,而且只能存儲一種類型的對象。
2.ArrayList,動態數組,可以動態擴充,也可以存儲不同類型對象,但是在操作不同類型數據時需要裝箱拆箱(要做一個強制轉換),帶來性能耗損,並且不是類型安全。
3.List<T>,泛型,可動態擴充,但是不能存儲不同類型數據,需要制定存儲數據類型T。安全類型,不存在裝箱拆箱。
除此三種基本的之外還可以用堆,棧,哈希,Dictionary,如果需要根據key來查找具體的對象可以用哈希或Dictionary。博主下面代碼用List<T>來示范。
簡單的生成敵人的代碼
首先看看敵人類:
using UnityEngine; using System.Collections; public class Enemy : MonoBehaviour { public AnimationClip hurtAnimation; public AudioClip hertSound; public AudioClip dieSound; AudioSource audiosouce; int Max_HP = 3; int Now_HP = 3; bool isUsed = false; float moveSpeed = 0; float act = 0; // Use this for initialization public void init(bool _isUsed, float _moveSpeed, float _act, Vector3 _scale, Vector3 _pos, Quaternion _rot,int _Max_HP) { isUsed = _isUsed; moveSpeed = _moveSpeed; act = _act; this.transform.localScale = _scale; this.transform.position = _pos; this.transform.rotation = _rot; Max_HP = _Max_HP; Now_HP = _Max_HP; } public bool getUsed() { return isUsed; } void hert() { …受傷處理… } void Die() { audiosouce.PlayOneShot(dieSound); isUsed = false; this.GetComponent<Rigidbody>().useGravity = false; this.transform.position = Vector3.zero; // Destroy(this.gameObject);不需要destroy對象了 } void Start () { audiosouce = this.GetComponent<AudioSource>(); } void OnTriggerEnter(Collider other) { …hert().. } // Update is called once per frame void Update () { …邏輯….. } }
init函數負責初始化敵人,每次從池中生成都需要初始化,最開始初始化isUsed標記為false代表未使用然后再在池中調用init()賦參,getUsed方法是池獲取對象狀態的途徑。
一個簡單的生成敵人的對象池:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class EnemyPool : MonoBehaviour { public GameObject Hero; public GameObject perfab; List<GameObject> enemy = new List<GameObject>(); int Max_Amount = 10; // Use this for initialization void Start() { InvokeRepeating("setEnemy", 1, 10); for (int i = 0; i < Max_Amount; i++) { enemy.Add(Instantiate(perfab, Vector3.left*i*2, Quaternion.identity) as GameObject); } } void setEnemy() { for (int i = 0; i < Max_Amount; i++) { if (!enemy[i].GetComponent<Enemy>().getUsed()) { enemy[i].GetComponent<Enemy>().init(true,2,enemy[i].transform.localScale,_pos,Quaternion.identity, 3); enemy[i].GetComponent<Rigidbody>().useGravity = true; return; } } print("enemy all busy! create new!"); addEnemy(); } void addEnemy() { enemy.Add(Instantiate(perfab, Vector3.zero, Quaternion.identity) as GameObject); ++Max_Amount; } }
此處博主設定每10秒鍾生成一個敵人,可以自動擴充對象池。
setEnemy()為生成一個敵人,在 setEnemy()方法中我們遍歷所有敵人對象來尋找未使用的敵人,再標記為標為使用,再初始化敵人屬性,就成功“生成“了一個敵人。對於遍歷所產生的 消耗有一種辦法,把分為使用和未使用兩個表,create時就從未使用的表中獲取,再放入使用中的表中。
addEnemy()方法為擴充一個敵人,使用List<T>的add方法。
實現結果:
博主近期渲染:最近用unity5弄的一些渲染
---- by wolf96