使用Unity开发游戏使用适当的内存管理非常重要,如果你想制作流畅的游戏在手机上面运行;根据运行的平台和制作的游戏类型不同,对于尽可能的避免开辟不需要的堆内存非常的重要;最有效的途径的使用内存池,代码示例
public class Foo
{
public class Factory : PlaceholderFactory<Foo>
{
}
}
public class Bar
{
readonly Foo.Factory _fooFactory;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Factory fooFactory)
{
_fooFactory = fooFactory;
}
public void AddFoo()
{
_foos.Add(_fooFactory.Create());
}
public void RemoveFoo()
{
_foos.RemoveAt(0);
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindFactory<Foo, Foo.Factory>();
}
}
这里,每次我们调用Bar.AddFoo将会开辟新的堆内存;每次我们调用Bar.RemoveFoo,Bar类将会从Foo的实例里面停止引用,因此Foo的实例内存标记为可以垃圾回收;如果这样发生的次数过多,最终垃圾回收器将变成负担并且你的游戏由于
spike(急剧上升)过高导致卡顿,使用memory pools来修复急剧上升的开销替代
public class Foo
{
public class Pool : MemoryPool<Foo>
{
}
}
public class Bar
{
readonly Foo.Pool _fooPool;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Pool fooPool)
{
_fooPool = fooPool;
}
public void AddFoo()
{
_foos.Add(_fooPool.Spawn());
}
public void RemoveFoo()
{
var foo = _foos[0];
_fooPool.Despawn(foo);
_foos.Remove(foo);
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindMemoryPool<Foo, Foo.Pool>();
}
}
正如你看到的这样,内存池和工厂非常相似,除了部分的术语一点不同之后,不像工厂你需要返回实例给池子相比工厂的
仅仅是停止引用实例
使用了这种新的实现方式,将会在调用AddFoo()初始化的时候分配堆内存,但你调用RemoveFoo()然后再AddFoo(),池子
序列将重用之前的实例并因此节省你的堆内存开销
这样更好,我们想避免因为初始化导致内存的急剧上升;解决方法是再游戏启动的时候初始化好所有的内存开销
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindMemoryPool<Foo, Foo.Pool>().WithInitialSize(10);
}
}
当我们使用WithInitialSize语句绑定语法到我的池子,10个实例化的Foo将会在启动的时候立即创建作为池子的种子
内存池的语法大多数和工厂一样,只有部分新的绑定方法,类如 WithInitialSize 和 ExpandBy;同样,不像BindFactory,
没有必要给工厂指定参数,BindMemoryPool生成参数
和工厂一样,推荐使用内部公开类命名为Pool
public class Foo
{
public class Pool : MemoryPool<Foo>
{
}
}
通过添加生成参数来设置参数
public class Foo
{
public class Pool : MemoryPool<string, int, Foo>
{
}
}
全部的绑定语法格式
Container.BindMemoryPool<ObjectType, MemoryPoolType>()
.With(InitialSize|FixedSize)
.WithMaxSize(MaxSize)
.ExpandBy(OneAtATime|Doubling)()
.WithFactoryArguments(Factory Arguments)
.To<ResultType>()
.WithId(Identifier)
.FromConstructionMethod()
.AsScope()
.WithArguments(Arguments)
.OnInstantiated(InstantiatedCallback)
.When(Condition)
.CopyIntoAllSubContainers()
.NonLazy();
字段说明:
我们要注意使用内存池替代工厂必须确保完全重置了指定的实例;这个是非常有必要的,有可能上一个状态来之之前的数 据,重新实例化之后还保留了原来的数据,内存池的实现方法有下列这些
public class Foo
{
public class Pool : MemoryPool<Foo>
{
protected override void OnCreated(Foo item)
{
// Called immediately after the item is first added to the pool
}
protected override void OnDestroyed(Foo item)
{
// Called immediately after the item is removed from the pool without also being spawned
// This occurs when the pool is shrunk either by using WithMaxSize or by explicitly shrinking the pool by calling the `ShrinkBy` / `Resize methods
}
protected override void OnSpawned(Foo item)
{
// Called immediately after the item is removed from the pool
}
protected override void OnDespawned(Foo item)
{
// Called immediately after the item is returned to the pool
}
protected override void Reinitialize(Foo foo)
{
// Similar to OnSpawned
// Called immediately after the item is removed from the pool
// This method will also contain any parameters that are passed along
// to the memory pool from the spawning code
}
}
}
大多数情况下,你大概只要实现Reinitialize方法;代码示例
public class Foo
{
Vector3 _position = Vector3.zero;
public void Move(Vector3 delta)
{
_position += delta;
}
public class Pool : MemoryPool<Foo>
{
protected override void Reinitialize(Foo foo)
{
foo._position = Vector3.zero;
}
}
}
注意我们的池子可以自由访问Foo的私有变量,因为池子是一个内部类;或者,我们避免在Foo和Foo.Pool里面重复出现
public class Foo
{
Vector3 _position;
public Foo()
{
Reset();
}
public void Move(Vector3 delta)
{
_position += delta;
}
void Reset()
{
_position = Vector3.zero;
}
public class Pool : MemoryPool<Foo>
{
protected override void Reinitialize(Foo foo)
{
foo.Reset();
}
}
}
和工厂一样,你可以在运行时生成新的实例传递参数,不同的是,替代参数注入到类里面,通过Reinitialize方法
public class Foo
{
Vector3 _position;
Vector3 _velocity;
public Foo()
{
Reset(Vector3.zero);
}
public void Tick()
{
_position += _velocity * Time.deltaTime;
}
void Reset(Vector3 velocity)
{
_position = Vector3.zero;
_velocity = Vector3.zero;
}
public class Pool : MemoryPool<Vector3, Foo>
{
protected override void Reinitialize(Vector3 velocity, Foo foo)
{
foo.Reset(velocity);
}
}
}
public class Bar
{
readonly Foo.Pool _fooPool;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Pool fooPool)
{
_fooPool = fooPool;
}
public void AddFoo()
{
float maxSpeed = 10.0f;
float minSpeed = 1.0f;
_foos.Add(_fooPool.Spawn(
Random.onUnitSphere * Random.Range(minSpeed, maxSpeed)));
}
public void RemoveFoo()
{
var foo = _foos[0];
_fooPool.Despawn(foo);
_foos.Remove(foo);
}
}
上面提供的方法可以大致运行起来,但有以下不足:
我们可以通过使用PlaceholderFactory和Dispose Pattern来解决这些问题;任何代码可以调用工厂的创建方法和非常内存池
对象一样并且调用Dispose自动返回对象到池子里面
public class Foo : IPoolable<IMemoryPool>, IDisposable
{
IMemoryPool _pool;
public void Dispose()
{
_pool.Despawn(this);
}
public void OnDespawned()
{
_pool = null;
}
public void OnSpawned(IMemoryPool pool)
{
_pool = pool;
}
public class Factory : PlaceholderFactory<Foo>
{
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.BindFactory<Foo, Foo.Factory>().FromPoolableMemoryPool(x => x.WithInitialSize(2));
}
}
为了完成这个,我们使用了PlaceholderFactory内部派生类和非池子对象一样,实现了IPoolable< IMemoryPool>接口;
这个将会要求我们定义OnSpawned和OnDespawned方法来处理之前我们Reset逻辑;
我们也可以实现IDisposable来返回我们自己指定池子无论Dispose是否调用
当我们绑定了工厂,我们可以使用FromPoolableMemoryPool方法来配置池子,初始化种子值,最大数量,
拓展方法来构建对象;
注意如果你遇到了IL2CPP AOT问题,你应该使得内存池类更明确
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.BindFactory<Foo, Foo.Factory>().FromPoolableMemoryPool<Foo, FooPool>(x => x.WithInitialSize(2));
}
public class FooPool : PoolableMemoryPool<IMemoryPool, Foo>
{
}
}
发生这样的问题因为有时候IL2CPP不会自动包含PoolableMemoryPool
游戏物体的内存池和普通的内存池差不多,示例:
public class Foo : MonoBehaviour
{
Vector3 _velocity;
[Inject]
public void Construct()
{
Reset(Vector3.zero);
}
public void Update()
{
transform.position += _velocity * Time.deltaTime;
}
void Reset(Vector3 velocity)
{
transform.position = Vector3.zero;
_velocity = velocity;
}
public class Pool : MonoMemoryPool<Vector3, Foo>
{
protected override void Reinitialize(Vector3 velocity, Foo foo)
{
foo.Reset(velocity);
}
}
}
public class Bar
{
readonly Foo.Pool _fooPool;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Pool fooPool)
{
_fooPool = fooPool;
}
public void AddFoo()
{
float maxSpeed = 10.0f;
float minSpeed = 1.0f;
_foos.Add(_fooPool.Spawn(
Random.onUnitSphere * Random.Range(minSpeed, maxSpeed)));
}
public void RemoveFoo()
{
var foo = _foos[0];
_fooPool.Despawn(foo);
_foos.Remove(foo);
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public GameObject FooPrefab;
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindMemoryPool<Foo, Foo.Pool>()
.WithInitialSize(2)
.FromComponentInNewPrefab(FooPrefab)
.UnderTransformGroup("Foos");
}
}
主要的不同是Foo.Pool现在从MonoMemoryPool派生而不是MemoryPool;MonoMemoryPools是一个帮助类自动enable和
disable游戏物体当我们added/removed从池子里面,MonoMemoryPool是实现代码:
public abstract class MonoMemoryPool<TParam1, TValue> : MemoryPool<TParam1, TValue>
where TValue : Component
{
Transform _originalParent;
protected override void OnCreated(TValue item)
{
item.gameObject.SetActive(false);
// Record the original parent which will be set to whatever is used in the UnderTransform method
_originalParent = item.transform.parent;
}
protected override void OnDestroyed(TValue item)
{
GameObject.Destroy(item.gameObject);
}
protected override void OnSpawned(TValue item)
{
item.gameObject.SetActive(true);
}
protected override void OnDespawned(TValue item)
{
item.gameObject.SetActive(false);
if (item.transform.parent != _originalParent)
{
item.transform.SetParent(_originalParent, false);
}
}
}
因此,如果你重写了MonoMemoryPool之中的任意一个方法,你得确保父类已经被调用base version;
注意为了确保池子的正常逻辑,MonoBehaviour应该在Prefab的跟节点上,其他情况只有节点和子节点会被disable
你也可以使用Dispose模式,和non-MonoBehaviour的做法差不多,代码:
public class Foo : MonoBehaviour, IPoolable<Vector3, IMemoryPool>, IDisposable
{
Vector3 _velocity;
IMemoryPool _pool;
public void Dispose()
{
_pool.Despawn(this);
}
public void Update()
{
transform.position += _velocity * Time.deltaTime;
}
public void OnDespawned()
{
_pool = null;
_velocity = Vector3.zero;
}
public void OnSpawned(Vector3 velocity, IMemoryPool pool)
{
transform.position = Vector3.zero;
_pool = pool;
_velocity = velocity;
}
public class Factory : PlaceholderFactory<Vector3, Foo>
{
}
}
public class Bar
{
readonly Foo.Factory _fooFactory;
readonly List<Foo> _foos = new List<Foo>();
public Bar(Foo.Factory fooFactory)
{
_fooFactory = fooFactory;
}
public void AddFoo()
{
float maxSpeed = 10.0f;
float minSpeed = 1.0f;
var foo = _fooFactory.Create(
Random.onUnitSphere * Random.Range(minSpeed, maxSpeed));
foo.transform.SetParent(null);
_foos.Add(foo);
}
public void RemoveFoo()
{
if (_foos.Any())
{
var foo = _foos[0];
foo.Dispose();
_foos.Remove(foo);
}
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public GameObject FooPrefab;
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
Container.BindFactory<Vector3, Foo, Foo.Factory>().FromMonoPoolableMemoryPool<Foo>(
x => x.WithInitialSize(2).FromComponentInNewPrefab(FooPrefab).UnderTransformGroup("FooPool"));
}
}
和上面的示例不一样,我们从PlaceholderFactory派生,实现了IDisposable接口,使用了FromMonoPoolableMemoryPool替代了FromPoolableMemoryPool,这种情况也要注意AOT错误
另外i一种可能使用内存池是对安装器不进行改变,存储在类里面的静态内存池StaticMemoryPool,示例:
public class Foo
{
public static readonly StaticMemoryPool<Foo> Pool =
new StaticMemoryPool<Foo>(OnSpawned, OnDespawned);
static void OnSpawned(Foo that)
{
// Initialize
}
static void OnDespawned(Foo that)
{
// Reset
}
}
public class PoolExample : MonoBehaviour
{
public void Update()
{
var foo = Foo.Pool.Spawn();
// Use foo
Foo.Pool.Despawn(foo);
}
}
在这种情况下,内存池直接作为类里面静态成员变量来访问;这种情况在对象不需要引用的时候有帮助,或者你不想总是到处安装内存池;然而,和正常的内存池不一样的事情,在池子里面的对象将会一直留在内存里面即使切换了场景,池子的清除操作通过手动调用Foo.Pool.Clear
跟上面的一样,你也可以使用Dispose一样的despawn对象,示例:
public class Foo : IDisposable
{
public static readonly StaticMemoryPool<Foo> Pool =
new StaticMemoryPool<Foo>(OnSpawned, OnDespawned);
public void Dispose()
{
Pool.Despawn(this);
}
static void OnSpawned(Foo that)
{
// Initialize
}
static void OnDespawned(Foo that)
{
// Reset
}
}
public class PoolExample : MonoBehaviour
{
public void Update()
{
var foo = Foo.Pool.Spawn();
// Use foo
foo.Dispose();
}
}
你也可以引入参数到你的静态池子里面,使用StaticMemoryPool的生成参数,和正常内存池相似:
public class Foo : IDisposable
{
public static readonly StaticMemoryPool<string, Foo> Pool =
new StaticMemoryPool<string, Foo>(OnSpawned, OnDespawned);
public string Value
{
get; private set;
}
public void Dispose()
{
Pool.Despawn(this);
}
static void OnSpawned(string value, Foo that)
{
that.Value = value;
}
static void OnDespawned(Foo that)
{
that.Value = null;
}
}
同样,和正常的内存池一样,你可以使用IPoolable接口来结合PoolableStaticMemoryPool使用,避免样板代码和使用实例方法替代静态方法:
public class Foo : IPoolable<string>, IDisposable
{
public static readonly PoolableStaticMemoryPool<string, Foo> Pool =
new PoolableStaticMemoryPool<string, Foo>();
public string Data
{
get; private set;
}
public void Dispose()
{
Pool.Despawn(this);
}
public void OnSpawned(string data)
{
Data = data;
}
public void OnDespawned()
{
Data = null;
}
}
在下面的代码中有一些弊端:
public class PoolExample : MonoBehaviour
{
public void Update()
{
var foo = Foo.Pool.Spawn();
// Use foo
foo.Dispose();
}
}
public class PoolExample : MonoBehaviour
{
public void Update()
{
var foo = Foo.Pool.Spawn();
try
{
// Use foo
}
finally
{
foo.Dispose();
}
}
}
等同代码,使用using语句
public class PoolExample : MonoBehaviour
{
public void Update()
{
using (var foo = Foo.Pool.Spawn())
{
// Use foo
}
}
}
这种情况下,保证Foo对象将会返回到池子里面,不管是否有异常发生或者方法过早退出;这也另外一个理由使用Dispose行为有益于构建内存池对象
静态内存池子对于通用数据结构比如列表或者容器非常有帮助;Zenject包含了一些你可能额外需要的标准内存池子;例如,你编写一个MonoBehaviour需要每帧迭代每个组件,你可能会这样做:
public class PoolExample : MonoBehaviour
{
public void Update()
{
var components = this.GetComponents(typeof(Component));
foreach (var component in components)
{
// Some logic
}
}
}
然而,如果你在一个场景里面运行这个脚本,打开性能分析器,你将看到48bytes左右每帧开辟;我们可以最大化减少内存的开销,使用ListPool类来替代:
public class PoolExample : MonoBehaviour
{
public void Update()
{
var components = ListPool<Component>.Instance.Spawn();
this.GetComponents(typeof(Component), components);
foreach (var component in components)
{
// Some logic
}
ListPool<Component>.Instance.Despawn(components);
}
}
Zenject还包含了DictionaryPool,HashSetPool,和ArrayPool类,使用方式差不多
Zenject也提供了DisposeBlock类,简单的IDisposable集合对象,当使用了DisposeBlock.Dispose,集合全部disposed;
这个对于使用using语法有帮助,当你需要通过同一个池子或多个池子开辟多个临时实例;例如,我们需要spawn多个临时
对象在我们的PoolExample,可以这样做:
public class PoolExample : MonoBehaviour
{
public void Update()
{
using (var foo = Foo.Pool.Spawn())
using (var bar = Bar.Pool.Spawn())
{
// Some logic
}
}
}
这些可以正常运行,我们需要spawn更多的对象的时候,没有拓展性;因此更好的替代是使用DisposeBlock类:
public class PoolExample : MonoBehaviour
{
public void Update()
{
using (var block = DisposeBlock.Spawn())
{
var foo = Foo.Pool.Spawn();
var bar = Bar.Pool.Spawn();
block.Add(foo);
block.Add(bar);
// Some logic
}
}
}
使用简化的版本,DisposeBlock.Spawn方法:
public class PoolExample : MonoBehaviour
{
public void Update()
{
using (var block = DisposeBlock.Spawn())
{
var foo = block.Spawn(Foo.Pool);
var bar = block.Spawn(Bar.Pool);
// Some logic
}
}
}
我们也可以使用DisposeBlock来改进我们的ListPool,这样就不需要手动调用Despawn:
public class PoolExample : MonoBehaviour
{
public void Update()
{
using (var block = DisposeBlock.Spawn())
{
var components = block.Spawn(ListPool<Component>.Instance);
this.GetComponents(typeof(Component), components);
foreach (var component in components)
{
// Some logic
}
}
}
}
由于spawning lists这样的通用,DisposeBlock为此包含了一个帮助方法,因此可以这样简化:
public class PoolExample : MonoBehaviour
{
public void Update()
{
using (var block = DisposeBlock.Spawn())
{
var components = block.SpawnList<Component>();
this.GetComponents(typeof(Component), components);
foreach (var component in components)
{
// Some logic
}
}
}
}
如果你宁愿不使用上面解释的布置代码块,我们也可以通过正常使用Reset方法避免样板代码,然后你可以设置通过使用
PoolableMemoryPool或者MonoPoolableMemoryPool,代码示例:
public class Foo : IPoolable<string>
{
public string Data
{
get; private set;
}
public void OnDespawned()
{
Data = null;
}
public void OnSpawned(string data)
{
Data = data;
}
public class Pool : PoolableMemoryPool<string, Foo>
{
}
}
PoolableMemoryPool的实现非常简单并且只是在IPoolable类调用了实例方法:
public class PoolableMemoryPool<TParam1, TValue> : MemoryPool<TParam1, TValue>
where TValue : IPoolable<TParam1>
{
protected override void OnDespawned(TValue item)
{
item.OnDespawned();
}
protected override void Reinitialize(TParam1 p1, TValue item)
{
item.OnSpawned(p1);
}
}
你也可以设置他方法OnSpawned和OnDespawned成私有,通过使用C#的特性“明确接口实现”,你只要允许通过IPoolable接口调用OnSpawned和OnDespawned方法
public class Foo : IPoolable<string>
{
public string Data
{
get; private set;
}
void IPoolable<string>.OnDespawned()
{
Data = null;
}
void IPoolable<string>.OnSpawned(string data)
{
Data = data;
}
public class Pool : PoolableMemoryPool<string, Foo>
{
}
}
和抽象工厂一样,有时候你可能想创建一个内存池返回一个接口,实体类型通过安装器来决定,代码示例:
public interface IFoo
{
}
public class Foo1 : IFoo
{
}
public class Foo2 : IFoo
{
}
public class FooPool : MemoryPool<IFoo>
{
}
public class Bar
{
readonly FooPool _fooPool;
readonly List<IFoo> _foos = new List<IFoo>();
public Bar(FooPool fooPool)
{
_fooPool = fooPool;
}
public void AddFoo()
{
_foos.Add(_fooPool.Spawn());
}
public void RemoveFoo()
{
var foo = _foos[0];
_fooPool.Despawn(foo);
_foos.Remove(foo);
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public bool Use1;
public override void InstallBindings()
{
Container.Bind<Bar>().AsSingle();
if (Use1)
{
Container.BindMemoryPool<IFoo, FooPool>().WithInitialSize(10).To<Foo1>();
}
else
{
Container.BindMemoryPool<IFoo, FooPool>().WithInitialSize(10).To<Foo2>();
}
}
}
我们也可能想添加Reset()方法到IFoo接口,在Reinitialize()里面调用
你可能好奇,如果你使用动态子容器和外观模式,什么是最好的路径确保子容器在池子里面;代码示例:
public class EnemyFacade : MonoBehaviour, IPoolable<IMemoryPool>, IDisposable
{
IMemoryPool _pool;
public void OnSpawned(IMemoryPool pool)
{
_pool = pool;
}
public void OnDespawned()
{
_pool = null;
}
public void Dispose()
{
_pool.Despawn(this);
}
public class Factory : PlaceholderFactory<EnemyFacade>
{
}
}
public class EnemyMoveHandler : MonoBehaviour
{
Settings _settings;
float _velocity;
[Inject]
public void Construct(Settings settings)
{
_settings = settings;
}
public void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
_velocity += Time.deltaTime * _settings.Acceleration;
}
transform.position += Vector3.forward * _velocity * Time.deltaTime;
}
[Serializable]
public class Settings
{
public float Acceleration;
}
}
public class Runner : ITickable
{
readonly EnemyFacade.Factory _enemyFactory;
EnemyFacade _enemy;
public Runner(EnemyFacade.Factory enemyFactory)
{
_enemyFactory = enemyFactory;
}
public void Tick()
{
if (Input.GetKeyDown(KeyCode.Space) && _enemy == null)
{
_enemy = _enemyFactory.Create();
}
if (Input.GetKeyDown(KeyCode.Escape) && _enemy != null)
{
_enemy.Dispose();
_enemy = null;
}
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public EnemyMoveHandler.Settings EnemyMoveHandlerSettings;
public GameObject EnemyPrefab;
public override void InstallBindings()
{
Container.BindInstance(EnemyMoveHandlerSettings);
Container.BindInterfacesTo<Runner>().AsSingle();
Container.BindFactory<EnemyFacade, EnemyFacade.Factory>()
.FromMonoPoolableMemoryPool<EnemyFacade>(b => b
.WithInitialSize(1)
.FromSubContainerResolve()
.ByNewPrefabMethod(EnemyPrefab, InstallEnemy)
.UnderTransformGroup("Enemies"));
}
void InstallEnemy(DiContainer subContainer)
{
subContainer.Bind<EnemyFacade>().FromNewComponentOnRoot().AsSingle();
subContainer.Bind<EnemyMoveHandler>().FromNewComponentOnRoot().AsSingle().NonLazy();
}
}
如果我们添加这些代码到项目里面,运行起来玩家可以按空格生产敌人实例,按ESC取消生产敌人;然后,一旦生产了,用户口语按上箭头给敌人施加加速度;
这个运行正常,有一个问题;spawn新敌人之后,添加加速度,despawn敌人,然后又是再次spawn,加速度一直没有改变;
但我们从池子里面spawn对象,我们总是想要设置到默认状态;随着项目的做大,类增多,子容器变大,都要重置
我们可以通过EnemyFacade的Reset方法修复这个,明确的在其他对象调用其他的Reset方法,但如果有更多的生成方式来做这些事情更好;替代这些,你可以添加内置的PoolableManager类到子容器里面并且在任何类需要的时候实现IPoolable接口,代码示例:
public class EnemyMoveHandler : MonoBehaviour, IPoolable
{
Settings _settings;
float _velocity;
[Inject]
public void Construct(Settings settings)
{
_settings = settings;
}
public void OnDespawned()
{
}
public void OnSpawned()
{
_velocity = 0;
transform.position = Vector3.zero;
}
public void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
_velocity += Time.deltaTime * _settings.Acceleration;
}
if (Input.GetKey(KeyCode.DownArrow))
{
_velocity -= Time.deltaTime * _settings.Acceleration;
}
transform.position += Vector3.forward * _velocity * Time.deltaTime;
}
[Serializable]
public class Settings
{
public float Acceleration;
}
}
public class EnemyFacade : MonoBehaviour, IPoolable<IMemoryPool>, IDisposable
{
[Inject]
PoolableManager _poolableManager;
IMemoryPool _pool;
public void OnSpawned(IMemoryPool pool)
{
_pool = pool;
_poolableManager.TriggerOnSpawned();
}
public void OnDespawned()
{
_pool = null;
_poolableManager.TriggerOnDespawned();
}
public void Dispose()
{
_pool.Despawn(this);
}
public class Factory : PlaceholderFactory<EnemyFacade>
{
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public EnemyMoveHandler.Settings EnemyMoveHandlerSettings;
public GameObject EnemyPrefab;
public override void InstallBindings()
{
Container.BindInstance(EnemyMoveHandlerSettings);
Container.BindInterfacesTo<Runner>().AsSingle();
Container.BindFactory<EnemyFacade, EnemyFacade.Factory>()
.FromMonoPoolableMemoryPool<EnemyFacade>(b => b
.WithInitialSize(2)
.FromSubContainerResolve()
.ByNewPrefabMethod(EnemyPrefab, InstallEnemy)
.UnderTransformGroup("Enemies"));
}
static void InstallEnemy(DiContainer subContainer)
{
subContainer.Bind<EnemyFacade>().FromNewComponentOnRoot().AsSingle();
subContainer.BindInterfacesAndSelfTo<EnemyMoveHandler>().FromNewComponentOnRoot().AsSingle().NonLazy();
subContainer.Bind<PoolableManager>().AsSingle();
}
}
注意以下事项:
应付复杂的情况包括自定义工厂,有可能想直接实例化内存池;在这种情况下,你必须确保提供IFactory<>派生类,用于创建新实例和所有的设置信息通过绑定语法正常提供:
public class BarFactory : IFactory<Bar>
{
public Bar Create()
{
...
[Custom creation logic]
...
}
}
var settings = new MemoryPoolSettings(
// Initial size
1,
// Max size
int.MaxValue,
PoolExpandMethods.Double);
var pool = _container.Instantiate<MemoryPool<Bar>>(
new object[] { settings, new MyBarFactory<Bar>() });
有时候会发生当我们使用内存池生产了对象当对象有可能没有返回到池子里面;发生这样如果Spawn方法掉用了单然后程序员忘记了添加配对的Despwan/Dispose方法;
这不总是一个问题,有时候甚至故意不回收;假设生产的对象不是MonoBehaviour,一旦对象没有使用了,将会通过C#的垃圾回收自动销毁,因此忘记Despwan在这样的情况下不会产生内存泄漏;弊端是当对象没有返回到池子里面,意味着池子下次需要生产更多的对象,弱微的影响性能;
同样当然,如果你的对象有重要的自定义dispose逻辑,将会错过,这会有问题;
为了发现这些错误,Zenject包含了一个可选的类,当激活的生产对象的场景关闭了,将会抛出异常;在SceneInstaller里面添加以下代码:
Container.BindInterfacesTo<PoolCleanupChecker>().AsSingle()
这样,你每次忘记调用Despawn方法,Unity PlayMode结束后你将会在控制台获得错误报告;当池子只有0个激活的item并且在LateDispose事件中,这个类检查每个符合条件的池子;
一旦这个在一些情况下故意安排,你可以指定池子禁止错误报告,通过给PoolCleanupChecker传递一个参数类型列表:
Container.BindInterfacesTo<PoolCleanupChecker>().AsSingle()
Container.BindInstance(
new List<Type>()
{
typeof(Foo.Pool),
typeof(Bar.Pool),
})
.WhenInjectedInto<PoolCleanupChecker>();
Zenject也包含了一个体验的编辑窗口,你可以监视场景中所有内存池的大小;通过
Window -> Zenject Pool Monitor
打开窗口:
如果你确保你使用的池子全部都是合理的,监视器非常有作用;它也可以帮助你在初始化的时候选择一个合理的大小,在
WithInitializeSize方法确保所有的开辟内存发生在loading场景,而不是在游戏里面;
同样,你可以使用Expand按钮和Clear按钮配合Unity的新能分析器,观察池子销毁分析总计使用了多少内存
Zenject Github地址
Zenject 中文社区,搭建中
讨论群:518658723
欢迎喜欢使用DI的朋友加入群,有自愿报名活动!
本人代码功底浅,大神看到就当路过,希望来点评!
呀哈哈,有能力的朋友可以扫下面的打赏二维码↓↓↓