设为首页收藏本站
  • 官方微信
    lmkj_wx 微信公众号 添加方式:
    1:扫描左侧二维码
  • 手机访问
    lmkj_sj
  •  找回密码
     立即注册

    QQ登录

    只需一步,快速开始

    查看: 34|回复: 0

    如何使用事件驱动通信来解耦循环依赖的模块

    [复制链接]
    avatar
    • 打卡等级:魔龙套勇士
    • 打卡总天数:182
    • 打卡月天数:14
    • 打卡总奖励:19156
    • 最近打卡:2025-10-14 00:17:14

    7393

    主题

    230

    回帖

    9039

    积分

    管理员

    本站站长

    积分
    9039
    online_admin 发表于 2025-8-31 16:19:23 | 显示全部楼层 |阅读模式
    在传奇游戏脚本中,使用事件驱动通信解耦循环依赖模块的核心思路是:让原本互相依赖的模块通过 “发布事件” 和 “订阅事件” 实现间接交互,而非直接调用对方的函数。这种方式能彻底切断模块间的直接依赖链,尤其适合解耦 “双向依赖” 或 “多模块循环依赖” 场景。结合传奇引擎(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,循环被打破。
    总结
    使用事件驱动通信解耦循环依赖模块的核心是 **“用事件作为模块间的交互中介”**:通过事件总线统一管理订阅与发布,让模块从 “直接调用对方” 转变为 “对事件做出响应”。在传奇游戏脚本中,这种方式不仅能彻底消除循环依赖,还能提升模块的可复用性和扩展性,尤其适合复杂玩法(如多模块协同的活动、任务系统)的开发。实施时需注意事件设计的合理性和引擎性能适配,确保解耦的同时不引入额外的性能瓶颈。


    您需要登录后才可以回帖 登录 | 立即注册 qq_login

    本版积分规则

    QQArchiver 手机版 小黑屋 39传奇素材网 ( 蜀ICP备2022016510号-3 )

    快速回复 快速发帖 返回顶部 返回列表