游戏开发设计模式之对象池模式(unity3d 示例实现)

前篇:游戏开发设计模式之命令模式(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 

时间: 2024-10-14 16:50:10

游戏开发设计模式之对象池模式(unity3d 示例实现)的相关文章

设计模式之对象池模式

对象池模式 对象池模式, 或者称为对象池服务, 其意图为: 通过循环使用对象, 减少资源在初始化和释放时的昂贵损耗(这里的"昂贵"可能是时间效益(如性能), 也可能是空间效益(如并行处理), 在大多情况下, 指性能) 简单的说, 在需要时,从池中提取,不用时,放回池中,等待下一个请求. 典型的例子是连接池和线程池. 类图如下: 其中角色如下: ObjectPool 对象池角色: 提供对象池, 其中有两个公共方法, checkOut负责从池中提取对象, checkIn负责回收对象(很多时

游戏开发设计模式之原型模式 &amp; unity3d JSON的使用(unity3d 示例实现)

命令模式:游戏开发设计模式之命令模式(unity3d 示例实现) 对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现) 实现原型模式 原型模式带来的好处就是,想要构建生成任意独特对象的生成类,只需要一个生成类和一个原型即可.当我们有一个抽象的敌人Monster类就有很多继承它的各种各样的敌人,人类.动物.龙等等,如果我们想为每个敌人做一个生成器父类Spawner,也会有与monster对应数量的子类,也许就会这样: 这样就会产生类的数量变多,而且这些类的功能是重复的.开始的spa

《Unity3D》通过对象池模式,管理场景中的元素

池管理类有啥用? 在游戏场景中,我们有时候会需要复用一些游戏物体,比如常见的子弹.子弹碰撞类,某些情况下,怪物也可以使用池管理,UI部分比如:血条.文字等等 这些元素共同的特性是:存在固定生命周期,使用比较频繁,场景中大量使用. 所以,我们就通过池管理思路,在游戏初始化的时候,生成一个初始的池,存放我们要复用的元素, 当要用到时,从池中取出:生命周期结束,放回到池中. 代码 这个池的参数有两个:1池中存放的元素 2 池的初始容量(如果池不够了,则会按照这个容量进行扩展) 代码如下 using S

java/android 设计模式学习笔记(5)---对象池模式

这次要介绍一下对象池模式(Object Pool Pattern),这个模式为常见 23 种设计模式之外的设计模式,介绍的初衷主要是在平时的 android 开发中经常会看到,比如 ThreadPool 和 MessagePool 等. 在 java 中,所有对象的内存由虚拟机管理,所以在某些情况下,需要频繁创建一些生命周期很短使用完之后就可以立即销毁,但是数量很大的对象集合,那么此时 GC 的次数必然会增加,这时候为了减小系统 GC 的压力,对象池模式就很适用了.对象池模式也是创建型模式之一,

cocos2dx游戏开发——设计模式

一.二段设计模式 二段构建模式就是将内存空间的分配和初始化分开来完成,然后调用一个静态方法来返回这个对象. 调用Sprite::create()的时候内部先使用new来分配内存空间,然后调用init方法来初始化一些变量的设置.所以cocos2dx中的二段构建模式就是将new分配内存空间和init初始化内容分开来处理,而不是c++传统的做法在构造函数中初始化变量. Sprite* Sprite::create() { Sprite *sprite = new Sprite(); if (sprit

对象池模式

1.对象池技术并没有限制说只能创建一个对象,而且这种技术同样适用于创建固定数量的对象,然而,这种情况下,你就得面对如何共享对象池里的对象这种问题. 当创建多个对象会的代价会很大的时候,可以考虑使用对象池技术,目前已有的技术比如:线程池技术.数据库连接池技术 2.UML图(astah/jude)下载地址: 3.模拟一个数据库连接池进行实现: 实现的接口: 1 package com.xinye.test.pool; 2 /** 3 * 用户需要的实际的东西都实现这个接口 4 * @author x

C++设计模式 之 “对象性能” 模式:Singleton、Flyweight

“对象性能”模式 面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价.对于通常情况来讲,面向对象的成本大都可以忽略不计.但是某些情况,面向对象所带来的成本必须谨慎处理. 典型模式 # Singleton # Flyweight Part 1 单件模式(单例模式) 动机 #在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保他们的逻辑正确性.以及良好的效率. #如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例? #这应该是类设计者的责任,而不

C++设计模式 之 “对象创建”模式:Factory Method

part 0 “对象创建”模式 通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定.它是接口抽象之后的第一步工作. 典型模式 Factory Method Abstract Factory Prototype Builder Part 1 Factory Method 工厂方法 动机(Motivation) 在软件系统中,经常面临着创建对象的工作:由于需求的变化,需要创建的对象的具体类型经常变化. 如何应对这种变化?如何绕过常规的

JAVA模式 对象池 简要代码示例

package org.rui.util; import java.util.ArrayList; /** * 对象复用模式 * * @author PC * */ public class PoolManager { //连接池对象 public static class PoolItem { boolean inUse = false; Object item;//池数据 PoolItem(Object item) { this.item = item; } } //连接池集合 privat