博主还年轻,难免会犯错误,尤其是设计模式,很有禅意游戏开发设计模式,需要大量的经验。 如有书写错误或遗漏,请指正。
原理:重用固定池中的对象来提高性能和内存使用率,而不是一一分配内存然后释放。
当需要创建大量重复对象并频繁使用这些对象时,应该考虑使用对象池,因为重复创建和销毁就是重复分配和释放内存的过程,很容易造成内存碎片。
与 PC 相比,游戏机和移动设备上的内存匮乏。 我们都希望游戏能够更加稳定,但是内存却无法得到有效的管理。 这时候大量的内存碎片是致命的。
内存碎片是指内存被一一分割成小块,而不是整个大块。 所有小内存块的大小可能很大但无法使用。 比如你要分配16bytes的内存,那么如果你有20bytes的空间,你就可以分配成功,但是如果这20个字节是内存碎片材质材料,那么两个10个字节的分配就会失败。 因此,如果存在大量内存碎片,理论上会有足够的可用内存,分配就会失败。
许多游戏公司对他们的游戏进行浸泡测试,让游戏运行几天,看看是否崩溃,以检测内存泄漏等,因为内存碎片是一个缓慢的过程,可能会产生毁灭性的结果。
内存池的原理是预先分配一大块内存,生成需要经常使用的对象,然后将其全部释放,直到不再使用为止。 内存池可以看作是可重用对象的集合。 它可以在一定程度上避免大量的内存碎片。
在创建内存池的时候,我们会生成指定数量的所有对象,然后对这些对象进行标记,以区分它们是否正在使用,所以当我们想要一个对象时,只需要从池中获取标记为“未使用”的对象即可,然后只需将其标记为“正在使用”,然后在使用完毕后将其标记为“未使用”。 这就像一个出租办公室。 需要时借出,用完后归还。 使用此方法可以重用对象。 注意:使用对象时记得初始化,不再需要对象池时立即释放。
对象池常用于粒子系统中,用来生成粒子、子弹、敌人等,或者需要播放的声音。
当您需要时:
1.频繁创建和销毁对象
2. 需要大致相同大小空间的对象
3、可能出现内存碎片
4. 可重用且创建和销毁成本高昂的对象
请毫不犹豫地使用对象池。
对象池也有一些缺点。 它可能开始生成很多对象,但其中大部分不会被使用,这会浪费大量内存。 因此,需要根据情况控制最初生成的对象数量,或者创建第二个池。 接下来我们要讲的是动态扩展对象池。 将解决这个问题。 另一种可能是世代不够游戏评测,这是一个悲剧。 一种解决办法是强行“不使用”一件或几件东西,拆东墙补西墙。 那些不会被玩家发现的最好拆掉。 另一种方式就是如上面我们讲的动态扩展对象池来解决这个问题。 非常小的对象池可能没有用,并且仍然可能导致内存碎片。 而且,初始化时瞬间分配大量内存可能会导致问题。
我们需要将生成的对象存储在池中,以便将它们取出并增加回收。 有多种选项可供选择:
1.数组,这里要么是ArrayList,要么是普通的Object[]数组。 它在内存中连续存储,索引速度很快,比较好用。 但这种数组不能动态扩展,即生成的对象数量保持不变。 如果一不小心超出了这个范围,就会导致数据溢出,而且只能存储一种类型的对象。
2、ArrayList,动态数组,可以动态扩展,也可以存储不同类型的对象。 但操作不同类型的数据时,需要装箱和拆箱(需要强制转换),带来性能损失,且类型不安全。
3.列表,通用,可以动态扩展,但不能存储不同类型的数据。 需要指定存储数据类型T。 安全型,无需装箱或拆箱。
除了这三个基本的之外,还可以使用堆、栈、哈希、字典。 如果需要根据key查找特定对象,可以使用hash或者Dictionary。 下面博主的代码使用List来演示。
生成敌人的简单代码
首先看一下敌人的类别:
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().useGravity = false;
this.transform.position = Vector3.zero;
// Destroy(this.gameObject);不需要destroy对象了
}
void Start () {
audiosouce = this.GetComponent();
}
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 enemy = new List();
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().getUsed())
{
enemy[i].GetComponent().init(true,2,enemy[i].transform.localScale,_pos,Quaternion.identity, 3);
enemy[i].GetComponent().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()方法中,我们遍历所有敌人对象,找到未使用的敌人,将其标记为已使用,然后初始化敌人属性,成功“生成”了一个敌人。 有一个办法可以处理遍历产生的消耗,就是分成两张表:used和unused。 创建时,从未使用的表中取出,放入已使用的表中。
addEnemy()方法是扩展一个敌人,使用List的add方法。
取得成果:
博主近期效果图:最近用unity5做的一些效果图
---- 作者:wolf96