Godot C#实现unity中的对象池功能

一、为什么需要对象池?——从Unity到Godot的共性需求

在游戏开发中,对象池技术通过复用已创建对象,显著降低高频创建/销毁操作带来的性能损耗。无论是Unity还是Godot,在以下场景都需对象池:

  • ​弹幕射击游戏​​:每秒数十发的子弹生成
  • ​开放世界NPC管理​​:动态加载的AI角色
  • ​特效系统​​:频繁出现的爆炸、粒子效果

二、Godot C#基础对象池实现 

1. 实现对象池代码
using Godot;
using System.Collections.Generic;

public partial class ObjectPool : Node
{
    private static ObjectPool _instance;
    public static ObjectPool Instance => _instance;

    private Dictionary> _pools = new Dictionary>();
    private Dictionary _prefabs = new Dictionary();
	[Export] PackedScene bulletPrefab;

    public override void _Ready()
    {
        _instance = this; // Godot单例需通过Autoload加载
		InitializePool("Bullet", bulletPrefab, 40);
    }

    // 初始化对象池
    public void InitializePool(string poolKey, PackedScene prefab, int initialSize)
    {
        if (!_pools.ContainsKey(poolKey))
        {
            var queue = new Queue();
            for (int i = 0; i < initialSize; i++)
            {
                Node obj = prefab.Instantiate();
                obj.ProcessMode = Node.ProcessModeEnum.Disabled; // 禁用节点逻辑
                queue.Enqueue(obj);
            }
            _pools.Add(poolKey, queue);
            _prefabs.Add(poolKey, prefab);
        }
    }

    // 获取对象
    public Node Spawn(string poolKey)
    {
        if (_pools.TryGetValue(poolKey, out Queue pool))
        {
            Node3D obj = (Node3D)(pool.Count > 0 ? pool.Dequeue() : CreateNewObject(poolKey));
            obj.ProcessMode = Node.ProcessModeEnum.Inherit; // 启用节点逻辑
			// obj.PhysicsInterpolationMode = PhysicsInterpolationModeEnum.Inherit;
            return obj;
        }
        GD.PrintErr($"Pool {poolKey} not initialized!");
        return null;
    }

    // 回收对象
    public void Despawn(string poolKey, Node obj)
    {
        if (_pools.ContainsKey(poolKey))
        {
            obj.ProcessMode = Node.ProcessModeEnum.Disabled;
			// obj.PhysicsInterpolationMode = PhysicsInterpolationModeEnum.Off;
            _pools[poolKey].Enqueue(obj);
        }
    }

    private Node CreateNewObject(string poolKey)
    {
        if (_prefabs.TryGetValue(poolKey, out PackedScene prefab))
        {
            return prefab.Instantiate();
        }
        return null;
    }
}
 2. 可回收对象基类
public partial class Bullet : RigidBody3D
{
	[Export] public string PoolKey = "Bullet"; // 在Godot编辑器中指定所属对象池
	public void ReturnToPool()
	{
		GetTree().Root.RemoveChild(this);
		ObjectPool.Instance.Despawn(PoolKey, this);
	}
}

三、使用流程

1. ​​初始化对象池
public partial class ObjectPool : Node
{
    [Export] PackedScene bulletPrefab;
    // 游戏启动时初始化
    public override void _Ready()
    {
        _instance = this; // Godot单例需通过Autoload加载
        InitializePool("Bullet", bulletPrefab, 40);
    }

}
2. 生成对象
public partial class Fight : Node
{
	[Export] PackedScene bulletPack;
    public override void _Ready()
    {
		// 当然你也可以在这里初始化
		ObjectPool.Instance.InitializePool("Bullet", bulletPack, 40);
    }
	public void Fire()
	{
		// 原有的实例化方法
		// Bullet bullet = bulletPack.Instantiate();
		Bullet bullet = (Bullet)ObjectPool.Instance.Spawn("Bullet");
		// AddChild(bullet); // 需要手动添加至场景树
		GetTree().Root.AddChild(bullet);
	}
}
3. ​​回收对象
public partial class Bullet : RigidBody3D
{
	// 在PoolableObject中触发回收(如碰撞检测后)
	protected virtual void OnBodyEntered(Node3D body)
	{
		CallDeferred("ReturnToPool");
	}
}

实际应用效果测试

https://www.bilibili.com/video/BV1DnZtYLEFU/?share_source=copy_web&vd_source=c3f882a4384ffe1e99cabd9e8780b610

四、进阶优化方案(AI提供)

我对目前的对象池并不满意,正在探索其他优化更加显著的方案,一下内容全是AI生成,并无实际测试

1. 优化版对象池实现(Stack方案)

public partial class ObjectPool : Node
{
    // 改用Stack存储可用对象
    private Dictionary> _pools = new Dictionary>();
    private Dictionary _prefabs = new Dictionary>();
    private Dictionary> _activeObjects = new Dictionary>();

    public void InitializePool(string poolKey, PackedScene prefab, int initialSize)
    {
        if (!_pools.ContainsKey(poolKey))
        {
            var stack = new Stack(initialSize); // 预分配容量
            for (int i = 0; i < initialSize; i++)
            {
                Node obj = prefab.Instantiate();
                obj.ProcessMode = Node.ProcessModeEnum.Disabled;
                stack.Push(obj);
            }
            _pools.Add(poolKey, stack);
            _prefabs.Add(poolKey, prefab);
            _activeObjects.Add(poolKey, new List());
        }
    }

    public Node Spawn(string poolKey, Vector2 position)
    {
        if (_pools.TryGetValue(poolKey, out Stack pool))
        {
            Node obj = pool.Count > 0 ? pool.Pop() : CreateNewObject(poolKey);
            
            // 自动添加至场景树
            if (obj.GetParent() == null) 
                AddChild(obj);
            else
                obj.GetParent().MoveChild(obj, -1); // 提升渲染优先级

            obj.Position = position;
            obj.ProcessMode = Node.ProcessModeEnum.Inherit;
            _activeObjects[poolKey].Add(obj);
            return obj;
        }
        GD.PrintErr($"Pool {poolKey} not initialized!");
        return null;
    }

    public void Despawn(string poolKey, Node obj)
    {
        if (_pools.ContainsKey(poolKey))
        {
            obj.ProcessMode = Node.ProcessModeEnum.Disabled;
            _pools[poolKey].Push(obj);
            _activeObjects[poolKey].Remove(obj);
            
            // 优化内存:当空闲对象超过上限时自动释放
            if (_pools[poolKey].Count > _prefabs[poolKey].GetMeta("MaxPoolSize", 50))
            {
                Node excessObj = _pools[poolKey].Pop();
                excessObj.QueueFree();
            }
        }
    }
}

为什么Stack更适合对象池?

  • ​LIFO特性​​:最后回收的对象最可能仍在CPU缓存中,复用时可减少Cache Miss
  • ​内存连续性​​:Stack的连续内存访问模式比Queue更适合现代CPU架构

 2. 分代混合索引池(推荐方案)

public class AdvancedObjectPool
{
    // 分代管理 + 索引查找
    private struct PoolSegment
    {
        public Stack HotBuffer;   // 高频区(L1缓存友好)
        public Queue WarmQueue;   // 中频区
        public List ColdStorage;  // 低频区(可能被GC)
    }

    private Dictionary _pools = new();
    private Dictionary _objectMap = new();
}
性能优势
  • ​缓存分级​​:HotBuffer使用Stack,命中率90%+
  • ​内存压缩​​:ColdStorage定期清理,减少内存占用
  • ​对象追踪​​:通过_objectMap实现O(1)复杂度回收

3. 内存池 + 对象池双缓冲方案 

public class MemoryPool where T : Node, new()
{
    private T[] _memoryBlock;  // 连续内存块
    private int[] _freeIndexes; // 空闲索引栈
    private int _pointer;

    public void Initialize(int capacity)
    {
        _memoryBlock = new T[capacity];
        _freeIndexes = new int[capacity];
        
        // 预分配内存并初始化
        for (int i = 0; i < capacity; i++)
        {
            _memoryBlock[i] = new T();
            _freeIndexes[i] = i;
        }
        _pointer = capacity - 1;
    }

    public T Spawn()
    {
        if (_pointer >= 0)
        {
            int index = _freeIndexes[_pointer--];
            return _memoryBlock[index];
        }
        // 动态扩容策略
        return ExpandPool().Spawn();
    }
}

4. 惰性分块池(Lazy Chunked Pool)

public class ChunkedPool
{
    private List _chunks = new();  // 内存块集合
    private Stack _freeSlots = new(); // 全局空闲槽位
    
    public Node Spawn()
    {
        if (_freeSlots.Count == 0)
        {
            AddNewChunk();
        }
        
        int index = _freeSlots.Pop();
        (int chunkIdx, int slotIdx) = DecodeIndex(index);
        return _chunks[chunkIdx][slotIdx];
    }

    private void AddNewChunk()
    {
        Node[] chunk = new Node[256]; // 每块256对象
        for (int i = 0; i < 256; i++)
        {
            chunk[i] = CreateObject();
            _freeSlots.Push(EncodeIndex(_chunks.Count, i));
        }
        _chunks.Add(chunk);
    }
}

你可能感兴趣的:(godot,c#)