一.背景

目前cocos2d游戏最主要的开发方式是通过官方提供的GUI图形界面工具——creator,通过 creator 开发者无需关注构建本身,只需通过界面操作即可对游戏代码进行构建打包。但是这样也存在着以下几个问题:

  • 构建闭源,导致开发者对项目构建无法定制化,假如编译出来的代码存在兼容性问题,那只能进入 creator 安装目录寻找对应的某个配置文件进行修改,这种侵入性的修改很有可能会引发不稳定性。
  • 无法使用其他构建工具进行打包,意味着项目无法使用新的技术方案,只能局限于 creator 设定的框架之中
  • 游戏组件在不同项目之间难以复用,组件通常包含了 prefab、sprite 等资源,如何发布托管并在其他项目复用组件,简单地通过 creator 是无法做到的。通过 EMP微前端的方案 能从根本上解决这些问题

通过这些问题让我们有了一种想法,有没有可能通过其他构建工具打包的代码,也能关联到 creator 的项目中,这样也能在creator cocos2d项目中引入EMP,从而解决组件复用的问题

二. creator项目接入webpack模型

首先看看单一 creator 的开发过程,它会在本地服务开启 7456 的端口服务,整个本地开发流程如下图:

cocos2d线上项目落地微前端_cocos2d

接入 webpack 和 emp 后的开发过程,首先 webpack 会通过 axios 抓去 creator服务生成出来的 index.html文件作为 template,并开启一个新的服务,并通过 devServer 将资源请求转发回 creator的端口服务,确保资源访问正常,开发流程图如下:

cocos2d线上项目落地微前端_cocos2d_02

三. 接入流程

step1

全局安装 @efox/emp-cli,通过 emp init 初始化游戏种子工程,选择 cocos2d 模版

yarn add --global @efox/emp-cli && emp init

step2

在根项目通过命令 yarn 安装依赖

step3

安装 creator cocos 开发工具 ,打开 cocosDashboard, 导入刚刚的游戏模版项目,然后打开项目,下面是导入成功后的开发工具截图;

cocos2d线上项目落地微前端_cocos2d_03

复制开发工具上方提示的本地调试链接到浏览器上,呈现出来的界面如下,可以看到右方console出现报错的情况,这是因为目前打开的只是 creator 开启的本地服务,而项目引入了 webpack 构建的代码,所以暂时会出现报错的情况;

cocos2d线上项目落地微前端_cocos2d_04

step4

进入项目根目录,运行 yarn dev

浏览器会自动打开新端口服务,这个本地服务就是 webpack 代理 creator cocos 后的服务,通过下图看到console已经没有报错了,并且界面上的 Hello World拥有了渐变背景色,这个渐变背景色实际上就是一个 Game Component,是通过 webpack 构建并注入到游戏项目中引入的;

cocos2d线上项目落地微前端_cocos2d_05

ps: 必须先通过creator cocos开启项目,再运行 yarn dev,因为 webpack 编译时需要通过 axios 抓取 creator cocos 服务模版;

四. 项目代码分析

emp-config.js

// cocos2d emp配置
const withCocos2d = require('@efox/emp-cocos2d')
const ip = require('ip')

module.exports = withCocos2d(
  ({config, env, empEnv}) => {
    // webpack本地服务端口
    const port = 9000
    const projectName = 'empCocos2dDemo'
    const host = ip.address()
    const publicPath = `http://${host}:${port}/`

    config.plugin('mf').tap(args => {
      args[0] = {
        ...args[0],
        ...{
          name: projectName,
          library: {type: 'var', name: projectName},
          filename: 'emp.js',
          // remotes: {
          //   '@emp-game/base': 'empGameBase',
          // },
          exposes: {
            // 将当前项目的component expose出去给其他项目使用
            './components': 'src/components',
          },
        },
      }
      return args
    })

    config.output.publicPath(publicPath)
    config.devServer.host(host)
    config.devServer.port(port)
  },
  {
    // creator开启的服务端口
    creatorPort: 7456,
    // 引用基站资源链接
    empJs: [],
  },
)

src/index.ts

webpack 的代码如何注入到 creator cocos 代码中,靠的是 cc 这个全局变量,cc 变量是cocos2d引擎暴露在全局的,包含了全部的引擎方法;creator cocos 接入 emp 的关键点就是在 cc全局变量上创建一个 EMP 属性,这样后续无论远程组件模块,抑或是本地构建的module, 都可以附值在 cc.EMP 上,从而在游戏代码中引入;

cc这个全局变量是cocos2d引擎暴露在全局的,这里也分为本地环境和生产环境的情况,这是因为本地环境cocos2d引擎脚本是同步加载的,而生产环境是异步的,这就是为什么下面代码判断了 window.boot 是否存在的情况,附值 cc.EMP 的操作是需要 cc 存在才能进行;

import Components from 'src/components'

export type EMPData = {
  Components: typeof Components
}

const EMP: EMPData = {
  Components,
}

// 关键部分
// 生产环境 cocos2d脚本异步加载后 再执行window.boot
// 通过重写 window.boot 在函数体内进行cc.EMP附值,再执行原函数,确保游戏代码运行时 cc.EMP正常有值
if (window.boot) {
  const fn = window.boot
  window.boot = async function () {
    cc.EMP = EMP
    fn()
  }
} else {
  // 本地环境 直接附值
  cc.EMP = EMP
}

assets/Script/HelloWorld.ts

看回游戏代码,了解游戏代码是如何引入外部组件的

const {ccclass, property} = cc._decorator

@ccclass
export default class Helloworld extends cc.Component {
  @property(cc.Label)
  label!: cc.Label

  onLoad(): void {
    // 通过cc.EMP获取Component模块
    // 再通过Component模块拓展出渐变背景色组件
    const {colorGrad} = cc.EMP.Components
    // 获取labelNode 节点
    const labelNode = cc.find('labelNode', this.node)
    const label = cc.find('label', labelNode)
    // 通过addComponent添加渐变背景色,并设置脚本属性 _colors
    labelNode.addComponent(colorGrad)._colors = [
      cc.Color.WHITE.fromHEX('#ffffff'),
      cc.Color.WHITE.fromHEX('#5A51FF'),
      cc.Color.WHITE.fromHEX('#8668FF'),
      cc.Color.WHITE.fromHEX('#5A51FF'),
    ]
    setTimeout(() => {
      // 重设labelNode节点宽高
      labelNode.setContentSize(label.width, label.height)
    })
  }
}

五. 已有项目如何接入

接入步骤如下:

  1. 在根项目安装 emp 相关依赖;
yarn add -D @efox/cli
yarn add -D @efox/emp-tsconfig
yarn add -D @efox/emp-cocos2d
  1. 在 package.json 添加如下片段:
{
    ...,
    "scripts": {
        ...,
        + "dev": "emp dev",
        + "build": "emp build --ts --env prod && cp -r ./dist/. ./build/web-mobile"
    }
}
  1. 根目录创建 emp-config.js,并复制以下内容
const withCocos2d = require('@efox/emp-cocos2d')
const ip = require('ip')

module.exports = withCocos2d(
  ({config, env, empEnv}) => {
    const port = 9000
    const projectName = 'empCocos2dDemo'
    const host = ip.address()
    const publicPath = `http://${host}:${port}/`

    config.plugin('mf').tap(args => {
      args[0] = {
        ...args[0],
        ...{
          name: projectName,
          library: {type: 'var', name: projectName},
          filename: 'emp.js',
          // remotes: {

          // },
          exposes: {

          },
        },
      }
      return args
    })

    config.output.publicPath(publicPath)
    config.devServer.host(host)
    config.devServer.port(port)
  },
  {
    // creator开启的服务端口
    creatorPort: 7456,
    // 引用基站资源链接
    empJs: [],
  },
)
  1. 将 tsconfig.json 替换如下内容:
{
  "extends": "@efox/emp-tsconfig",
  "compilerOptions": {
    "experimentalDecorators": true,
    "baseUrl": "./"
  },
  "include": ["src", "creator.d.ts", "index.d.ts", "assets"]
}
  1. 创建 src 目录,并新建 index.ts
export type EMPData = {}

const EMP: EMPData = {}

if (window.boot) {
  const fn = window.boot
  window.boot = async function () {
    cc.EMP = EMP
    fn()
  }
} else {
  cc.EMP = EMP
}
  1. 创建 index.d.ts,定义 cc.EMP 类型声明
import {EMPData} from './src'

declare global {
  namespace cc {
    export let EMP: EMPData
  }
  interface Window {
    boot: () => void
  }
}

六. 总结

目前 creator cocos 游戏开发只能依赖官方开发工具,接入这套模型,能使得项目定制化更加便捷,且具有以下优点:

  1. 几乎零成本接入

首先看看接入 emp 后的 creator cocos项目目录结构

cocos2d线上项目落地微前端_cocos2d_06

由上图可以看出,与 普通的creator cocos2项目 相比,只多了以下2个文件和1个目录;

  • src/*
  • emp-config.js
  • index.d.ts

无需改变原游戏代码任何的部分,做到了几乎0成本接入

  1. 将开发方式回归到我们熟悉的步伐上,且更灵活,原本的开发方式是GUI工具制作 一个一个 prefab,再写一个一个脚本绑定进去,虽然操作简单,但开发体验很不好,接入模型后,GUI工具依然负责制作 prefab,但脚本就可以抽离出来由webpack构建,这能给编写的脚本带来更多的新特性,丰富开发方式;

  2. 组件开发灵活,多项目共享,当前开发游戏组件只能在当前项目复用,如果其他游戏项目也想用呢?这时可能会想到发布到官方托管的仓库或者npm仓库,但是有个问题,如果这个组件依赖了图片,那发布到npm仓库可能是一个难题;emp基站的模型能很好地解决这些问题,因为本身游戏项目就是一个基站,其他游戏项目可以轻松复用其他游戏 expose 出来的组件

不同游戏项目之间组件的互相调用,可以参考 EMP中的cocos2d

基于Webpack 5 Module Federation实现的 EMP微前端方案,creator cocos这种闭源的开发过程都能接入 EMP,证明这套方案并不局限技术栈;如果是刚开始接触微前端的话,可以尝试去了解一下,可以带给你很好的使用体验喔

具体的EMP微前端方案教程目录如下: