using System.Collections.Generic;
using Godot;namespace GoDogKit
{/// <summary>/// A simple implementation of an object pool without security checks. /// </summary>public partial class ObjectPool : Node{/// <summary>/// The scene stored in the pool. /// </summary>[Export] public PackedScene Scene { get; set; }/// <summary>/// The initial size of the pool./// </summary>[Export]public int InitialSize{get => m_initialSize;set => m_initialSize = value < 0 ? 0 : value;}private int m_initialSize = 10;/// <summary>/// Emitted when a node is gotten from the pool. /// </summary>/// <param name="node"> The node that was gotten.</param>[Signal] public delegate void GottenEventHandler(Node node);/// <summary>/// Emitted when a node is instantiated inside the pool. /// </summary>/// <param name="node"> The node that was instantiated.</param>[Signal] public delegate void InstantiatedEventHandler(Node node);/// <summary>/// Emitted when a node is released back to the pool. /// </summary>/// <param name="node"> The node that was released.</param>[Signal] public delegate void ReleasedEventHandler(Node node);/// <summary>/// Emitted when a node is freed from the pool./// </summary>/// <param name="node"> The node that was freed.</param>[Signal] public delegate void FreedEventHandler(Node node);// The queue of nodes in the pool.private Queue<Node> queue;/// <summary>/// The number of nodes in the pool currently. /// </summary>public virtual int Size{get => queue.Count;}public override void _Ready(){queue = new Queue<Node>();// Auto initialize if initial size is greater than 0if (InitialSize > 0){for (int i = 0; i < InitialSize; i++){Node node = Scene.Instantiate();queue.Enqueue(node);}}}/// <summary>/// Get a node from the pool as Node, and add it to the scene tree root. /// If the pool is empty, a new node will be instantiated and added to the scene tree./// </summary> /// <returns> The node that was gotten. </returns>public virtual Node Get(){if (!queue.TryDequeue(out Node node)){node = Scene.Instantiate();EmitSignal(SignalName.Instantiated, node);}GetTree().Root.AddChild(node);EmitSignal(SignalName.Gotten, node);return node;}/// <summary>/// Get a node from the pool as specified Type, and add it to the scene tree root. ///If the pool is empty, a new node will be instantiated and added to the scene tree root./// </summary>/// <typeparam name="T"> Node type to get. </typeparam>/// <returns> The node that was gotten. </returns> public virtual T Get<T>() where T : Node{if (!queue.TryDequeue(out Node node)){node = Scene.Instantiate() as T;EmitSignal(SignalName.Instantiated, node);}GetTree().Root.AddChild(node);EmitSignal(SignalName.Gotten, node);return node as T;}/// <summary>/// Release a node back to the pool and remove it from the scene tree root./// Notice that there are no checks for whethet the node was creatd by the pool or not./// </summary>/// <param name="node"> The Node to release. </param>public virtual void Release(Node node){GetTree().Root.RemoveChild(node);queue.Enqueue(node);EmitSignal(SignalName.Released, node);}/// <summary>/// Free a node from the pool and remove it from the scene tree at the end of the frame./// Notice that there are no checks for whethet the node was creatd by the pool or not./// </summary>/// <param name="node"> The Node to free. </param>public virtual void Free(Node node){node.QueueFree();EmitSignal(SignalName.Freed, node);}}
}
这是一个及其简单的Godot对象池,而且还非常不安全。不过作为接触Godot的一些设计理念看来刚刚好。
这里边唯一值得注意的点应该就是AddChild和RemoveChild了,对象池技术的本质实际上基于对实例的管理,用的时候激活,不用的时候失活。但是在Godot尽然没有明确的Enable或Disable操作,比如Unity中的。
但是官方介绍了几种改变场景的策略和方式。其中一种就是这个对象池用的:AddChild和RemoveChild。这两种方式改变节点在场景树中的存在,但不改变它们在内存中的存在。
也就是说它们实际上在内存中存在,不过还没进入到场景树中,也就不会被处理,所以才类似于Unity中的Enable和Disable。PackedScene实例化出来的节点也是这个状态。
这个对象池最大的问题就是没有跟它所管理的有很好的耦合。这也就意味着我们要在它所管理的对象中至少设置好以下内容,否则会有很多奇奇怪怪的问题:
1.首先要建立对象与对象池的依赖。否则等到后面回收时,够不清楚到底是哪个池子里的对象就很麻烦了。
2.管理对象状态。该对象池仅仅负责存取对象,根本没有任何初始化对象状态的功能。打个比方,一颗子弹被从对象池拿出来后射了出去,但是回收它时没有进行任何设置操作,意味着它还在原来的位置或者具有原本的状态,第二次拿出来后依然保留。所以需要我们手动添加一些代码帮助对象池对对象的管理,比如拿到对象时做什么,回收对象时做什么。
3.设置回收时机。只拿不回的对象池根本没有用,所以要设置好被管理对象的回收时机。如上文说的子弹,在Godot中有个很好用的节点叫做Timer,用来计时的,我们可以设置时间一到就马上回收子弹,不过要注意Timer的设置以及子弹状态的设置。
值得一提的是,我们除了可以通过代码(比如C#事件:信号+=方法)的模式,还能通过在Editor直接建立信号链接的形式,这样可以直接不用写+=了。
综上所述,该对象池的缺点是:
1.缺乏安全检查。如判断对象是否属于自己。
2.缺乏与对象的耦合。需要多余代码构建完整存取流。
优点: 轻量,灵活。