Featured image of post 在本地完成大部分playcanvas项目构建的工作流的描述

在本地完成大部分playcanvas项目构建的工作流的描述

之前写了两篇playcanvas本地开发的文章,但是依旧对于playcanvas的web editor有比较多的依赖,开发过程中仍然需要将脚本打包并同步到playcanvas之后才能够运行,对于网络环境有一定的需求,并且合并等工作都有一些繁琐,因此这篇文章会介绍一种在本地完成大部playcanvas项目构建的工作方式。

现状

之前我写了一篇名为 Playcanvas本地编辑器开发方案 的文章,介绍了在vscode中开发playcanvas项目,并通过webpack打包脚本并使用pcsync将打包后的脚本上传到playcanvas的web editor中,再在editor中去运行项目进行测试,这种工作方式我们一直在使用,因为我们的工作有下面几个特征,所以这种方式是比较合适的:

  • 大量使用GLB作为程序内的资源,而非传统的游戏资源,并且这些模型都是在运行时动态引入,因此我们并不需要在编辑器中操作各类模型、材质等资源,而是在运行时按需从外部加载。
  • 大部分的工作为脚本编写,也不太用的到编辑器中的各种工具,因此我们并不需要在编辑器中进行大量的操作。
  • 少部分工作需要使用web editor,当尝试添加一下playcanvas内的UI时,如果完全脱离编辑器(直接使用playcanvas engine),需要开发者对于引擎能够做什么、怎么做有一定的了解,但是通过web editor,可以直接在编辑器中进行操作,而不需要了解引擎的内部实现,这样可以大大提高开发效率。

但是采用上面提到的工作流程还是有一些问题:

  • 本地脚本编写后,必须要通过playcanvas sync同步到playcanvas web editor中,才能够在web editor中运行,而且每次同步都需要一定的时间(更不要说从中国大陆同步到他们的aws存储的稳定性了),这样就会影响开发效率,实际上大部分修改都是不涉及编辑器中的资源变化的。
  • index.html 在playcanvas web editor构建后我们才能拿到,不方便修改其内容。当前我们项目部署采用的是github action通过playcanvas restful api发送项目构建打包的指令,并获得打包后的文件,直到这个时候我们才能拿到index.html,这就导致我们想要修改index.html变得很麻烦(比如修改title、metadata这一需求,我们还得在github action中通过sed来替换原index.html中的内容)。
  • 合并流程繁琐。由于playcanvas和github都提供了版本管理,而我们项目发布是采用从playcanvas上下载的文件,因此我们在合并时要处理两边的版本管理,在两边都要进行合并,需要保证发布时playcanvas主分支的代码是由github上最新的主分支代码构建的,这样才能保证发布的代码是最新的,否则就会出现发布的代码和github上的代码不一致的情况。这一步很麻烦,需要手动在两边进行操作,而不能通过github actions来自动化。

因此我们希望能够有一种在本地就能够直接构建/运行的方式,只有在必须的时候才与playcanvas web editor打交道,并且要能够支持只通过github便能完成发布操作,这个项目 webxr-make-it-rain 给了我们很大的启发。

在本地运行playcavnas项目

我们可以先看看playcanvas web editor上构建得到的压缩包中的内容(记得不要勾选concatenate scripts选项,因为我们webpack会打包出多个文件,所以我们希望能够操作单独的文件,而不是将它们连接成一整个 __game_scripts.js):

下载选项

打包后的文件内容

这个目录中直接可见的几个脚本分别是引擎本身以及几个用于初始化项目的脚本,两个json比较重要的则是 sceneid.json 以及 config.json ,这两个文件记录了我们项目的配置,我们在playcanvas web editor中设置的所有变量都反映在这两个文件中,比如我们设置的默认相机、重力、我们的项目中有哪些脚本(经过web editor parse得到的)、脚本有哪些属性(attributes)等等。

当我们以这个目录为根目录开启一个web服务器时(nginx等任意能够提供文件服务的服务器即可),我们可以直接在本地访问该项目。

将本地脚本的变化应用到项目中

我先首先介绍一下我们项目的脚本文件是怎么打包的,在经过webpack打包之后,我们只输出了两个文件:负责游戏逻辑的main.js文件和负责react ui的ui.js文件,这两个文件之前会通过pcsync上传到playcanvas web editor中,在我们下载后的项目文件夹中的files目录中我们可以找到这两个文件,路径是固定的,files目录中的目录名称对应了playcanvas中的资源ID。因此,当我们的修改只涉及到脚本内容时,我们只需要使用生成的文件替换掉对应的两个目录下的文件即可,我在下面贴了一个webpack的配置文件,其中包含了项目构建,webpack-dev-server(为了让我们能够在本地访问这个项目),以及WebpackShellPluginNext的设置(用于替换文件),需要注意的点我都写注释说明了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
const path = require('path');
const webpack = require('webpack');
const WebpackShellPluginNext = require('webpack-shell-plugin-next');

module.exports = {
  entry: {
    main: './src/main.ts',
    ui: './src/ui-entry.ts',
  },
  output: {
    path: path.resolve(__dirname, 'dist/Scripts'),
    filename: '[name].build.js',
    clean: true,
  },
  externals: {
    'playcanvas': 'pc',
    'playcanvas-extra': 'pcx',
    'video.js': 'global videojs',
    'agora-rtc-sdk-ng': 'global AgoraRTC',

  },
  cache: {
    type: 'filesystem'
  },
  plugins: [
    new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 1
    }),
    new WebpackShellPluginNext({
      onBuildEnd: {
        scripts: ['cp dist/Scripts/main.build.js build/files/assets/113763957/1/main.build.js', 'cp dist/Scripts/ui.build.js build/files/assets/113763971/1/ui.build.js'],
        blocking: true,
        parallel: false,
      },
      dev: false
    })
  ],
  devtool: 'inline-source-map',
  devServer: {
    devMiddleware: {
      index: true,
      writeToDisk: true, // 这个需要开启,因为我们另外设置了网站的根目录,不然文件的变化只体现在内存中,我们没办法替换掉原来的文件
    },
    static: {
      directory: path.join(__dirname, 'build'),
    },
    compress: true,
    port: 9000,
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: 'ts-loader',
      }, {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins: [
              require('@babel/plugin-proposal-nullish-coalescing-operator'),
              require('@babel/plugin-proposal-optional-chaining')
            ]
          }
        }
      }, {
        test: /\.css$/,
        use: [{
          loader: "style-loader"
        }, {
          loader: "css-loader", options: {
            sourceMap: true
          }
        }]
      }
    ]
  }
};

这个webpack的配置,使得我们可以直接在本地访问到我们的项目,并且每当我们修改本地文件后,webpack会自动重新打包并自动刷新页面,这样的话比起之前的上传到playcanvas web editor要快了非常多。

关于本地新建脚本的说明

在之前,我们新建了一个playcanvas中的脚本时,我们需要在本地的脚本文件中调用 pc.registerScript(ScriptClass, 'scriptName'); 这一方法,并且还需要在web editor中选择对应的脚本(main.js),点击parse解析出我们新增的脚本,然后在实体树上新增一个实体(或者选择已有的实体),并为其添加脚本组件并将我们的新脚本添加到脚本组件中。但是我们这篇文章的目的是减少对web editor的使用,如果每次新增脚本都要重复这个流程,那还是太麻烦了,其实这一步并非是必须的。除非我们想要把脚本直接加到现在实体树上已有的实体上面(其实这个也不是非得在编辑器中操作,只是这样稍微简单一些),或者是要明确的控制脚本的执行顺序(比如在另一个在编辑器中绑定的脚本之前执行)

而大部分的脚本,我们完全可以采取运行时创建实体并绑定的方式添加到项目中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import * as pc from 'playcanvas';
import { TestScript } from './testsc';

export class ScriptManager extends pc.ScriptType {
  initialize() {
    const testManager = new pc.Entity('testManager');
    testManager.addComponent('script');
    testManager?.script?.create(TestScript); // create 方法同时支持传入字符串和类
    testManager?.script?.create('testScript');

    this.app.root.addChild(testManager);
  }
}

pc.registerScript(ScriptManager, 'scriptManager');

这种方式的缺陷就是,因为我们并非所有的脚本的都是这样添加的,这就导致我们没办法灵活的调整脚本的执行顺序,比如一个脚本是直接在web editor中绑定在root实体上的,我们没办法在这里创建两个脚本,一个在其前面执行,一个在其后面执行,只能要么都在前面或者要么都在后面执行(由scriptManager这个脚本的位置来决定)。不过对于顺序不敏感的脚本或者是不需要添加到已有的实体上的脚本,采用上面的方式我们便可便捷的添加新脚本,而无须与编辑器打交道。

playcanvas修改同步到本地(待更新)

github actions 构建与发布 (待更新)

comments powered by Disqus
本站访客数:
使用 Hugo 构建
主题 StackJimmy 设计