- 打卡等级:魔龙套勇士
- 打卡总天数:182
- 打卡月天数:14
- 打卡总奖励:19156
- 最近打卡:2025-10-14 00:17:14
管理员
本站站长
- 积分
- 9039
|
在传奇游戏脚本中,使用事件驱动通信解耦循环依赖模块的核心思路是:让原本互相依赖的模块通过 “发布事件” 和 “订阅事件” 实现间接交互,而非直接调用对方的函数。这种方式能彻底切断模块间的直接依赖链,尤其适合解耦 “双向依赖” 或 “多模块循环依赖” 场景。结合传奇引擎(GOM、HXM2)的脚本特性(支持函数调用、数组存储、自定义变量),具体实现步骤如下:
一、设计事件驱动的核心组件
首先需要实现一个事件总线(EventBus)模块,作为事件的 “中介”,负责管理事件的订阅、发布和分发。该模块是解耦的关键,所有业务模块仅依赖事件总线,不直接依赖其他模块。
事件总线的核心功能:
事件订阅:模块注册需要关注的事件类型及自身的事件处理函数。
事件发布:模块触发事件时,将事件类型和参数发送到事件总线。
事件分发:事件总线将收到的事件转发给所有订阅该事件的模块。
传奇脚本实现(以 HXM2 引擎为例):
scp
// 事件总线模块:EventBus.scp
// 存储订阅关系:EventSubscribers[事件类型][索引] = 模块名
Local Array EventSubscribers
// 1. 订阅事件:模块注册需要监听的事件
// 参数:ModuleName(订阅模块名)、EventType(事件类型)
Function Subscribe(ModuleName, EventType)
// 初始化该事件类型的订阅者数组(若不存在)
If Not IsArray(EventSubscribers[EventType])
EventSubscribers[EventType] = NewArray()
EndIf
// 添加订阅者
Local Index = GetArrayLength(EventSubscribers[EventType])
EventSubscribers[EventType][Index] = ModuleName
Call LogModule::WriteLog("EventBus", "模块" + ModuleName + "订阅了事件" + EventType)
EndFunction
// 2. 发布事件:模块触发事件并传递参数
// 参数:EventType(事件类型)、Param1~Param3(事件参数,根据需求扩展)
Function Publish(EventType, Param1, Param2, Param3)
If Not IsArray(EventSubscribers[EventType])
Return // 无订阅者,直接返回
EndIf
// 遍历所有订阅者,调用其事件处理函数
For i=0 To GetArrayLength(EventSubscribers[EventType])-1
Local ModuleName = EventSubscribers[EventType][i]
// 调用订阅模块的OnEvent函数(约定所有模块的事件处理函数名为OnEvent)
Call ModuleName::OnEvent(EventType, Param1, Param2, Param3)
EndFor
Call LogModule::WriteLog("EventBus", "发布事件" + EventType + ",参数:" + Param1 + "," + Param2)
EndFunction
二、改造循环依赖的模块:从 “直接调用” 到 “事件交互”
以典型的 “任务模块(TaskModule)” 与 “奖励模块(RewardModule)” 循环依赖为例(TaskModule 需要调用 RewardModule 发放奖励,RewardModule 需要调用 TaskModule 校验任务状态),演示如何通过事件驱动解耦。
步骤 1:定义事件类型
根据模块交互场景,定义需要的事件(事件名需唯一且语义清晰):
事件类型 触发场景 携带参数
EVENT_TASK_FINISH 任务完成时(由 TaskModule 触发) PlayerID(玩家 ID)、TaskID(任务 ID)
EVENT_REWARD_ISSUED 奖励发放后(由 RewardModule 触发) PlayerID、TaskID、RewardID(奖励 ID)
EVENT_CHECK_TASK 需要校验任务状态时(由 RewardModule 触发) PlayerID、TaskID(待校验任务 ID)
步骤 2:改造 TaskModule(任务模块)
移除对 RewardModule 的直接调用,改为发布事件;同时订阅EVENT_CHECK_TASK事件,响应奖励模块的校验请求。
scp
// 任务模块:TaskModule.scp
Function Init()
// 订阅“校验任务状态”事件(响应RewardModule的请求)
Call EventBus::Subscribe("TaskModule", "EVENT_CHECK_TASK")
EndFunction
// 玩家完成任务时触发
Function CompleteTask(PlayerID, TaskID)
// 标记任务为完成(内部逻辑)
SetTaskStatus(PlayerID, TaskID, 1) // 1=已完成
// 发布“任务完成”事件(替代直接调用RewardModule发放奖励)
Call EventBus::Publish("EVENT_TASK_FINISH", PlayerID, TaskID, 0)
EndFunction
// 事件处理函数(响应订阅的事件)
Function OnEvent(EventType, Param1, Param2, Param3)
If EventType == "EVENT_CHECK_TASK"
// 收到校验任务的请求,返回任务状态
Local PlayerID = Param1
Local TaskID = Param2
Local Status = GetTaskStatus(PlayerID, TaskID) // 0=未完成,1=已完成
// 发布“任务状态结果”事件(供RewardModule接收)
Call EventBus::Publish("EVENT_TASK_STATUS", PlayerID, TaskID, Status)
EndIf
EndFunction
步骤 3:改造 RewardModule(奖励模块)
移除对 TaskModule 的直接调用,改为订阅事件获取任务完成通知;通过发布EVENT_CHECK_TASK事件请求校验任务状态,而非直接调用 TaskModule。
scp
// 奖励模块:RewardModule.scp
Function Init()
// 订阅“任务完成”事件(接收TaskModule的通知)
Call EventBus::Subscribe("RewardModule", "EVENT_TASK_FINISH")
// 订阅“任务状态结果”事件(接收TaskModule的校验结果)
Call EventBus::Subscribe("RewardModule", "EVENT_TASK_STATUS")
EndFunction
// 事件处理函数(响应订阅的事件)
Function OnEvent(EventType, Param1, Param2, Param3)
If EventType == "EVENT_TASK_FINISH"
// 收到任务完成通知,发放奖励
Local PlayerID = Param1
Local TaskID = Param2
Call IssueReward(PlayerID, TaskID) // 内部发放奖励逻辑
// 发布“奖励已发放”事件(可选,供其他模块监听)
Call EventBus::Publish("EVENT_REWARD_ISSUED", PlayerID, TaskID, GetRewardID(TaskID))
ElseIf EventType == "EVENT_TASK_STATUS"
// 收到任务状态校验结果(处理自己发起的校验请求)
Local PlayerID = Param1
Local TaskID = Param2
Local Status = Param3
If Status == 1
Call LogModule::WriteLog("RewardModule", "玩家" + PlayerID + "的任务" + TaskID + "已完成")
EndIf
EndIf
EndFunction
// 主动校验任务状态(如发放特殊奖励前)
Function CheckTaskBeforeSpecialReward(PlayerID, TaskID)
// 发布“校验任务”事件(替代直接调用TaskModule::GetStatus)
Call EventBus::Publish("EVENT_CHECK_TASK", PlayerID, TaskID, 0)
EndFunction
三、解耦效果验证
改造后,模块间的依赖关系从:
TaskModule ↔ RewardModule(循环依赖)
变为:
TaskModule → EventBus ← RewardModule(单向依赖事件总线,无循环)
核心优势:
彻底解耦:模块间无需知道对方的存在,仅通过事件交互,新增或移除模块不影响其他模块。
扩展性强:若新增 “成就模块(AchievementModule)” 需要监听任务完成事件,只需订阅EVENT_TASK_FINISH,无需修改 TaskModule 或 RewardModule。
逻辑清晰:事件类型明确记录了模块间的交互意图(如EVENT_TASK_FINISH清晰表示 “任务已完成”),便于维护。
四、适配传奇引擎的注意事项
事件命名规范:采用 “模块_动作” 格式(如TASK_FINISH、REWARD_ISSUED),避免事件名冲突。
参数设计:事件参数需包含关键信息(如 PlayerID、TaskID),但避免传递过大数据(如整个背包信息),防止性能损耗。
同步 / 异步处理:
高频事件(如战斗伤害):采用同步发布(事件总线立即分发),确保实时性;
低频事件(如任务完成):可采用异步队列(事件总线缓存事件,批量处理),降低瞬时压力。
循环事件防护:避免模块在处理事件时再次发布自身订阅的事件(如 RewardModule 处理EVENT_TASK_FINISH时又发布EVENT_TASK_FINISH),导致无限循环,可在事件参数中添加 “来源标记” 过滤。
五、多模块循环依赖的扩展应用
对于三个及以上模块的循环依赖(如 A→B→C→A),事件驱动同样适用。例如:
A(战斗模块)发布EVENT_BATTLE_END事件;
B(任务模块)订阅该事件更新任务进度,发布EVENT_TASK_UPDATE;
C(奖励模块)订阅EVENT_TASK_UPDATE发放奖励,发布EVENT_REWARD_GIVEN;
A 订阅EVENT_REWARD_GIVEN更新战斗增益(如临时属性加成)。
依赖链变为A→EventBus←B→EventBus←C→EventBus←A,循环被打破。
总结
使用事件驱动通信解耦循环依赖模块的核心是 **“用事件作为模块间的交互中介”**:通过事件总线统一管理订阅与发布,让模块从 “直接调用对方” 转变为 “对事件做出响应”。在传奇游戏脚本中,这种方式不仅能彻底消除循环依赖,还能提升模块的可复用性和扩展性,尤其适合复杂玩法(如多模块协同的活动、任务系统)的开发。实施时需注意事件设计的合理性和引擎性能适配,确保解耦的同时不引入额外的性能瓶颈。
|
|