在浏览器中游玩!
动态激烈的在线射击游戏。
挑战你的朋友进行激烈的决斗,或者集结两个阵营展开一场惊心动魄的战争。
使用现代C++编写,无需游戏引擎!
永远免费且开源 :heart:
加入我们:
在Steam上获取!
下载游戏
仅29 MB!
游戏将从互动教程开始!
下载 无头专用服务器
媒体报道
Liam @ GamingOnLinux
GitHub博客
Open Source Friend (200票,20k浏览量)
📰 你是记者吗?点击这里!
观看游戏视频
学习制作地图!
最新开发者日志
目录
简介
Hypersomnia是一款以免费软件形式发布的竞技场游戏。
该游戏自2017年起就已上线并可游玩。它融合了以下元素:
- 反恐精英的战术性,
- Hotline Miami的动态性,
- 老式RPG的像素艺术怀旧风格,
- 以及得益于游戏内地图编辑器带来的无限创造潜力!
Hypersomnia旨在成为终极开源2D射击游戏 - 一个可无限扩展的宏大社区项目。
宣誓效忠于三大阵营之一,他们因道德卓越观念的分歧而产生纷争:
大都会。亚特兰蒂斯。抵抗组织。
你是否会为模拟劣等宇宙的不道德行为复仇? 你是否会支持残酷实验以赢得对轮回的完全控制? 或者你会加入地下文明,在这个危险的来世现实中等待战争的结束?
特性
-
- 外加4种手榴弹、7种近战武器以及6种魔法咒语!
-
10张社区地图,数量还在增加中!
-
2种游戏模式:拆弹和军备竞赛。
-
游戏内地图编辑器让你可以托管正在制作的地图,立即与朋友一起游玩,甚至可以穿透路由器!
-
你的朋友将自动下载你的地图及其所有自定义资源!
(就像CS 1.6一样)
-
技术亮点
-
为了打包纹理而编写的 rectpack2D 变得非常有名,并被用于 刺客信条:英灵殿。
-
我在2013年提出的实体组件系统**想法描述的技术与Unity引擎在2018年的专利**类似。
-
网络功能基于跨平台模拟确定性。它在浏览器和原生客户端(Windows、Linux、MacOS)之间100%确定,甚至包括原生ARM构建(aarch64)!
- 这种技术传统上被RTS游戏用于处理数百个持续移动的士兵单位。
- 通过网络持续更新每个单位是不切实际的。
- 相反,只传输玩家输入("我把鼠标移到这里","我按了这个按钮")- 客户端在本地模拟其他所有内容。想象一下通过电话与朋友下棋。你永远不会大声说出整个棋盘的状态,只说出动作("皇后移到H5")。
- 但是Hypersomnia不是RTS - 由于它是基于物理的,它大量使用浮点数,而不仅仅是整数。
- 当涉及浮点计算时,模拟确定性变得极其困难。
- 在这篇Glenn Fiedler的优秀文章中可以了解原因。
- 为了在Hypersomnia中实现这一点,我必须:
- 在所有操作系统上使用相同的编译器 -
clang
。LLVM默认符合ieee754标准真是太好了。 - 为Windows构建传递
/fp:strict
。 - 为ARM构建传递
-ffp-model=strict
。 - 用STREFLOP中的函数替换所有数学函数,如
std::sin
、std::sqrt
。 - 之后
streflop::sqrt
成为了一个巨大的瓶颈 - 幸运的是,我找到了另一个高效的符合ieee754标准的实现。
- 在所有操作系统上使用相同的编译器 -
- 当涉及浮点计算时,模拟确定性变得极其困难。
- 除了担心浮点数之外..
- 我甚至在迭代
std::unordered_map
时也必须小心 - 经常用确定性排序的std::map
替换它们。 - 我必须使用可移植的RNG(xorshift),放弃整个
<random>
头文件(其实现在不同操作系统上有所不同)。
- 我甚至在迭代
- 还需要一个神奇的技巧来确保物理本身完全确定:
- 问题:当新客户端连接时,它会收到初始物理状态 - 但数据包只包含物体的位置、旋转和速度。
- 特别是,它不包含"热"物理状态,如已跟踪的接触点,或已构建/平衡的用于快速碰撞检测的四叉树。
- 如果服务器上已经有一些玩家,并且在新玩家加入时有几个物体重叠怎么办?
- 新客户端将仅使用位置/速度/旋转数据重新创建整个内部物理状态,但接触点可能以与现有客户端不同的顺序创建!新的四叉树也可能与经历了长时间游戏会话中多次插入/删除的四叉树完全不同 - 所以它后来可能会以不同的顺序报告碰撞。
- 我的解决方案是我经历过的最大的顿悟之一:
- 当新客户端连接时,强制已连接的客户端从位置、旋转和速度完全重建物理状态,就好像它们刚刚连接一样。 放下麦克风
- 只要从位置/速度/旋转数据构建内部物理数据是确定性的,游戏中的客户端将与新连接的客户端有完全相同的状态。
- 我在网络的许多其他领域也应用了相同的原则。在我的代码库中,我称这个过程为*缓存重推断*。
- 在商业引擎中实现这一切将非常困难,在闭源引擎中更是完全不可能。
- 然而在Hypersomnia中,你可以在地图上有成千上万的动态箱子或子弹 - 只要有两个玩家控制的角色,在60 Hz的刷新率下,流量将约为
40 kbit/s(= 5 KB/s)
。只有玩家控制的角色会产生流量。- 即使是最微小的细节,比如子弹壳,也能完全同步,而不会增加网络流量。
- 作为额外好处,延迟被极好地隐藏了,因为游戏不只是盲目地在视觉上"外推"帧 - 而是在屏幕外向前模拟整个游戏世界,以准确预测未来。
- 对于非常好奇的人 - 这里有一篇来自我废弃的旧博客的文章,展示了我早在2016年就是如何实现这一点的。
- 这种技术传统上被RTS游戏用于处理数百个持续移动的士兵单位。
-
浏览器客户端和原生客户端可以在同一服务器上玩游戏,这要归功于libdatachannel和datachannel-wasm。
- 你可以在浏览器中托管服务器,并用原生客户端连接到它...
- ...反之亦然:在原生客户端中托管服务器,并发送链接给任何人从浏览器加入!
-
在浏览器版本中,你可以使用Discord登录并进行排位比赛,以在全球排行榜上竞争!
- 你还可以将你的Discord账号与Steam账号关联,这样Steam版本和网页版本共享相同的评分!
-
你可以从主菜单就托管一个工作的游戏服务器 - 游戏能够开箱即用地转发端口!
- 该算法需要第三方服务器 - 在这种情况下,主服务器(服务器列表保管者)促进穿透。
- 原理很简单。使用Google的STUN服务器,我们首先检测客户端和服务器的NAT类型(圆锥型、对称型或无NAT),以及发送到两个不同地址的数据包之间的端口差异("端口增量")。
- 双方向主服务器发送数据包,主服务器反过来中继另一方最后的外部端口
P
。 - 然后双方在端口
P + 增量 * n
上发送例如10-20个UDP数据包。 - 即使是一对对称NAT也能形成连接,只要它们有确定性的端口增量 - 尽管可能需要一段时间。如果客户端或服务器有随机端口选择的对称NAT,连接将失败。但实际上,大多数路由器都是圆锥型的,这使得穿透能够立即工作。
-
可爱的鱼类和昆虫AI**具有群集行为。** 完整源码:movement_path_system.cpp。
- 这些也通过网络同步!
- 其他玩家看到的鱼类和昆虫位置相同,尽管它们不会增加网络流量。
- 鱼类和昆虫会对射击和爆炸做出反应!
- 一旦它们受到惊吓,就会更靠近同类。
- 这些也通过网络同步!
-
任何人都可以托管**整个Hypersomnia服务器基础设施。**
- 编辑器、游戏服务器和主服务器(服务器列表保管者)都嵌入在同一个游戏可执行文件中,适用于每个操作系统。
- 你可以为你自己的社区运行一个单独的服务器列表,围绕一个完全修改过的Hypersomnia版本!
-
内存池实现具有:
- 连续存储。
- 所有存在的游戏对象都线性存储在内存中(在一个简单的
std::vector
中)。- 这意味着可以极快地迭代所有相同类型的游戏对象,并且池序列化也变得简单。
- 所有存在的游戏对象都线性存储在内存中(在一个简单的
- **O(1)**分配(主要是一个高级的
push_back
)。 - **O(1)**释放(通过与最后一个元素
std::swap
并pop_back
- 一个众所周知的习语)。 - **O(1)**解引用。
- 唯一的缺点是:解引用涉及四次内存读取(而不是直接指针的一次或指针+数组索引的两次):
- 它首先必须读取
indirection_array
指针**(1)**。- 间接数组包含请求对象在所有已分配对象向量中的当前索引。
- 这个索引位于
indirection_array[identifier]
(2)。
- 然后它读取实际的
objects
向量指针**(3)**。
- 连续存储。
-
最后,它会在
objects[indirection_array[identifier]]
处读取对象本身**(4)**。 -
(请注意,
objects
和indirection_array
通常已经被缓存,所以后续的解引用最多只需从内存中获取两次)。 -
这之所以有效,是因为每当对象被分配或移除时,
indirection_array
都会保持最新状态。 -
完全可调整大小。你不需要指定最大池大小。它会自行扩展。
-
完全确定性。从给定的初始池状态开始,给定相同的分配和释放序列,分配将产生相同的整数标识符,对象在线性内存中的顺序也将完全相同。
- 当客户端连接,服务器发送初始世界状态时,它会包含游戏对象池的整个内部状态。
- 这意味着池本身就是游戏模拟的一部分 - 我不需要在实体创建时通过网络发送任何"创建"通知事件,因为客户端会确定性地模拟自己的分配,从而得到相同的对象标识符以及它们在内存中的顺序。
- 当客户端连接,服务器发送初始世界状态时,它会包含游戏对象池的整个内部状态。
-
完美可撤销的分配和释放。
- 编辑器要求所有命令实现确定性的撤销/重做,特别是那些创建或删除资源的命令。
- 假设你在编辑器中创建了一个自定义材质,现在它有了一个整数标识符
I
。- 之后你将这个材质应用到场景中的一些墙上。这些墙现在引用标识符
I
。 - 现在你想撤销所有操作,回到甚至创建材质之前的状态。
- 材质从池中被删除。
- 现在你实际上想要重做所有这些命令。
- 你首先重做材质分配命令。
- 但如果池使用的是不可撤销的分配方案,材质现在可能会被分配到
I+1
的id。 - 这意味着后续的命令(改变墙的材质)将设置一个现在无效的标识符 -
I
。 - 为了解决这个问题,我的内存池实现除了标准的
free
之外还提供了undo_last_allocate
。它被那些需要撤销创建资源(即删除它)的命令使用。它会以这样一种方式释放对象:下一次分配将产生与释放资源之前相同的整数id和内部池状态(包括已分配对象的顺序)。这是为了编辑器命令而设计的可选功能,在实际游戏过程中从不使用。类似地,还有一个undo_last_free
。
- 之后你将这个材质应用到场景中的一些墙上。这些墙现在引用标识符
-
内置自动更新器:游戏将自动下载并应用更新。
-
不仅如此,它还会验证更新是否来自硬编码的开发者公钥,通过调用
ssh-keygen
。- 如果构建托管被黑客入侵并上传了恶意游戏版本,现有的游戏客户端将拒绝应用更新。
-
..而且我正在离线使用Trezor硬件钱包签名构建。 这是我这边每次更新的样子:
-
在以下情况下发送Discord和Telegram通知:
-
部署新游戏版本时,
-
玩家连接时,
-
1v1"荣誉决斗"开始时(当每个阵营只有1名玩家时自动检测),
-
社区服务器启动时,
-
或比赛结束时。这包括完整的玩家统计数据和MVP。
-
-
游戏地图使用简洁优雅的JSON格式。这段简短的json:
{ "settings": { "ambient_light_color": [53, 25, 102, 255] }, "nodes": [ { "id": "floor", "type": "dev_floor_128", "pos": [640, 0], "size": [1536, 1536] }, { "id": "light", "type": "point_light", "color": [255, 165, 0, 255], "pos": [100, 100], "radius": 700 }, { "id": "wall", "type": "dev_wall_128", "pos": [-384, 0], "size": [512, 1536] }, { "id": "crate", "type": "crate", "pos": [64, 448] }, { "id": "wood1", "type": "hard_wooden_wall", "pos": [192, -192] }, { "id": "wood2", "type": "hard_wooden_wall", "pos": [561.819, 206.436], "size": [384, 128], "rotation": -15 }, { "id": "aquarium", "type": "aquarium", "pos": [1408, 0], "rotation": 90 }, { "id": "baka1", "type": "baka47", "pos": [698, -8] }, { "id": "baka2", "type": "baka47", "pos": [454, -264] } ] }
立即生成:
-
说到编辑器..
-
它是用优秀的ImGui创建的。
-
你直接在游戏世界中工作。100%所见即所得。
-
支持自定义资源。只需将包含PNG、WAV、OGG的文件夹粘贴到地图目录中..
- ..然后切换回游戏,它就会自动识别!
-
也支持GIF!只需将它们拖放到场景中,它们就会在游戏中自动播放动画,开箱即用!
-
可以通过单击来测试正在进行中的地图:
-
之后你会立即出现在游戏的服务器浏览器中:
-
你将以主机身份进入游戏。
-
连接的客户端将自动下载当前版本的地图及其所有自定义资源。
- 之后他们还可以创建自己的重制版——毕竟地图是以JSON格式保存的!
-
按ESC键可以停止会话并返回到编辑器,精确地回到你离开时的状态,实现超高效的迭代周期。
-
这是可能的,因为服务器、游戏和编辑器都在同一个可执行文件中。
-
官方Discord也会收到你正在测试地图的通知,这样其他人也可以加入一起玩!
-
-
编辑器花了一年多的时间来实现。
-
这项投资获得了巨大回报——现在有了一个整洁的社区地图目录——每张地图都可以下载。
-
背景
Hypersomnia自2013年开始开发(从提交历史可以看出)。
不过并不是连续10年不间断的编码——在此期间,我从事商业工作以支付生活费用。我攒钱以便能少工作,专注于Hypersomnia。我的财务决策现在让我能全职开发这款游戏。
我使用了很多第三方库,如Box2D
(物理引擎)或yojimbo
(传输层)——但是,这个列表之外的所有内容基本上都是从头开始用纯C++编写的。
许多人认为不使用引擎编写游戏无异于重新发明轮子,或者更直白地说,完全是浪费时间。
我希望这个项目能成为反驳这种观点的有力证明。
如果我从未踏上这段旅程,我就永远不会做出技术亮点部分中详述的一些有趣发现。 视频游戏内部机制是如此广泛和跨学科,以至于它们拥有无限的创新突破潜力,而如果我们从不考虑一些广泛使用的解决方案可能被绝对巧妙的新方法取代,那将是一种浪费。
快速游戏说明
来源:https://hypersomnia.xyz/guide
你可以通过进入设置->控制选项卡来重新配置所有按键绑定。
- WSAD:移动
- Shift - 冲刺,会消耗耐力
- 空格键 - 冲刺,会消耗大量耐力
- 左Ctrl - 安静移动
- B - 打开商店。通常在热身阶段商店不可用
- M - 更换队伍。建议选择自动分配以保持平衡
- G - 丢弃最近使用的物品
- H - 隐藏最近使用的物品。如果背包没有空间可能会失败!
- E - 拾取物品/拆除炸弹。拆弹时移动会重置计时器,所以要保持静止。可以开枪,但记住要保持触发器在炸弹范围内,必须接触到
- C - 拿出炸弹(作为恐怖分子)
- 滚轮向下 - 快速投掷小刀(或其他近战武器)。注意在任何活动中都可以投掷小刀,即使在换弹或安放炸弹时
- 滚轮向上 - 快速投掷两把小刀(或其他近战武器)
- 鼠标中键 - 快速投掷力场手雷
- 鼠标侧键4 - 快速投掷闪光弹
- 鼠标侧键5 - 快速投掷PED手雷(摧毁个人电力装置)
- Q - 快速切换到最近使用的武器或其他可用武器。如果当前武器无法放入背包可能会失败!
- 0, 1, 2, ..., 9 - 从快捷栏选择武器
- TAB - 显示比赛统计,如玩家和分数
- F8 - 服务器管理面板,可以更换地图或重启比赛
- 波浪号(~) - 释放鼠标光标以与GUI交互。不常用,但如果想从背包中丢弃特定物品很有用 - 只需在拖动时按右键。再次按波浪号返回游戏并重新控制准星
- 鼠标左键:
- 使用右手物品,即:
- 炸弹:安放
- 手雷:拔掉保险
- 枪支:射击
- 近战武器:大幅度挥动
- 鼠标右键 - 使用左手物品,或右手物品的次要功能(仅当左手空闲时),例如AO44左轮手枪的连发射击。近战武器则是小幅度有力挥动。拔掉手雷保险以扔在脚下,而不是远距离投掷
如何构建
目前,Hypersomnia 只能使用 clang
构建。
你的操作系统必须是64位的。
无论使用何种操作系统,你都需要以下软件来构建 Hypersomnia:
- 最新版的 CMake
- git 用于克隆仓库并生成版本信息
- ninja 用于执行构建
- LLVM 工具链版本15
- Windows 用户可以使用这个安装程序,或更新版本
- Linux 用户使用特定发行版的软件包。确保同时安装
libc++
、libc++abi
和lld
- MacOS 用户可以使用 Xcode 预装的版本
- OpenSSL 自动更新程序需要通过HTTPS下载最新游戏二进制文件
- Windows 用户可以在这里获取适当的安装程序:https://slproweb.com/download/Win64OpenSSL-3_2_2.msi
- 链接失效?其他 OpenSSL 版本可以在这里找到:https://slproweb.com/products/Win32OpenSSL.html
- 游戏通常可以使用 OpenSSL 3.0 或更高版本构建
- 如果 CMake 无法找到 OpenSSL(尽管传递了正确的
-DOPENSSL_ROOT_DIR=C:\OpenSSL-v32-Win64
),请确保更新 CMake。最近有很多 CMake 无法找到 OpenSSL 库的问题,但最新的 CMake 应该已经可以正常工作
- 链接失效?其他 OpenSSL 版本可以在这里找到:https://slproweb.com/products/Win32OpenSSL.html
- Windows 用户可以在这里获取适当的安装程序:https://slproweb.com/download/Win64OpenSSL-3_2_2.msi
安装依赖项后,进入你想要下载 Hypersomnia 项目的目录, 打开 git bash 并粘贴:
git clone --recurse-submodules https://github.com/TeamHypersomnia/Hypersomnia
--recurse-submodules
参数是必要的,用于同时克隆子模块。
等待下载完成。 接下来的步骤取决于你所使用的平台。
在所有平台上,你可以选择以下三种构建配置:
Debug
- 构建最快,提供调试信息。 推荐用于日常开发。Release
- 无调试信息。仅用于生产构建。指定IS_PRODUCTION_BUILD=1
C++ 预处理器定义,禁用性能关键区域的断言。RelWithDebInfo
- 与Release
相同,但包含调试信息并编译了许多断言("确保")。 在需要全速运行的情况下进行游戏开发测试的首选。
Windows 说明
请参考 appveyor.yml 文件获取最新的构建程序。以下是过程的简要概述。
先决条件:
- Visual Studio 2022 Community 或更新版本。
打开标准的 Windows cmd
提示符(不能使用 PowerShell 或其他)。设置环境:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
(如果找不到该文件,说明你没有使用 Visual Studio 2022 Community。你需要自行寻找对应的 vcvarsall.bat
位置。)
接下来,运行以下命令:
cd Hypersomnia
mkdir build
cd build
set CONFIGURATION=RelWithDebInfo
cmake -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_LINKER=lld-link -DARCHITECTURE="x64" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DGENERATE_DEBUG_INFORMATION=0 -DOPENSSL_ROOT_DIR=C:\OpenSSL-Win64 ..
ninja
这将默认构建非 Steam 客户端。
要构建 Steam 客户端,请在 cmake
命令中添加 -DLINK_STEAM_INTEGRATION=1
标志。
注意:在构建期间,你的电脑可能会严重卡顿,因为 ninja
将使用所有可用的核心进行编译。
如果你打算开发游戏,最好使用"Debug"配置以获得最快的构建速度。
如果你想自定义构建,例如禁用某些游戏功能,请参考 CMakeLists.txt
的开头,查看可以传递给 cmake
命令的选项。
如果游戏构建成功,运行以下命令启动:
ninja run
如果你使用 -DLINK_STEAM_INTEGRATION=1
构建,别忘了创建一个 hypersomnia/steam_appid.txt
文件,内容为 2660970
。
否则游戏将尝试通过 Steam 重新启动自身。
如果由于某些原因某个步骤失败,请参考最新的可用 Appveyor 构建和相关的 appveyor.yml
文件。
Linux 说明
请参考 Linux_build.yml 文件获取最新的构建程序 - 它一直在变化。以下是过程的简要概述。
当前积极测试和支持的平台:
- 使用 i3 窗口管理器的 Arch Linux - 开发者的机器。
- Ubuntu,因为这是部署专用服务器的地方。
特定发行版的依赖项
Arch Linux:
libc++ lld pkg-config libx11 libxcb xcb-util-keysyms libsodium
- 可能需要更多 - 如果此列表缺少什么,请告诉我们。
Ubuntu:
sudo apt-get install cmake ninja-build libxcb-keysyms1 libxcb-keysyms1-dev libxi6 libxi-dev alsa-oss osspd-alsa osspd libasound2 libasound2-dev p7zip p7zip-full libgl1-mesa-dev libxcb-glx0-dev libx11-xcb-dev
一键启动
一旦所有依赖项都设置好,这是从头开始构建和启动游戏的完整脚本,使用 RelWithDebInfo 配置:
git clone --depth 1 --recurse-submodules https://github.com/TeamHypersomnia/Hypersomnia
cd Hypersomnia
export CXX=clang++; export CC=clang;
cmake/build.sh RelWithDebInfo x64
ninja run -C build/current
详细说明
使用你喜欢的 shell 进入仓库目录。 然后运行:
cmake/build.sh [Debug|Release|RelWithDebInfo] [x86|x64] ["附加 CMAKE 标志"]
例如:
export CXX=clang++; export CC=clang;
cmake/build.sh Debug x64
之后,生成的 build.ninja
文件应该出现在 build/Debug-x64-clang 目录中。
gcc 的示例:
export CXX=g++; export CC=gcc;
cmake/build.sh Debug x64
之后,生成的 build.ninja
文件应该出现在 build/Debug-x64-gcc 目录中。
调用 ninja
定义了几个额外的 ninja 目标:
ninja run
正常启动游戏。
ninja tests
仅运行单元测试并正常退出。
上述目标会自动将工作目录设置为 ${PROJECT_SOURCE_DIR}/hypersomnia
。
如果由于某些原因某个步骤失败,请参考最新的可用 Linux_build.yml
文件。
如果游戏无法启动,它应该会使用 $VISUAL
可执行文件自动打开一个包含相关信息的日志文件。
文件对话框集成
你可能需要进行一些额外配置以在 Linux 上获得更好的体验。
打开和显示文件
Hypersomnia 编辑器可以在文件浏览器中显示文件。
此外,游戏可能会提示你选择一个文件,例如选择头像。
在 Windows 上,这是通过 IFileDialog
实现的。
不用说,Linux 上并不存在这样的类。
Hypersomnia 在 hypersomnia/detail/unix/managers
中为常见的文件管理器提供了 shell 脚本。
你需要一个用于选择要打开的文件的脚本,以及一个用于在文件浏览器中显示文件的脚本。
cd
到 hypersomnia/detail/unix
,假设你想使用 ranger
作为文件管理器,创建如下符号链接:
ln -s managers/reveal_file_ranger.zsh reveal_file.local
ln -s managers/open_file_ranger.zsh open_file.local
所有符号链接都不会被 git 追踪。
目前支持以下文件管理器:
- ranger,通过
--choosefile
和--selectfile
选项
MacOS 说明
有关最新说明,请参阅 MacOS_build.yml 文件。
贡献
欢迎提交拉取请求,哪怕是修复拼写错误或添加缺失的 const 保证。 如果你计划添加一个全新的功能,请创建一个相关的议题,以便每个人都知道这件事, 因为该项目正在持续进行非常活跃的开发,可能会在最意想不到的时候发生革命性变化。
可以在wiki上找到正在编写的文档。
确保查看 TeamHypersomnia 以了解其他在设置自定义服务器时有用的仓库。
如果你有问题或无法构建 Hypersomnia,请在 Discord 上联系我们。 或者如果你迫不及待地想表达一些关于游戏的绝妙想法,也请这样做!