[教程][Headless] 使用 NodeJs 运行无渲染的Cocos项目

[Headless模式]简单介绍:

了解过Unity 或者 UE4 的可以知道在编译当中有一个服务器模式(无渲染只有逻辑) 可将自己的项目编译出一个服务端让项目像Java或者Python一样运行 当然就可以运行在无桌面的Liunx系统中!

Unity - 手动:专用服务器

设置专用服务器 | 虚幻引擎文档 (unrealengine.com)

用途来源

一般适用联机当中 我在这里举例两个场景:

帧同步(参考王者荣耀)

帧同步是一个通过执行过程的一种同步方式这里举例一个易懂的例子

假设有两个玩家 累加一个数值这里从 0 开始 每一次接收到帧消息我都累加 1 服务端发送十次最终他们两得出的结果都是 10 这就是帧同步

帧同步有一个弊端就是 有一个玩家 使用了外挂将原本 +1 的修改成 +2 这时候 服务器就会接受到两个不同的结果一个是10 一个是 20 服务器这时不知道取谁的结果

解决方法

目前常用的解决方法有两种 第一种多人裁决 第二种服务器裁决

多人裁决顾名思义 就是 人多获胜 比如三个人 如果两个人得出结果是10 另一个得出结果是20 则选择人多的 10 缺点: 只适用于外挂少于正常玩家的时候 如果相等 或者 大于正常玩家都是会裁决错误

服务器裁决(Headless模式) 就是服务器自己决定结果 可以决定结果的前提是 服务器通过客户端的运行方式得出一样的正确结果 但是在实战中项目都是比较复杂多变的 比如 使用了物理引擎,动画系统,寻路… 尤其一些动作游戏 根本是不可能在服务端写一份一样的 所以需要 Headless模式 将项目打包成服务器 这样服务器就可以一模一样的运行客户端的逻辑去裁决 这种模式唯一缺点就是吃服务器CPU 优点就是可以知道那些玩家开挂进行封号

帧同步正常流程: 帧同步开始 - 玩家游戏过程 - 帧同步结束 - 服务器追帧快速得出胜利方 - 通知最终战斗结果

状态同步(参考 逆水寒 我的世界服务器)

状态同步是服务器模拟游戏 玩家实时显示服务器模拟好的内容

常用在 MMO 游戏中

目前实现方式有三种 第一种棋牌模式[状态同步] 第二种同步位置模式[状态帧同步] 第三种 Headless模式[状态同步]

棋牌模式 只适用像棋牌一样简单的游戏 服务器可以完全写出逻辑 客户端进行显示 如果游戏复杂则不适应 因为服务器比较难写出 物理引擎,动画系统,模型… 并且很难定位到错误

同步位置模式[状态帧同步] 这是通过每帧像服务器上报自己的状态 服务器将玩家状态转发给所有玩家的一种简单 方式 很多人将它成为状态同步 其实这是错误叫法 因为服务器没有模拟游戏内容 在这里我称它为 状态帧同步 缺点很多 如很难显示完美的物理效果,开挂 … 我就不举例了

Headless模式[状态同步] 将游戏编译成服务器 这样服务器就可以模拟游戏内容 比如 物理引擎,动画系统,AI… 都只需要游戏引擎中写好 就可以打包出来 客户端只需要显示服务器模拟的状态即可 目前做的最好的游戏引擎就是 虚幻引擎

Cocos 实现 Headless 模式

上面介绍这么多 大家应该知道 Headless 模式在联机中重要的地位了吧 现在就开始教大家 如何将自己项目打包成服务器

(温馨提示:本人不怎么会Nodejs 所以使用比较生疏 有优化的地方大家都要告诉我哈)

1.新建 headless 文件夹

2.创建 headless nodejs 项目


npm init

3.导入必要依赖 (jsdom 模拟DOM) (gl 模拟WebGL)


npm install -g cnpm

cnpm install --save jsdom gl canvas xmlhttprequest node-fetch systemjs

4.在package.json scripts增加启动入口


"main": "node index.js",

5.创建 index.js 文件 复制以下代码

//这里替换HTML URL 地址 须 index.html 结尾
const URL = "http://192.168.1.23:7456/web-desktop/web-desktop/index.html"

const { JSDOM,ResourceLoader } = require('jsdom')
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));

const WebGL = require('gl');
const WebGLRenderingContext = WebGL.WebGLRenderingContext;
WebGLRenderingContext.prototype.texSubImage2D = function(){};
const gl = WebGL(64, 64, { preserveDrawingBuffer: true });

const resourceLoader = new ResourceLoader({
    proxy: URL,
    strictSSL: false,
    userAgent: "Mellblomenator/9000",
});

fetch(URL).then(res => res.text()).then(html => {
    console.log("HTML Index 文件: ",html)
    global.window = (new JSDOM(html, { 
        url: URL,
        referrer: URL,
        contentType: "text/html",
        resources:resourceLoader,
        storageQuota: 10000000 , 
        runScripts: "dangerously"
    })).window;
    
    let HGContext = window.HTMLCanvasElement.prototype.getContext;
    window.TextDecoder = global.TextDecoder;
    window.HTMLCanvasElement.prototype.getContext = function(type,data) {
        if(type == "2d") return HGContext.bind(this)(type,data);
        return gl;
    };
    window.requestAnimationFrame = global.requestAnimationFrame;
    window.fetch = fetch;
})

setTimeout(() => {}, 99999999);

6.修改引擎( 2处 ) [可选]
image
image

7.Cocos 打包 HTML

image

8.修改模式 [可选]
image

9.将index.js 代码中的 URL 替换 H5 URL 地址

10.执行 npm run main 运行

image

案例链接

headless.rar (1.0 KB)
测试H5链接 (有模型我的服务器带宽低所以加载久一点) : Cocos Creator | DemoHeadless (jisol.cn)

更多

2.x: 我做了一些调整支持2.x版本,只测试了2.4.11

在Headless中 使用 画布 会导致 2D坐标 出现 NaN: 在Headless中 使用 画布 会导致 2D坐标 出现 NaN 解决方法:去除画布中所有的Widget 将 Canvas 的 Align Canavs With Screen 勾选去除

在Nodejs使用axios: 在Nodejs使用axios 响应体错误

15赞

牛逼plus,尝尝鲜

貌似勾选擦除模块 可以不需要改模式和引擎 下班了 我没时间试 你帮我试试…

我先跑一下试试

可以记得过来冒泡哈

(帖子被作者删除,如无标记将在 24 小时后自动删除)

我用的是3.8.0,放到nodejs项目里面运行到某一行停了我一会打个断点试试为啥子1 正常应该是:2

wasm 你可以切换其他物理引擎 不用wasm 当然你可以看看怎么让nodejs加载wasm

我再试试模块擦除的试试

1赞

反正我是直接把wasm 切成 内置

嗯嗯,应该是wasm加载的问题

我那个demo 压缩包 应该可以运行吧

(帖子被作者删除,如无标记将在 24 小时后自动删除)

公司电脑build呼呼的,回家再试试,下班下班,多谢大佬分享

非常好,建议官方引入

(帖子被作者删除,如无标记将在 24 小时后自动删除)

1赞

昨天我改了一个写法好像不需要改模式和引擎 也可以加载wasm了

nodejs 加载的吗

index.js改这个

1赞

性能这些指标有测试过没有,服务器压力大不大