Unity 网络框架学习

👀 框架的选择

在这次开发之前, 我先去大概浏览了目前比较常用的网络框架, 像是:

UNet Mirror Netcode for GameObjects Netcode for Entities FishNet

以及 Photon 系列:

PUN (Photon Unity Networking), Photon Fushion, Photon Realtime

了解它们的设计理念以及使用方式, 同时也分析了自己项目的需求, 我的游戏项目是简单的卡牌对战, 不需要物理系统, 也没有运动同步, 因此对于实时性几乎没有要求, 而上述框架都是基于 Unity 做的, 或者和 Unity 深度绑定, 给我一种 Unity 专属网络框架的感觉

同时我对代码编写也有要求, 底线是客户端必须和服务端分离, 如果服务端能直接脱离 Unity 则更好, 另一个底线则是服务端必须拥有绝对权威性, 如果框架直接能做到这点则更好, 即使框架做不到, 我也会自己编码实现

在这两个底线的作用下, 我对于框架的选择就很纠结 ... 至少这些框架都或多或少不满足要求 ...

纠结的时间中我在 GDC 演讲中发现了一个新的框架, 叫做 SpacetimeDB, 准确来说不是网络框架, 而是一个专门为多人游戏设计的网络数据库, 但是它满足我的需求

🤩 STDB 的特点

因为我几乎没有使用服务端框架的经验, 仅仅是用 UNet 做了一个 FPS demo 的程度, 所以也别指望我会去对比上述框架, 我可没那个能力, 我就说下近期使用 STDB 的经验, 说下 STDB 的特点

第一 : 客户端和服务端的彻底分离

使用 STDB 进行开发的话, 服务端和客户端是完全分离的, 不仅仅是代码分离, 而是两个端压根不是一个项目

STDB 的服务端是一个 C# 项目, 当然你也可以选择别的语言, 这就意味着如果你是 Unity 开发者, 你的服务端将无法使用 Unity 的任何功能, 碰撞系统, 运动系统, 甚至 Update 生命周期都无法使用, 因为服务端是一个纯粹的 C# 项目

STDB 的客户端就随意了, 在我这里肯定就是 Unity 项目了, STDB 对 Unity 开发者挺友好的, 做了很多对 Unity 的支持, 包括两端数据模型的同步, Unity 插件支持等

第二 : Table + Reducer

服务端的编程模型是 Table + Reducer

Table 是数据, 可以简单理解为类似字典的一种结构, 如果你有数据库相关知识, 那这玩意儿就是数据库中的表, 一模一样的

STDB 提供了 Insert Delete Update 方法对数据进行处理

数据都是以 为单位来处理的: 插入一行, 删除一行, 修改一行

Reducer 则是逻辑, 那它和普通的逻辑有什么区别呢 ? 为什么非要额外定义一个 Reducer 呢 ? 这点就是我选择 STDB 的理由之一, Reducer 中对数据库所做的操作必定会全部生效或者全部失效, 绝对不会存在仅生效了一部分的情况

举个例子: 喝回血药剂, 执行此逻辑的方法内必然分为至少两个步骤, 一个步骤是玩家血量的修改, 另一个则是药剂库存的修改, 如果其中一个操作失败了, 另一个操作是不会自动回滚的, 你得自己编写逻辑来回滚数据: 如果回血失败了, 药剂也不能扣除, 得找回来, 或者药剂扣除失败了, 那前面回复的血量也得扣回去, 而 Reducer 就原生保证了这一点, 要么全部生效, 要么全部失效

第三 : 绝对的服务器权威

服务端全部的 Table 只能通过 Reducer 修改, 服务端不接受客户端的任何直接传值, 或者直接修改, 客户端是纯被动的, 一切状态, 一切数据都是服务端下发的

特点这种东西都是自己用了一段时间后的感受, 我这里说再多, 没用过的人也是感受不到的, 所以接下里直接开始讲解 STDB 的使用

🐬 Server : 安装 SpacetimeDB

以数据库 MySQL 为类比, 这一步就是在安装 MySQL 软件, 直接在任意 shell 中执行下面的命令即可安装

iwr https://windows.spacetimedb.com -useb | iex

如果你不想上面那样云里雾里地被安装, 也可以手动安装, 这是 STDB 的 Github 地址, 单击即可跳转

https://github.com/clockworklabs/SpacetimeDB

打开页面后, 转到 Release 页面, 可以看到下载列表

下载列表

STDB 分为两部分, 一部分是命令行程序, 用于命令行支持, 另一部分则是真正的 STDB 软件

  • 首先下载命令行程序 spacetimedb-update-x86_64-pc-windows-msvc.exe
  • 下载后重命名为 spacetime.exe
  • 将重命名后的 spacetime.exe 放置到自己的工具目录中, 配置好环境变量

环境变量配置到文件夹即可, 不需要配置到具体的程序

比如我放在了 E:\Tool\Spacetime\spacetime.exe 中, 环境变量则配置 E:\Tool\Spacetime 即可

完成上面的步骤就可以直接在命令行中使用 spacetime 命令了, 如下图, 我直接输入 spacetime

spacetime

可以看到命令可以正常识别到, 但是有报错, 提示没有在下面的目录中找到对应的程序, 所以我们继续操作

C:\Users\Administrator\AppData\Local\SpacetimeDB\bin\current\spacetimedb-cli.exe

  • 首先按照提示, 新建文件夹, 找到 C:\Users\Administrator\AppData\Local
  • 在里面新建 SpacetimeDB, 再新建 bin, 最后是新建 current
  • 这次下载 STDB 程序 spacetime-x86_64-pc-windows-msvc.zip
  • 下载后的压缩包解压后可以看到两个 exe 程序, 将这两个 exe 剪切到之前新建的 current 目录中

做完上述步骤后会这样

spacetime

此时再去执行 spacetime 命令就不会报错了, 比如执行 spacetime --version 命令可以查看版本号

注意 : 你可能会觉得 spacetime 只能放在 C 盘用户的 Local 目录下很蠢, 但是 ... 这个嘛, 我也只能说确实很蠢, 但我也没找到解决办法 (当然也不是完全不能解决), 只能先这样了

如果你了解 Windows 的软链接功能, 可以将 current 文件夹链接到自定义目录, 这样就可以实现将 spacetime 放置到自己的目录了

总结一下就是: 下载命令行程序, 为其配置环境变量, 下载执行程序, 将其放到指定目录, 结束!

🎄 Server : 启动 SpacetimeDB

默认情况下, 启动 STDB 只需要在命令行中输入 spacetime start 即可

但是这样启动后, 数据库的数据将会被保存到 Local 文件夹中, 如果你希望自己定义数据库文件的位置, 可以参考下面的命令

spacetime start --listen-addr 0.0.0.0:3000 --data-dir F:\U-BlackHole\Data

这样启动就是让 STDB 监听 3000 端口, 并将数据库文件放到 F:\U-BlackHole\Data

spacetime

关于 Server 配置 cli.toml 后面再讲, 现在无视即可

🍉 Server : 初始化服务端项目

进入到项目目录中, 务必先进入到项目目录中

之后执行命令 spacetime init --lang csharp Server

该命令会自动在当前目录下新建 Server 文件夹作为服务端, 并使用 C# 语言初始化此服务端项目

此时使用 Rider 或者 VS 等打开服务端项目应该是报错的, 因为环境还没有准备好

我的建议是进入 Server 文件夹内, 打开终端

运行 spacetime publish -s local black-hole 命令, 关于命令的含义请查阅文章最后的额外参考部分

这句命令会尝试编译并发布服务器到配置名称为 local 的 Server 上, 且数据模型会被命名为 black-hole

在编译过程中, 会自动检查环境配置, 需要安装的是 .net sdk 以及 wasi 工作负载

.net sdk 可能需要手动安装, 而 wasi 则会自动安装相应的版本, 不需要担心

当你成功发布数据模型后, 整个项目应该也就编译通过了, 至此, 服务端就可以开始编写代码了

🦴 Server : C# 服务端的编码

📦 表结构相关属性

属性 用法说明
[SpacetimeDB.[Table()]] 声明数据库表, Name 指定表名, Public 表示是否对客户端可见
(Name = "tb_config", Public = true) 仅可用于 partial classpartial struct
[SpacetimeDB.PrimaryKey] 标记主键字段, 每个表只能有一个, 自动具备唯一性
[SpacetimeDB.Unique] 标记字段为唯一约束, 可用于一个或多个字段
[SpacetimeDB.AutoInc] 标记字段为自增字段, 支持自动递增的整数类型, 常与主键或唯一字段搭配使用
[SpacetimeDB.Type] 声明可用于键的自定义类型, 仅用于 partial struct, 在索引键等场景中需要使用

🌲 索引相关属性

属性 用法说明
[SpacetimeDB.Index.BTree()] 创建 B-Tree 索引
(Name = "索引名", Columns = [字段1, 字段2...]) 支持单字段或多字段索引, 可自定义索引名称

🔁 Reducer(数据库事务函数)

属性 用法说明
[SpacetimeDB.Reducer] 声明 Reducer, 用于服务端逻辑处理, 必须作用于静态方法, 且第一个参数为 ReducerContext
[SpacetimeDB.Reducer(ReducerKind.Init)] 当模块首次上线或数据库重置时自动调用, 用于准备初始数据
[SpacetimeDB.Reducer(ReducerKind.ClientConnected)] 当客户端连接数据库时自动调用, 用于初始化该用户状态
[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)] 当客户端断开连接时自动调用, 用于清理该用户状态或释放资源

🦴 Server : 服务端编码注意事项

  • 服务端内的 .csproj 文件不能重命名, 必须使用 StdbModule.csproj (是不是很蠢 ? 是的, 巨蠢, 但没办法 ... 后续版本肯定会优化的吧, 肯定会吧? 肯定会吧!)
  • 全部的 Table 以及 Reducer 都必须在同一个 static partial class 类中, 所以可以看到服务端中全部的文件都用 public static partial class Module 包起来了
  • 默认数据库启动后会自动监听 0.0.0.0, 但客户端连接时可不能使用 0.0.0.0 作为地址, 因为 0.0.0.0 是一个特殊的非路由地址, 它表示的含义其实是 监听本机所有可用 IPv4 地址

🌴 Client : SpacetimeDB 的使用

首先需要导入插件包, 在 Unity Package 中添加 Git Package : https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk.git

之后新建一个全局物体挂载上 SpacetimeDBNetworkManager

仅此两步, 客户端配置完成, 可以开始编写客户端代码了!

📚 额外参考

STDB 中 Server 层概念的解释

Server 层是为了开发方便而设计的一层概念, 对客户端而言是无感的

设想一下, 我们目前操作的机器就是 spacetimedb 所在的机器, 我们可以使用 publish 命令直接发布数据模型

但是如果我们现在操作的机器并不是 spacetimedb 所在的机器呢, 我们的 spacetimedb 运行在远端, 比如阿里的服务器上, 我们是不可能跑到阿里机库那里操作电脑的

所以有了 Server 层的概念, 我们可以在

C:\Users\Administrator\AppData\Local\SpacetimeDB\config\cli.toml

文件中配置上远端服务端的地址, 再起一个名字, 这样就可以直接用自己的电脑发布数据模型到远端的 spacetimedb 上了

比如默认情况下, spacetimedb 都为我们提供了一个免费的远端, 叫做 maincloud, 你打开 cli.toml 就可以看到

我们总不可能跑到人家公司机库的电脑上输入命令吧, 所以这里我们可以使用 spacetime publish -y -c -s maincloud black-hole 命令

其中的 -s 就是选择 Server, 这样我们就可以将数据模型发布到 maincloud 上了

客户端在连接 maincloud 时使用 https://maincloud.spacetimedb.comwss://maincloud.spacetimedb.com 即可, 我这边测试过后完全没问题

而且 spacetimedb 还有一个后台可以使用, 登录 spacetime 后, 点击头像, 点击 Your Profile, 点击数据模型列表中右侧的大眼睛即可进入后台

spacetime

STDB 中关于命令行 SQL 的说明

STDB 直接在命令行中输入 SQL 命令来查询服务端数据, 具体情况可以使用 spacetime sql -h 查询

spacetime sql -s local black-hole "SELECT * FROM tb_player_logged_out"

.net 8.0 sdk 下载地址

https://dotnet.microsoft.com/en-us/download/dotnet/8.0

可以在 shell 中执行 dotnet --list-sdks 来检查当前已经安装了哪些 .Net SDK

安装 wasi 工作负载

直接在 shell 中执行 dotnet workload install wasi-experimental 即可

因为手动安装的版本不一定就是 STDB 需要的版本, 所以不建议手动安装工作负载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> Downloading Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.Msi.x64 (9.0.6)
> 正在安装 Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.Msi.x64 .... Done
> Downloading Microsoft.NETCore.App.Runtime.Mono.wasi-wasm.Msi.x64 (9.0.6)
> 正在安装 Microsoft.NETCore.App.Runtime.Mono.wasi-wasm.Msi.x64 ..... Done
> Downloading Microsoft.NET.Runtime.WebAssembly.Templates.net9.Msi.x64 (9.0.6)
> 正在安装 Microsoft.NET.Runtime.WebAssembly.Templates.net9.Msi.x64 ... Done
> Downloading Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.wasi-wasm.Msi.x64 (9.0.6)
> 正在安装 Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.wasi-wasm.Msi.x64 .... Done
> Downloading Microsoft.NET.Runtime.MonoAOTCompiler.Task.Msi.x64 (9.0.6)
> 正在安装 Microsoft.NET.Runtime.MonoAOTCompiler.Task.Msi.x64 .... Done
> Downloading Microsoft.NET.Runtime.MonoTargets.Sdk.Msi.x64 (9.0.6)
> 正在安装 Microsoft.NET.Runtime.MonoTargets.Sdk.Msi.x64 .... Done
>
> 已成功安装的工作负载 wasi-experimental。

STDB 命令行列表

https://spacetimedb.com/docs/cli-reference

generate 命令详解

generate 命令的作用是将服务端的数据模型生成一份代码到客户端, 这样客户端便可以和服务端共享数据模型

spacetime generate --lang <LANG> --out-dir <DIR> [--project-path <DIR> | --bin-path <PATH>]

  • -l, --lang <LANG> : 指定生成代码的语言, 如 csharp, typescript, rust
  • -o, --out-dir <DIR> : 生成的代码输出到哪个目录
  • -p, --project-path <DIR> : 服务端模块的项目路径, 默认是当前目录
  • -b, --bin-path <PATH> : 直接指定已编译的 wasm 文件路径
  • -y, --yes : 尽量保持非交互性, 自动回答选项, 大部分是 yes, 有时是 no, 比如是否使用 spacetimedb.com 登录时
  • --namespace <NAMESPACE> : 生成的代码所使用的命名空间, 默认是: SpacetimeDB.Types

示例: spacetime generate -l csharp -o ../Client/Assets/Assets/Script/Game.Core/Module

publish 命令解释

publish 命令的作用是编译服务端, 并将新的服务端发布到 Spacetime 中, 如果不存在同名服务端, 会自动新建, 如果已存在同名服务端, 则会自动更新

如果不知道当前有哪些数据模型已经发布了, 可以使用 list 命令查询: spacetime list

如果你想知道具体是如何解决版本数据冲突的, 请参阅官方文档中有关自动迁移的说明: https://spacetimedb.com/docs/modules/c-sharp#automatic-migrations

spacetime publish [OPTIONS] [name|identity]

  • [name|identity] : 数据库名或身份标识 (只能使用数字和小写字母和横线, 不能使用其他所有符号, 比如大写字母, 比如下划线)
  • -c, --delete-data : 若数据库已存在, 则先清空该数据库中所有数据再重新发布
  • --build-options <BUILD_OPTIONS> : 传递给构建工具的额外选项
  • -p, --project-path <PROJECT_PATH> : 指定项目路径, 默认当前路径
  • -b, --bin-path <WASM_FILE> : 直接指定已构建完成的 wasm 文件
  • --anonymous : 使用匿名身份执行发布, 不绑定任何已登录的账号
  • -s, --server <SERVER> : 指定目标服务器地址
  • -y, --yes : 跳过所有提示, 以非交互方式执行命令

示例: spacetime publish -y -c -s local black-hole

Could not find wasm-opt to optimise the module 怎么解决

wasm-opt 是 Binaryen 项目提供的一个 WebAssembly 优化工具,它可以减小 .wasm 文件大小, 优化指令执行顺序, 提高浏览器的运行效率

如果你要打包 WebGL 游戏, 可以安装 wasm-opt, 安装过程非常简单, 前往发布页: https://github.com/WebAssembly/binaryen/releases

下载对应操作系统的预编译版本, 解压后,把 wasm-opt 放到你的 PATH 环境变量中

可以用我的做参考, 我将解压后的文件夹命名为 wasm-opt 所以我环境变量中配置的是: E:\Tool\wasm-opt\bin

dotnet 识别不到怎么办

dotnet 的默认安装路径都是 C:\Program Files\dotnet

除非你是 32 位电脑, 那么在这个目录: C:\Program Files (x86)\dotnet

但是这基本不可能了对吧, 现在还有谁的电脑是 32 位呢?

所以直接在环境变量中配置 C:\Program Files\dotnet 即可识别到

常用命令

  • spacetime start --listen-addr 0.0.0.0:3000 --data-dir F:\U-BlackHole\Data
  • spacetime generate -l csharp -o ../Client/Assets/Assets/Script/Game.Core/Module
  • spacetime publish -y -c -s local black-hole
  • spacetime publish -y -c -s maincloud black-hole
  • spacetime list -s local
  • spacetime logs -s local black-hole
  • spacetime sql -s local black-hole "SELECT * FROM tb_player_logged_out"

参考链接

  • https://spacetimedb.com/docs
  • https://github.com/clockworklabs/SpacetimeDB
  • https://github.com/ClockworkLabs/Blackholio
  • https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk
  • https://github.com/WebAssembly/binaryen/releases