Unity MyFramework 用法说明(八):逻辑就是对象,使用 Command 封装跨系统业务

游戏开发中,经常会有一段逻辑同时涉及多个系统。如果这些操作散落在角色、UI、任务和战斗系统中,后续很难确认一次业务操作到底会产生哪些影响。MyFramework 使用 Command 思想,把一段具有明确含义的业务逻辑集中封装到一个对象中。

项目地址:

github.com/ZHOURUIH/My...

一、Command 解决什么问题

以怪物血量变化为例,一次血量修改可能同时包含:

bash 复制代码
修改怪物血量数据
刷新怪物头顶血条
播放受击表现
判断怪物是否死亡
更新任务或战斗统计

如果调用方分别操作这些系统:

bash 复制代码
monster.setHealth(newHealth);
monster.getHealthBar()?.setHealth(newHealth);
mBattleStatistic.addDamage(damage);

if (newHealth <= 0)
{
	monster.dead();
}

这些代码很容易出现在网络消息、技能、Buff、调试工具等多个地方。

时间久了以后,不同入口执行的逻辑可能不一致:

bash 复制代码
技能伤害会刷新血条
Buff 伤害忘记刷新血条
GM 命令修改血量没有触发死亡
网络同步没有更新战斗统计

更合适的做法,是把"怪物血量变化"封装成一个命令。

二、创建一个静态命令

如果逻辑不需要延迟执行,也不需要保存执行状态,命令可以只是一个带静态 execute() 的类:

bash 复制代码
public class CmdMonsterHealthChanged
{
	public static void execute(Monster monster, int newHealth, int damage)
	{
		if (monster == null || monster.isDead())
		{
			return;
		}

		int health = MathUtility.clamp(newHealth, 0, monster.getMaxHealth());
		monster.setHealth(health);

		monster.getHealthBar()?.setHealth(
			health,
			monster.getMaxHealth());

		mBattleStatistic.addDamage(damage);

		if (health == 0)
		{
			CmdMonsterDead.execute(monster);
		}
	}
}

调用方只需要:

bash 复制代码
CmdMonsterHealthChanged.execute(
	monster,
	monster.getHealth() - damage,
	damage);

看到这句代码时,可以明确知道这里执行的是一次完整的"怪物血量变化",而不是单纯修改一个字段。

三、为什么静态类仍然属于 Command

Command 的核心不是必须:

bash 复制代码
继承 Command
创建命令实例
设置命令接收者
压入命令队列
支持延迟和中断

它的核心是:

逻辑就是对象。

命令类本身代表一段业务逻辑:

bash 复制代码
类名        表示这段逻辑的业务含义
execute     表示统一执行入口
参数        表示执行逻辑需要的数据
类内部      保存这次操作的完整流程

因此,即使没有创建实例,CmdMonsterHealthChanged 在设计上仍然是一个明确的逻辑对象。

只有在确实需要延迟执行、保存状态或中断时,才有必要继承框架中的 Command 并创建实例。

不要为了使用 Command 而强行增加对象和调度流程。

四、命令可以继续拆分

一个命令不应该无限扩大。

怪物死亡本身也是一段完整业务,因此可以继续封装:

bash 复制代码
public class CmdMonsterDead
{
	public static void execute(Monster monster)
	{
		if (monster == null || monster.isDead())
		{
			return;
		}

		monster.setDead(true);
		monster.stopMove();
		monster.playDeadAnimation();

		mMonsterManager.removeAliveMonster(monster);
		mBattleStatistic.addKillCount();

		LT.HIDE(monster.getHealthBar());
	}
}

血量变化命令只需要决定:

bash 复制代码
if (health == 0)
{
	CmdMonsterDead.execute(monster);
}

这样每个命令都有明确含义,也能被其他入口复用。

五、Command 和 EventSystem 的区别

Command 和 EventSystem 都可能涉及多个系统,但两者用途不同。

Command:明确知道要执行什么

bash 复制代码
CmdMonsterHealthChanged.execute(monster, health, damage);

调用关系是确定的:

bash 复制代码
调用方
  ↓
血量变化命令
  ├── 修改怪物数据
  ├── 更新血条
  ├── 更新统计
  └── 判断死亡

它不需要订阅,也不需要分发。

需要执行的逻辑直接写在命令中,阅读代码时可以顺着调用关系找到全部行为。

EventSystem:不知道谁会处理

bash 复制代码
mEventSystem.pushEvent<EventMonsterHealthChanged>();

事件发送方只负责通知:

bash 复制代码
怪物血量已经发生变化

至于哪些系统监听、监听者什么时候注册、会有多少监听者,发送方并不知道。

EventSystem 更适合:

bash 复制代码
监听者数量不确定
监听关系会动态变化
发送方不应该依赖监听方
某些响应属于可选行为

如果业务发生时已经明确知道必须修改哪些系统,就不需要为了"解耦"而全部改成事件。

过多事件会让执行链路变得隐蔽,阅读发送代码时无法直接知道最后会触发哪些逻辑。

六、Command 中也可以发送事件

Command 和 EventSystem 并不冲突。

命令负责必须执行的主逻辑,完成以后仍然可以发送事件,通知那些不属于核心流程的监听者:

bash 复制代码
public class CmdMonsterHealthChanged
{
	public static void execute(Monster monster, int newHealth)
	{
		monster.setHealth(newHealth);
		monster.getHealthBar()?.setHealth(newHealth);

		if (newHealth <= 0)
		{
			CmdMonsterDead.execute(monster);
		}

		mEventSystem.pushEvent<EventMonsterHealthChanged>();
	}
}

这里可以区分为:

bash 复制代码
修改数据、刷新血条、判断死亡
属于这次操作必须执行的逻辑,写在 Command 中

成就、埋点或临时活动监听
不属于命令必须知道的逻辑,可以通过 EventSystem 响应

七、命令应该放什么逻辑

适合封装成 Command 的通常是具有明确业务含义的操作:

bash 复制代码
角色受到伤害
怪物血量变化
角色死亡
拾取物品
穿戴装备
领取任务奖励
读取邮件
购买商品
切换地图

不建议把简单的字段访问也全部写成命令:

bash 复制代码
monster.getHealth();
monster.getPosition();
item.getCount();

Command 应该代表一段完整逻辑,而不是代替所有普通函数。

八、命名和调用规则

命令类建议使用统一前缀:

bash 复制代码
CmdMonsterHealthChanged
CmdMonsterDead
CmdCharacterSelect
CmdGlobalPickItem
CmdMailRead

统一入口使用:

bash 复制代码
public static void execute(...)

调用时就能形成清晰的业务代码:

bash 复制代码
CmdCharacterTakeDamage.execute(character, attacker, damage);
CmdMonsterHealthChanged.execute(monster, health, damage);
CmdMonsterDead.execute(monster);

命令名称应描述业务行为,而不是描述内部实现。

例如:

bash 复制代码
CmdMonsterHealthChanged    清楚
CmdSetValue                含义过于模糊
CmdUpdate                  无法判断更新什么

九、总结

MyFramework 中 Command 的核心不是命令队列,也不是延迟、中断或接收者,而是一种逻辑封装思想:

逻辑就是对象。

当一个业务操作会明确影响多个系统时,把完整逻辑集中到一个命令类中:

bash 复制代码
调用入口统一
执行内容明确
跨系统逻辑集中
不同调用方可以复用
代码调用关系容易追踪

不需要延迟时,直接使用静态 execute() 即可。

Command 负责明确、必须执行的业务逻辑;EventSystem 负责订阅、广播和不确定的监听关系。两者用途不同,不应该相互替代。

相关推荐
qiqizizzz21 小时前
Unity编译提速#05 | 认识 Assembly Definition:给代码划定边界
unity3d
_zhourui_h_1 天前
Unity MyFramework 用法说明(七):使用 RedPointSystem 构建树形红点
unity3d
SmalBox1 天前
【节点】[EvaluateScatteringColor节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox2 天前
【节点】[EvaluateRefractionData节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_2 天前
Unity MyFramework 用法说明(六):使用 FrameSystem 管理全局快捷键
unity3d·游戏开发
_zhourui_h_3 天前
Unity MyFramework 用法说明(五):使用 GameScene 和 SceneProcedure 管理业务流程
unity3d·游戏开发
_zhourui_h_3 天前
Unity MyFramework 用法说明(四):使用 UGUIGenerator 自动生成 UI 代码
unity3d