写点什么

Rust 元宇宙 1 —— 创世纪

作者:Miracle
  • 2021 年 11 月 29 日
  • 本文字数:2361 字

    阅读完需:约 8 分钟

Rust 元宇宙 1 —— 创世纪

Why Rust ?

为什么选择 Rust 语言,这是一个复杂的设计决策,从无数考虑的理由中,我选择下面三个,作为我自己认为最重要,最有说服力的理由。


安全

我的第一个 Rust 程序是 360 游戏开放平台的一个广告匹配系统,可以认为是一个简化版的竞价排名,这是我一次写 rust 程序,而且那个时候,rust 的编译器远没有今天那么智能(成功的去掉了大多数生命周期的定义),成功编译花了整整一个月的时间,神奇的是,一次性上线之后,在数个月的正式环境运行过程中,这个简单的 daemon 甚至坚持到最终机房搬迁,一次 crash 也没有。


强大的表达能力

利用 trait 和 现代函数设计语言的 N 多模式,充分利用 fold,iter,map,collect 等等函数表达式,我们能够将注意力的焦点从数据结构的操作切换到数据结构本身,比如说 map 使得我们可以对结构整体进行变换和处理,而不用去管内部的遍历器和算子是如何起作用的。我们的实践表明,Rust 能够比 C/C++节省 2/3 的代码量。


性能

这对硬件厂商不是一个好消息,我们的实践再次表明,相对于 Java ,Rust 完成同样任务所需要的硬件配置要比 Java 低一个数量级。

而且没有 GC,程序的运行行为也是可以预期的,不会有超出想象之外的 Stop the World。


我们采用 自底往上 的建构方法,

但是在进行任何设计决策的时候,我们必须考虑元宇宙本身的普适性,也就是说,我们的实现可以非常细节和具体,任何一步思考的环节都可以看到可运行可检验的成果,但是我们所做的任何决策,都必须,至少自很长一段时间内必须,可以成为最终元宇宙的一部分。

元宇宙 是 虚拟的世界,这么一个庞大的名词,我们必须从一个最简单基础开始构建。


所以简化到极致是什么?

最简单的元宇宙场景是什么?


整个世界由多个场所组成

现实世界的人类,生活在一个无限的宇宙,对于个人来说,就是近乎无限的地球。

但是任何绝大多数的行为,都发生在某一个具体的场所(考虑到飞速移动的汽车/火车/轮船/飞机,上面的人类也是处在一个有限的场所)。

所以,我们的元宇宙简化为,无数个有限大小的场所,通过可快速通过的道路相连接。


这里面隐含了两种可能性


  • 一个内容提供商构建了足够多的场所,并通过道路将它们连接起来

  • 多个内容提供商共同构建了无限的场所,并通过一套开放的规范将他们连接起来


真正的元宇宙应当然是后面这种可能性,但是作为从底往上的构建,我们先作为第一个建筑师,开始构建我们的 元宇宙


第一步,是构造一个有限大小的场所

在端游的早期,大家为了追求所谓的无限大的世界,设计了无数的技术方案来实现所谓的无缝地图切换,其中尤以坑人的 BigWorld 为甚,这方面网易的同学有惨痛的经验,Bigworld 在所谓的无缝大世界中花费了太多的精力,以至于最后没有开发出一款真正流行的游戏(我印象中,网易的《天下》项目组就使用了 Bigworld,毫无悬念的销声匿迹了)。


二维还是三维,这就是问题?

也许大家会疑惑,为什么会提出这个问题呢,我们难道不是生活在一个三维的世界吗?但是仔细想想,我们的世界是三维的,我们日常生活是三维的吗?

我们都生活在地球的表面,我们必须站在地面上/甲板上/飞机的地板上,当我们告诉别人某一个位置的时候,我们使用的是二维的坐标,经度加上纬度,而没有高度。

所以实际上,在绝大多数情况下,我们是二维的,当然具体到某一个大楼,某一个商场的时候,简单的经纬度不够了,我们需要加上楼层。

这也就是从魔兽世界开始绝大多数 MMORPG,不约而同的选择使用二维坐标来描述玩家位置的核心原因。

在野外,玩家是二维的,在复杂的副本或者楼房里面,我们要加上楼层甚至高度。

作为一个合理的简化,我们认为场所是二维的。

想想我们的现实世界吧,当我们进入某一个场所的时候,我们是不是会看到很多自然风景/建筑物,但是这些东西在一定的时间是不会变化的。那么变化的是什么呢?

当然是来来往往的人们(或者包括他们的宠物)

所以我们的元宇宙的第一步,就是模拟一个场所,场所里面有来来往往的人群(玩家)


所以我们定义最基础的结构 Position

表示玩家的位置,我们使用 f32 作为 位置,在一个有限大小的场所,f32 足够了

struct Position {    x: f32,    y: f32}
复制代码


我们需要一个方法来计算两个 位置之间的距离,根据简单的勾股定理,我们知道



但是我们知道开平方根是非常耗时的操作,大多数情况下,我们只需要比较距离的大小,不需要计算出距离,所以,我们定义下面的函数,能够获取距离的平方就足够了。


fn distance_square(&self, pos: &Position)-> i32 {    let delta_x = (self.x - pos.x) as i32;    let delta_y = (self.y - pos.y) as i32;    delta_x * delta_x + delta_y * delta_y }
复制代码


对于一个玩家来说,除了自己的位置以外,最重要的是什么呢?

除了长期不变的自然/人工风景以外,最重要的,当然是来来往往的人,我们知道,人的视野是有限度的,只有在视野范围内的人/宠物 才会在当前场所跟 自己发生联系,所以我们需要知道一个玩家的邻近玩家列表。

理论上来说,只要遍历整个场所的所有玩家,我们就能知道 跟 自己距离在某一个可视距离之内的玩家,如果所有玩家静止不动还好,虽然耗时,但是遍历 N x N 次也就足够了,问题是,玩家是不断移动的,即使我们不追求实时更新这个邻近玩家,比如说每隔五秒一次 N x N 的计算距离操作,对服务器也是一个巨大的开销。

更可怕的,当玩家做了某个动作,比如说,原地翻了个筋斗,这个动作需要让他周围的玩家都看到,这个时候,我们需要知道该告诉谁,需要计算邻近玩家的列表,每个动作都要计算一次,这显然是不可能的。

所以,我们必须保存玩家的邻近玩家列表


考虑到每个玩家都需要保存 这个列表,我们需要尽可能的减少内存占用,所以我们使用 32 位整数 u32 来唯一标识一个玩家,所以,我们一个玩家(考虑到宠物,我们用角色这个中性词),角色信息定义如下:


struct RoleInfo {    pos: Position,    eighbor: Vec<u32>,}
复制代码


发布于: 1 小时前阅读数: 93
用户头像

Miracle

关注

还未添加个人签名 2019.10.25 加入

还未添加个人简介

评论

发布
暂无评论
Rust 元宇宙 1 —— 创世纪