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
可以看到命令可以正常识别到, 但是有报错, 提示没有在下面的目录中找到对应的程序, 所以我们继续操作
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 --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
中
关于 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 class 和 partial 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.com
或 wss://maincloud.spacetimedb.com
即可, 我这边测试过后完全没问题
而且 spacetimedb 还有一个后台可以使用, 登录 spacetime 后, 点击头像, 点击 Your Profile, 点击数据模型列表中右侧的大眼睛即可进入后台
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 | Downloading Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.Msi.x64 (9.0.6) |
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