Skip to content

Wenext Game Plugin(游戏通用插件)

面向 H5 游戏的可插拔组件插件包,提供游戏房间(Game Room)、榜单(Ranking)、活动入口(Activity Entrance)等通用 UI 能力,并在宿主(Host)项目中以“配置驱动”的方式自动挂载(Mount)。

  • 定位与价值(Positioning & Value)

    • 在宿主项目内按需挂载(mount)游戏相关 UI 组件,减少重复开发成本
    • 通过 plugins.json 配置实现跨 App / Game 场景的通用化与差异化输出
    • 统一事件总线(Event Bus)与全局桥(Global Bridge)接口,简化与客户端交互
  • 构建模式(Build Mode,库模式 Library Mode)

  • 运行时挂载(Runtime Mount)

  • 事件总线(Event Bus)

  • 可选 Tailwind 3(Tailwind CSS optional)

  • 国际化(i18n)扩展与组件内局部实例


开发环境要求(Dev Requirements)

  • Node.js:>= 18(Vite 5 要求)

  • 包管理器(Package Manager):npm / pnpm

  • TypeScript:^5.9.3

  • Vite:^5.0.0

  • Vue:^2.7.7(peer deps)

  • Vue I18n:^8.28.2(peer deps)

  • Tailwind:3.x(可选)

  • PostCSS:^8.4.0,Autoprefixer:^10.4.0

  • SCSS:sass ^1.80.0

  • 集成方式(Integration)

    • 宿主侧提供全局桥(Global Bridge)与全局共享变量(Global Properties)
    • 通过 loadPlugins(App, gameId, props, globalProperties) 启动插件加载与挂载管理
    • 使用 plugins.json 为不同 App / Game 场景声明组件清单、挂载点、样式定位与启用条件

关键配置项:

  • Tailwind 作为可选依赖:postcss.config.js 通过环境变量启用或降级
  • i18n 集成支持两种模式:extendI18ncreateLocalI18n(由组件选择)

项目引入(Host Integration)

  • 安装包与样式引入:
    • pnpm add @wenext/game-plugin
    • import '@wenext/game-plugin/style.css'
  • 引入 API:
    • loadPlugins(App, gameId, props, globalProperties):按 plugins.json 自动注册与挂载
    • registerWenextGamePluginsGlobal(globals):注入 MonoNetwork/httpMonoBridge(如 getLanguageCodeKVStorageGetgetUserRegion)等
    • event:导出游戏事件常量,供宿主与插件协同使用
  • 全局 Vue 要求:
    • 目标环境为 Vue 2.7(peer 依赖)。宿主需提供 window.Vue(或直接使用 Vue 2.7 工程)。
    • 若宿主为 Vue 3,请确保兼容提供 window.Vue.createApp;当前组件主要以 Vue 2 构建,推荐宿主使用 Vue 2.7。
  • 修改插件配置:
    • 当前由库内 plugins.json 驱动。如需调整挂载点与组件清单,请在本仓库修改 packages/wenext-game-plugin/plugins.json 后重新构建与发布。
  • CDN 与资源:
ts
export const getCdnUrl = (cdnPath: string, fileName: string) => {
  if (!cdnPath || !fileName) return '';
  return `https://web-resource-cf.wenext-activity.chat/common-libs/game-plugin/${cdnPath}/${fileName}`;
};

cdnPath 为组件名称,对应cdn上的文件夹,示例:

  • game-room:游戏房间组件
  • ranking:榜单组件
  • activity-entrance:活动入口组件

fileName 为组件内静态资源文件名,示例:

  • rank_bg_1.webp:榜单组件背景图
  • entrance_icon_1.svg:活动入口组件图标

使用说明(Usage)

入口与导出

  • 引入样式:import '@wenext/game-plugin/style.css'
  • 包入口导出:import { loadPlugins, registerWenextGamePluginsGlobal } from '@wenext/game-plugin'
  • 事件常量:import { eventBus, GameCoreEvents } from '@wenext/game-plugin'

宿主侧初始化

  • 全局桥注入(网络、端能力、工具等)通过 registerWenextGamePluginsGlobal
    ts
    import VueI18n from "vue-i18n";
    import * as MonoUtils from "@mono/utils";
    import * as MonoBridge from "@mono/bridge";
    import MonoNetwork from "@mono/network";
    import * as MonoNetworkConstant from "@mono/network/constant";
    import AvatarView from "@mono/ui/components/AvatarView.vue";
    import { loadPlugins, registerWenextGamePluginsGlobal, eventBus, GameCoreEvents } from '@wenext/game-plugin';
    import '@wenext/game-plugin/style.css';
    
    // Vue 2.7 环境
    // loadPlugins之前需注入全局桥(Mono*):网络/http、端能力/Bridge、工具/Utils、UI等
    registerWenextGamePluginsGlobal({
      MonoUtils,
      MonoBridge,
      MonoNetwork: { http: MonoNetwork, ...MonoNetworkConstant },
      MonoUI: { AvatarView },
    });
    
    const App = 'wyak';
    const gameId = 'wyak-greedy'; // 或 'wyak-greedy-personal' 等
    const props = {};
    const globalProperties = {
      stores: {},
      i18n: new VueI18n(),
      constants: {},
      components: {},
    };
    
    // 加载插件:根据 plugins.json 自动挂载组件
    loadPlugins(App, gameId, props, globalProperties);
    // 页面卸载时清理
    eventBus.clear();

锚点与动态挂载

  • 固定锚点:需在宿主页面 DOM 中提供 plugins.json 指定的 anchorId
    html
    <div id="rank-top-3"></div>
    <div id="game-activity-entrance"></div>
  • 动态锚点(game-room):使用 anchorId: 'game-room-entrance-$',按下注项替换 $betOption.id
    html
    <div id="game-room-entrance-1"></div>
    <div id="game-room-entrance-2"></div>
  • 位置样式(anchorStyle):posY/posX 基于设计稿(750px)转换为 vw,内部通过 setElementTopAndStart 适配 RTL/LTR。

动态启用(enabledFunc)

  • plugins.json 中通过 enabledFunc 绑定函数名,函数实现位于 src/utils/dynamic-enable-funcs.ts
    json
    {
      "componentName": "rank-top-3",
      "anchorId": "rank-top-3",
      "props": { "gameType": 11 },
      "enabledFunc": "checkRankingsEnabled"
    }
  • 示例实现:
    ts
    // src/utils/dynamic-enable-funcs.ts
    export const dynamicEnableFuncs = {
      checkRankingsEnabled: async () => {
        const { KVStorageGet } = getMonoBridge();
        const { res, err } = await KVStorageGet({
          key: KVStorageKey.KEY_LUCK_GAME_RANK_REGION_ENABLE,
          defaultValue: 'false',
        });
        return !(err || res !== 'true');
      },
    };

销毁与生命周期

  • 插件管理器内部有加载轮询与超时保护,退出页面需调用 destroy() 释放定时器与队列。

开发调试(Dev & Debug)

  • 工作区准备:

    • 进入插件包:cd packages/wenext-game-plugin
  • 构建与类型:

    • 构建:pnpm run build
    • 监听构建:pnpm run build:dev(Vite --watch + 生成类型)
  • 本地联调(推荐 pnpm link)

    • 在插件包中:
      bash
      pnpm run build:dev
      pnpm link
    • 在宿主项目中:
      bash
      pnpm link @wenext/game-plugin
    • 取消链接:
      bash
      # 宿主侧
      pnpm unlink @wenext/game-plugin
      # 插件包侧
      cd packages/wenext-game-plugin && pnpm unlink
    • 路径链接(备选,跨仓库无全局):
      bash
      pnpm link /absolute-path-to/wenext-h5-common-libs/packages/wenext-game-plugin
  • Tailwind 可选启用:

    • 通过环境变量开启:WN_ENABLE_TAILWIND=1 pnpm -F @wenext/game-plugin build
    • 未安装 tailwindcss 时自动降级(移除 @tailwind 指令),详见 postcss.config.js
  • 调试要点:

    • 控制台日志:
      • [Plugin register] 列出待注册组件清单
      • [Plugin] mounted: <name> ... 成功挂载提示
    • i18n:组件若提供 createLocalI18n 会优先使用局部实例;否则尝试扩展宿主传入的 i18n

事件总线升级说明(Event Bus Upgrade)

本版本在不改变公开 API(on/emit/off/clear)的前提下,为事件总线新增以下能力:

  • 来源追踪:为每个监听器记录来源(组件/模块名)
  • 重复注册防护:同一事件、同一回调、同一来源的重复注册将被忽略
  • 性能统计:记录每个事件的触发次数(emits)、监听数(listeners)、错误次数(errors)
  • 控制台可视化:开发环境下定期打印事件统计(节流控制)

使用方式(保持不变)

ts
import { eventBus } from '@wenext/game-plugin';

// 订阅(保持原有签名)
eventBus.on('game:start', (payload) => {
  // ...
});

// 触发(保持原有签名)
eventBus.emit('game:start', { id: 123 });

// 移除或清空(保持前两个参数不变,支持可选第三参数来源)
eventBus.off('game:start');
// 按来源移除监听器(不指定 callback 时批量移除该来源的监听)
eventBus.off('game:start', undefined, { source: 'RankingModule' });
eventBus.clear();

声明来源(可选)

监听器可以选择性地声明来源:

  • 通过 on(event, handler, { source }) 传入来源

未声明来源时不设置来源标识(通配),任何来源的 emit 都会触发该监听器。

日志与统计

  • 是否输出日志与统计由编译时配置 dev 控制(节流防刷屏)
  • 可通过全局配置覆盖默认参数(无需更改 API):

迁移说明(Migration)

  • 旧代码无需变更:原有 on/emit/off/clear 均保持不变
  • 来源策略:未指定来源时不设置来源标识(通配),任何来源的 emit 均会触发该监听器;若监听器显式指定了来源,则仅触发相同来源的消息
  • 重复注册行为:同一事件、同一回调、同一来源的重复注册将被忽略(旧版会叠加);如需并行处理,请使用不同回调或不同来源标识

来源声明的可选第三参数

为保持前两个参数类型不变,on 支持可选第三参数:

ts
eventBus.on('game:start', handler, { source: 'RankingModule' });

触发过滤的可选第三参数

为保持前两个参数类型不变,emit 支持可选第三参数进行来源过滤:

ts
// 仅触发来源为 RankingModule 的监听器
eventBus.emit('game:start', { id: 123 }, { source: 'RankingModule' });

// 未提供来源时:在 requireSourceMatch=true(默认)下,仅触发“未显式声明来源”的监听器
eventBus.emit('game:start', { id: 123 });

当指定了 source 时,仅与该来源完全匹配的监听器会被触发; 在 requireSourceMatch=true(默认)时,未指定来源不会触发“具显式来源”的监听器;仅触发未显式声明来源的监听器(兼容旧版)。

破坏性变更(Breaking Changes)

无。公开接口与默认行为保持兼容;新增的重复注册防护仅抑制冗余监听,不影响正常业务逻辑。