C#游戏开发:玩家移动事件触发多类逻辑的有序执行设计模式问询
适配玩家移动事件有序执行的C#设计方案
嘿,这个场景我在开发2D沙盒类游戏时刚好遇到过!核心需求就是保证玩家移动事件触发后,RenderGrid先执行瓦片实例化,再让WorldManager处理区块加载,下面几个设计模式都能完美适配,咱们一个个说:
1. 带优先级的观察者模式
标准的观察者模式(比如C#内置的EventHandler)默认是无序执行订阅者的,但咱们可以给每个订阅者加个优先级标记,触发事件时按优先级排序执行,刚好满足你的顺序需求。
实现思路:
- 自定义一个带优先级的事件订阅容器,存储事件处理方法和对应的优先级值
- 给
RenderGrid设置高优先级(比如10),WorldManager设置低优先级(比如5) - 玩家移动事件触发时,先执行高优先级的
RenderGrid逻辑,再执行低优先级的WorldManager
代码示例:
// 自定义带优先级的事件系统 public class PriorityEvent<TArgs> { private SortedList<int, List<Action<TArgs>>> _subscribers = new SortedList<int, List<Action<TArgs>>>(); public void Subscribe(Action<TArgs> handler, int priority) { if (!_subscribers.ContainsKey(priority)) { _subscribers[priority] = new List<Action<TArgs>>(); } _subscribers[priority].Add(handler); } public void Unsubscribe(Action<TArgs> handler, int priority) { if (_subscribers.ContainsKey(priority)) { _subscribers[priority].Remove(handler); } } public void Invoke(TArgs args) { // 按优先级从高到低执行(SortedList默认升序,所以反向遍历) foreach (var pair in _subscribers.Reverse()) { foreach (var handler in pair.Value) { handler.Invoke(args); } } } } // 玩家移动事件参数(包含bottomLeft和topRight向量) public class PlayerMoveArgs { public Vector2 BottomLeft { get; set; } public Vector2 TopRight { get; set; } } // 玩家控制器类 public class PlayerController { public PriorityEvent<PlayerMoveArgs> OnPlayerMoved = new PriorityEvent<PlayerMoveArgs>(); private void OnPlayerPositionChanged() { // 计算当前视图的bottomLeft和topRight var moveArgs = new PlayerMoveArgs { BottomLeft = CalculateBottomLeft(), TopRight = CalculateTopRight() }; // 触发事件 OnPlayerMoved.Invoke(moveArgs); } // 省略计算方法... private Vector2 CalculateBottomLeft() => default; private Vector2 CalculateTopRight() => default; } // RenderGrid类订阅(高优先级) public class RenderGrid { public RenderGrid(PlayerController player) { // 订阅事件,优先级设为10(越高越先执行) player.OnPlayerMoved.Subscribe(HandlePlayerMoved, 10); } private void HandlePlayerMoved(PlayerMoveArgs args) { // 立即实例化新可见的游戏瓦片 InstantiateTiles(args.BottomLeft, args.TopRight); } private void InstantiateTiles(Vector2 bottomLeft, Vector2 topRight) { // 瓦片实例化逻辑... } } // WorldManager类订阅(低优先级) public class WorldManager { public WorldManager(PlayerController player) { // 订阅事件,优先级设为5 player.OnPlayerMoved.Subscribe(HandlePlayerMoved, 5); } private void HandlePlayerMoved(PlayerMoveArgs args) { // 检查并加载新区块 LoadChunks(args.BottomLeft, args.TopRight); } private void LoadChunks(Vector2 bottomLeft, Vector2 topRight) { // 区块加载逻辑... } }
2. 责任链模式
如果后续可能要增加更多事件处理环节(比如加个AudioManager处理移动音效),责任链模式会更灵活——把每个处理类做成链条上的节点,事件按链条顺序传递,先经过RenderGrid,再到WorldManager,后续加节点只需要插入链条即可。
实现思路:
- 定义抽象的事件处理基类,包含处理方法和后继节点引用
- 让
RenderGridHandler和WorldManagerHandler继承基类,实现各自的处理逻辑 - 链条初始化时,把
RenderGridHandler的后继设为WorldManagerHandler - 玩家移动事件触发时,从链条头部(
RenderGridHandler)开始执行,自动传递给后继
代码示例:
// 抽象处理者基类 public abstract class PlayerMoveHandler { protected PlayerMoveHandler _nextHandler; public void SetNext(PlayerMoveHandler nextHandler) { _nextHandler = nextHandler; } public abstract void Handle(PlayerMoveArgs args); } // RenderGrid处理者 public class RenderGridHandler : PlayerMoveHandler { private RenderGrid _renderGrid; public RenderGridHandler(RenderGrid renderGrid) { _renderGrid = renderGrid; } public override void Handle(PlayerMoveArgs args) { // 先执行RenderGrid的逻辑 _renderGrid.InstantiateTiles(args.BottomLeft, args.TopRight); // 传递给下一个处理者 _nextHandler?.Handle(args); } } // WorldManager处理者 public class WorldManagerHandler : PlayerMoveHandler { private WorldManager _worldManager; public WorldManagerHandler(WorldManager worldManager) { _worldManager = worldManager; } public override void Handle(PlayerMoveArgs args) { // 执行WorldManager的逻辑 _worldManager.LoadChunks(args.BottomLeft, args.TopRight); // 如果有后续处理者,继续传递(这里可以省略,因为是最后一个) _nextHandler?.Handle(args); } } // 玩家控制器类调整 public class PlayerController { private PlayerMoveHandler _handlerChain; public void InitializeHandlerChain(RenderGrid renderGrid, WorldManager worldManager) { // 构建链条:RenderGrid -> WorldManager var renderHandler = new RenderGridHandler(renderGrid); var worldHandler = new WorldManagerHandler(worldManager); renderHandler.SetNext(worldHandler); _handlerChain = renderHandler; } private void OnPlayerPositionChanged() { var moveArgs = new PlayerMoveArgs { BottomLeft = CalculateBottomLeft(), TopRight = CalculateTopRight() }; // 触发链条处理 _handlerChain.Handle(moveArgs); } // 省略其他方法... }
3. 中介者模式
如果你的游戏模块比较多,不想让PlayerController直接和RenderGrid、WorldManager耦合,中介者模式是个不错的选择——用一个GameEventMediator来统一管理事件分发顺序,玩家只需要通知中介者,由中介者按预定顺序调用各个模块。
核心优势:
- 解耦玩家控制器和各个业务模块,后续修改执行顺序只需要调整中介者即可
- 集中管理所有游戏事件的分发逻辑,便于维护
简化示例:
public class GameEventMediator { private RenderGrid _renderGrid; private WorldManager _worldManager; public GameEventMediator(RenderGrid renderGrid, WorldManager worldManager) { _renderGrid = renderGrid; _worldManager = worldManager; } public void OnPlayerMoved(PlayerMoveArgs args) { // 严格按顺序执行:先渲染瓦片,再加载区块 _renderGrid.InstantiateTiles(args.BottomLeft, args.TopRight); _worldManager.LoadChunks(args.BottomLeft, args.TopRight); } } // 玩家控制器类 public class PlayerController { private GameEventMediator _mediator; public PlayerController(GameEventMediator mediator) { _mediator = mediator; } private void OnPlayerPositionChanged() { var moveArgs = new PlayerMoveArgs { BottomLeft = CalculateBottomLeft(), TopRight = CalculateTopRight() }; // 通知中介者 _mediator.OnPlayerMoved(moveArgs); } }
实践建议
- 如果是Unity游戏,你可以结合
UnityEvent但要注意:默认UnityEvent的订阅顺序是按添加顺序来的,所以只要先给RenderGrid注册事件,再给WorldManager注册,就能保证顺序,但这种方式不够灵活,后续调整顺序需要修改注册代码 - 性能敏感的场景(比如大地图频繁移动),优先选择带优先级的观察者模式,因为责任链和中介者的调用开销几乎可以忽略,但观察者模式的排序逻辑要尽量高效(比如用
SortedList提前排序,避免每次触发都排序)
内容的提问来源于stack exchange,提问作者Row Rebel
相关产品推荐
相关产品推荐

