Unity和C#:将多个事件附加到一个侦听器。有更有效的方法吗?

3
我正在使用C#和Unity创建一个棋盘游戏。 棋盘类似于网格(类似于国际象棋)。 板上的每个方块都负责自己的选择状态,动画和事件,并会发出事件(例如当选择时,它将发出OnSelect事件)来指示发生了什么。
此外,还有一个整体的BoardManager类,需要监视这些事件并对其做出响应。
据我所知,在BoardManager类中,我需要:
  1. 为每个Square类创建单独的引用,以便我可以监视它们输出的事件
  2. 订阅每个方块的每个事件到BoardManager中所需的回调函数
  3. 记得在销毁时取消订阅每个事件
我可以使用foreach循环来完成此操作,但感觉不太对。
我希望有一种方法,而不是创建50个对象引用和50个事件订阅,只需告诉它订阅特定对象的任何实例(例如Square的任何实例),以便我可以引用它那一次,并订阅它一次。这有意义吗?

3
为每个方格创建一个非静态事件不如创建一个静态事件,并将确切的方格作为参数传递给处理程序。 然后可以从任何方格调用事件,然后将其自身(或标识符)作为参数传递。 一个事件,一个订阅,您将不需要在任何时候取消订阅或重新订阅。 事实上,该事件甚至不必是静态的,您只需要让方格类可以访问它以便调用它。 - Jesse
1
@Jesse 的答案是一个简洁的解决方案,但我认为使用 foreach 循环分配事件也没有什么问题。 - Nigel
1个回答

3

正如评论中@Jesse和@Nigel Bess所提到的,静态事件和foreach都是不错而且有效的解决方案,它们在代码可读性、可用性和性能方面都有其优缺点。

单个事件

// Manager.cs
// You can use Singleton pattern or make OnSelect static

public Action<Cell> OnSelect;

void Start()
{
    OnSelect += OnSelectAction;
}

void OnDestroy()
{
    OnSelect -= OnSelectAction;
}

// Cell.cs
void Update()
{
    if(isSelected)
    {
       _manager.OnSelect(this);
    }

}

这种方法明显的优势在于清晰地分离了每个对象的责任。Cell 管理自己的状态, 你只需要在某些事件发生时接收一个回调函数即可。

然而,你需要确保 _manager 在这个上下文中存在,否则会影响可测试性和可扩展性,或者使用单例模式。

这种方法实现起来相当干净利落,但所有委托都有它们难以调试和管理潜在问题的缺点。

对于每一个 Cell 事件

// Manager.cs

void InitializeCell(Cell cell)
{
    cell.OnSelect += OnSelectAction;
}

void DestroyCell(Cell cell)
{
    // If an object would be destroyed via Unity's Destroy, this is not needed
    cell.OnSelect -= OnSelectAction;
}

// Cell.cs
public event Action<Cell> OnSelect;

void Start(){
    _manager.InitializeCell(this);
}

void OnDestroy(){
    _manager.DestroyCell(this);
}

void Update()
{ 
    if(isSelected && OnSelect != null)
    {
       OnSelect(this);
    }
}

这种方法有点混乱,因为你不仅需要假设 _manager 在此上下文中存在,而且还需要假设对于给定实例,OnSelect 已经在 manager 中被订阅了。

但是,这给了你一个选项,可以直接从 Manager 类停止接收任何选定实例的 OnSelect。这在第一种方法中是不可能的。在第一种方法中,你需要通过 if cell == foo 或通过 cell 类本身来处理输入。

如果给定的 Action 指针未存储在 CPU 缓存中,则此方法的速度可能会较慢。因此,其速度与单例方法相当,每个单元格比单例方法慢多达 100ns,但我认为这并不明显且无需担心。

使用 foreach 实现 foreach 方法的另一种方法。初始化速度没有区别,并减少了 CellManager 的依赖性。

// Manager.cs

void Start()
{
    foreach(var cell in Cells)
    {
        cell.OnSelect += OnSelectAction;
    }
}

// Cell.cs
public event Action<Cell> OnSelect;

void Update()
{
    if(isSelected && OnSelect != null)
    {
       OnSelect(this);
    }
}

在这种方法中,由于我们不知道何时会被销毁,单元格的生命周期管理可能变得复杂。 直接方法 如果我们假设通过Singleton或者OnSelectAction是静态的方式来访问Manager,那么也有一种直接方法可以实现。这种方法非常容易进行调试(相比于操作),但与第一种方法存在相同的问题。
// Manager.cs
// You can use Singleton pattern or make OnSelect static

void OnSelectAction(Cell cell)
{
   ...
}

// Cell.cs
void Update()
{
    if(isSelected)
    {
       _manager.OnSelectAction(this);
    }

}

这种方法执行速度最快,应用程序的内存占用也较低,因为您不需要存储大量数据。它也是最干净、最易于阅读和实现的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接