Ludum在48小时内勇敢地面对生锈和WebAssembly

2020-07-15 05:07:06

Compo的规则要求您必须在48小时内制作游戏(源代码除外)。最后,您的其他参赛者将对您的游戏进行评分,您的游戏将与您的同行进行排名。

今年4月,由于每个人都因冠状病毒被隔离在家中,鲁德姆·达尔的入场者比往常多得多。共有1383人进入Compo,Jam有3576个参赛作品。这是一个荒谬的游戏数量!

我已经使用Unity进入Compo几次了,但这一次我想纯粹用相对较新的编程语言Rust来编写游戏。

这篇帖子一开始关注的是使用Rust的体验,但后来变成了对游戏技术和设计过程的总体概述。

Rust是一种专注于性能和安全的系统编程语言。安全意味着铁锈可以帮助您避免某些类别的漏洞和崩溃。我一直将铁锈用于我的个人工作,并决定现在是时候尝试一下GameJam了。

我已经使用Unity输入了至少五个过去的Ludum Dres,而使用C#的Unity是迄今为止用于Ludum Dare的最常用的一套工具。但我只是最近没有那么享受“团结”了。

Unity是一个巨大的游戏引擎,但我发现很难进入流畅状态。有太多的旋钮、功能和做事方式。翻动几个开关就能拥有一些像样的东西是很容易的,但这并不符合我个人的设计流程。我更喜欢在一个干净的精神环境中工作,而不是一个复杂的编辑。

此外,Unity的WebGL构建在我的笔记本电脑上需要大约20分钟,这绝对是令人痛苦的。

我专门在2016款Macbook Pro上编程已经有一段时间了。这不是最好的,但也不是最差的。

我本来可以使用现有的铁锈游戏引擎,但我对流行的游戏引擎并不熟悉。相反,我将Rust库和各种个人Rust脚本拼凑在一起。

Rust使得通过它的包管理器crates.io使用其他库(称为crates)变得非常容易。

建议使用Rust and WebAssembly(Wasm)的方法是通过一个名为wasm-pack的命令行工具。我跳过了使用wasm-pack,而只是使用了一个两行的bash脚本,该脚本将运行Rust构建,然后直接调用wasm-bindgen来生成Web绑定。

快速迭代时间对于GameJam来说绝对是至关重要的。以下工具在实现快速迭代方面发挥了重要作用:

Cargo Watch、devserver和Visual Studio Code设置的组合意味着我可以编辑Rust代码中的值,比如颜色,然后几乎可以立即在网页上看到它的变化。

在这个项目上工作时,典型的锈蚀构建时间大约是1-3秒,但有时会莫名其妙地上升到10秒左右。铁锈以缓慢的构建时间而闻名,而这些快速的迭代时间只有通过仔细选择微小的板条箱才能实现。我的目标是在切换到浏览器窗口时,始终在网页上准备好新的构建。

虽然只有48小时的最佳实践已经过时,但我还是做出了一些早期的选择,以帮助尽可能容易地编写新代码。我要确保的一件关键事情是,如果我要声明一个新变量或加载一个新资产,那么它只需声明一次。许多铁锈框架遵循的模式有点类似于下面的模式:

struct Game{Player:Player,/*Other Stuff*/}Impll Game{FN Setup(Context:&;Context)->;Game{Game{Player:Player::New(),}}FN update(Context:&;Context){/*响应用户输入并在此处重新绘制*/}}。

上述方法在常规情况下工作得很好,但是当您添加像Player这样的新变量时,需要在多个位置声明它。取而代之的是,我使用了一个有点类似于以下内容的结构:

fn main(){let player=player::new();/*此处有其他设置代码*/loop{/*响应用户输入,永远在此处重绘*/}}。

不幸的是,上述结构在Web上不起作用。在Web上,主循环必须将控制权返回给浏览器。

解决此问题的方法是传递一个闭包,当事件发生时浏览器会调用该闭包:

fn main(){let player=player::new();/*此处有其他设置代码*/run(Move|Context|{/*响应事件并在此处绘制*/});}。

Web上的另一个问题是加载资产。在非Web平台上,只需等待资产完成加载即可:

在Web上,上述操作是不可能的,因为它会阻止将控制权返回给浏览器。在等待资产从服务器返回时锁定是不合适的,因此所有加载都是异步的。

让WIND_SOUND_HANDLE=FETCH_ASSET(";wind.wav";);RUN(MOVE|CONTEXT|{LET WIND_SOUND=NONE;/*在此响应事件并绘制请求*/如果让一些(LOADED_ASSET)=WIND_SOUND_HANDLE)。GET_ASSET(){WINDOW_SOUND=LOAD_ASSET;}/*响应事件并在此绘制*/});

但这种方法可能会导致单调的簿记,这与你想要的GameJam正好相反。

取而代之的是,我决定使用Rust相对较新的功能:异步。幸运的是,我最近向Kapp添加了asyc支持。

Rust的异步功能为一个功能生成一个状态机,允许它暂停,并在准备好后恢复。

异步FN运行(app:application,events:events){让WIND_SOUND=AUDIO::LOAD_AUDIO(";wind.wav";)。等一下。UNWRAP();LOOP{匹配事件。NEXT_EVENT()。等待{/*响应事件并在此绘制*/}。

这让我可以用一行装入资产,而不必担心任何簿记。太棒了!

这次Ludum Dare的主题是“让它活着”,鉴于冠状病毒的迅速传播,这一主题立即让我感到过于病态。我无法激励自己创造一个关于死亡的游戏,我几乎决定完全退出这个游戏。

取而代之的是,我寻找了另一种解释主题的方式。当我凝视着公寓窗外的旧金山山丘时,我想到了人们上下班走路时低着头的样子。这是一个美丽的世界,但当生活的日常事务繁重时,很难注意到每天的美丽。

如果你扮演的角色是鼓舞人们的灵魂呢?你可以让他们的奇迹永存。你可以扮演某种精灵,也许你可以把一个骑自行车的通勤者举到空中,在那里你可以带他们四处飞驰,给他们看星星。

我想象着你会引导骑自行车的人在天空中收集星星,地面上的人会注意到并敬畏地指着。

很难想出如何将这个想法与游戏性配对,但我最终选择的是一款受到老式Flash游戏Line Rider启发的游戏。

你为骑自行车的人画线,他们就会沿着这些线滚动,然后撞上值得收藏的星星。

计划是将角色的物理效果作为一个滚球来实现,然后用角色艺术来代替骑自行车的人。

我首先需要的是线条渲染。我从之前的Rust项目中摘录了一些代码,该项目将通过创建末端带圆的矩形来生成网格线段。我不确定这种粗暴的线条连接方式是否会带来问题。每一个单独的线段都是由一堆三角形组成的,所以我觉得也许游戏会滞后,因为出现的线条太多了。

我通过一遍又一遍地涂鸦整个屏幕的行数来对此进行压力测试,当帧率根本没有下降时,我感到震惊。

在物理课上,我上了一些数学课,找出一条线上离球最近的点。如果点在球的内部,那么检查球的速度是否朝向该点。如果球正朝该点移动,则通过在球上推反方向使球反弹。

FN CHECK_LINES(&;mut self,Points:&;[Vector3]){设len=Points。LEN();FOR I in(1..。Len)。Step_by(2){let(Distance,p)=POINT_WITH_LINE_SELECTION(SELF.。位置,点[i-1],点[i]);如果距离<;(自身。半径+LINE_RADIUS-0.001/*允许球轻微下沉到曲面*/){设法线_of_Collision=(自身。位置-p)。Normal();设Velocity_Allow_Collision=Vector3::Dot(Normal_of_Collision,Self。速度);如果速度_沿_碰撞<;0.0{自身。速度-=碰撞的法线*速度沿碰撞*1.4;}自身。位置+=正常冲突*0.0001;}。

(如果我知道迭代器上的chunks方法,代码可能会更干净一些。)。

我很惊讶在没有太多调整的情况下,球的物理感觉是如此之好。当我开始这个设计的时候,物理对我来说就像一个巨大的未知数。我觉得我可能不能在48小时的窗口内正确地完成它们,但编码物理实际上是该项目中最短的功能之一。

我很担心球的物理会很迟钝,毕竟球的代码只是天真地测试与每一条线段的碰撞。我又一次在屏幕上涂鸦了满满的几行,让帧率下降。但它并没有下降。在LAG出现之前,它花了好几次在整个屏幕上涂鸦。所以我决定不做任何优化。又一场意想不到的胜利!谢谢Wasm和Rust!

事情变得越来越明显,我不会有时间画一个骑自行车的人,或者一个敬畏地抬头仰望天空的孩子。这是令人担忧的,因为用更抽象的图像来传达我想要的主题是非常困难的。

我开始集思广益。这个球让我想起了哈纳福达纸牌上的月亮,我开始思考从那种极简主义中画出的美学。

在我看来,这种新的审美观看起来很棒,而且是可以实现的。这感觉就像是一个美丽的时刻,点击月亮,看着它摇晃着滚出天空。月亮可以滚来滚去,收集代表其他星星或露珠的小圆圈。玩家会画线将月亮引向圆圈。

描述,描述[s]描述这些诗中仅含糊地暗示的事物的微妙的深刻性

不幸的是,我觉得很难用这种美学来传达这款游戏是如何与主题相联系的,让它保持活力。

此时已是凌晨1点,我决定睡一觉。我睡着了,想着用这种新的极简主义美学来传达主题的方法。

当我昏昏欲睡的时候,我的思绪在游戏的设计中发现了一种隐藏的力量。在游戏设计中,我最喜欢的事情之一是找到简单的技术,提供不成比例的价值。让人着迷的技术,嗯,那真的很酷,但是不需要费什么力气。

我可以使用我已经在使用的设计工具:使用鼠标绘制线条。

我的输入将被记录下来,以便在关卡开始时回放绘制线。

第二天早上,我迅速着手对这个想法进行编码。一次按键将开始记录输入,另一次按键将倒带,另一次将数据保存为文件中的一串数字。

回放输入时,我试图保留使用鼠标进行的暂停。这是为了保留一些绘画的人性化质量,而不是让回放感觉太数字化和不人道。如果我暂停的时间太长,我会将暂停调整到最大长度,但是代码很挑剔,有时甚至在最终版本中也会暂停太长时间。

因为我正在努力捕捉绘画和写作的人性品质,我开始思考如何通过一系列手写笔记来传达简短的叙事。

我开始思考人们是如何给别人写感人的信的,我开始想象一个抽象的故事,一个人给另一个身体不适的人写信。我想给医院里的人写一封信,让他们的奇迹永存,给他们一些愉快的时光。这里有一种二元性,通过让某人活着,你可能会帮助他们的肉体也活着。也许这个游戏既能唤起人们在困难时期保持人类奇迹的感觉,也能唤起个人的奇迹?

我不愿触及如此沉重的主题,但我决定将这些想法抽象地编织到不同的层次中。

一个关卡会有手写的纸条,上面写着一些东西,还记得繁星点点的夜晚吗?还附有一幅图画。每一张纸条都是向人类传达的信息,或许也是向另一个人传达的信息。通过扮演滚动的球,有时是月亮,你将成为提醒人们这些美好回忆的力量。

不幸的是,除了线条,没有时间做艺术,所以我决定把更多类型的记忆编织到游戏中。后来,我添加了一些愚蠢的回忆,因为我觉得游戏的基调需要一些轻快的时刻。

整个计划中不幸的缺陷是我的书法非常糟糕。我连接了一个绘图板,但即使有了绘图板,我也发现自己一遍又一遍地重新绘制关卡,试图让文字清晰易懂。如果我的笔迹写得更好的话,这个游戏会好得多。

但当我一遍又一遍地重写这些信息时,我开始真心地感觉到我是在给球员写信息。当时,很多人因为冠状病毒而感到困惑和恐惧。

随着我写下的每一小段记忆,我开始思考,嘿,也许这真的会让某人感觉好点?";这种语气开始贯穿整个游戏。

粗略地说,我认为这篇文章是一个虚构的人给另一个人的信息,一个从自然到人类的信息,它变成了我自己给玩家的信息。

它是多汁的,但我有点困,我发现自己被拉进去了。

这个游戏是否成功地传达了这种不同寻常的、极其抽象的对主题的解释,让它活着?

这个游戏并没有成功地把这个主题传达给大多数玩家。这一主题通过独特的诠释在美学上联系在一起,但不是机械地联系在一起。尽管在这个类别中没有成功,我还是很高兴我在我的主题诠释中冒了设计风险。

因为我在关卡设计工具上花了很长时间,所以制作它们的过程是有趣和流畅的。这个游戏被设计得足够长,足以让人满意,但不会长到让人在中途停止玩。

因为游戏的主题,我试着把游戏设计得相当简单。如果球员被挑战到沮丧的地步,留下的是恼怒而不是更快乐,那将是糟糕的。回想起来,我做得有点过头了,难度很容易。我希望玩家能通过创造性地使用这些机制找到乐趣,但我可以设计关卡来推动他们变得更有创造力。

对于我经常做的声音:在过去的一两个小时里惊慌失措。

我借了一个麦克风,用嘴巴发出风声作为背景氛围。为了收集星星的声音,我把厨房里的每一件东西和玻璃碰在一起,直到我发现最像星星闪烁的声音。然后我把音调调高了一点,让人感觉更有魔力。

当它们都是相同的音符时,这些集合听起来太过乏味了。我试着随意变换音高,但随后的一系列快速收集感觉像是不和谐的混乱。所以我所做的是根据收藏品在关卡中的垂直位置上下移动声音。当球收集了一系列音符时,这有一个很好的效果,那就是弹奏出令人愉快的音阶。留了一点随机性,这样一系列快速收集的球就不会听起来太相似了。

这些调整之所以可能,只是因为一开始就花费了精力来建立一个能够快速迭代的过程。

对于滚动的月亮/球,再次使用了风声,但球的速度调节了声音的音量。这些声音给球增添了一种飘逸的感觉。

我对收视率很满意,“铁锈”很棒,而且我没有吃力。总而言之,我对结果很满意。

对于未来的游戏堵塞,我希望有更多的铁锈助手代码准备就绪。将WebAudio Javascript调用组合在一起是低谷之一,只要做好更多准备就可以轻松避免。

我会鼓励其他人学习生锈和网络组装。如果你不怕自己发明一些东西,这是一个很棒的组合。