24小时Rust游戏:个人发展经验

图片

在本文中,我将谈谈我在Rust中开发小型游戏的个人经历。 创建工作版本大约需要24小时(我主要在晚上或周末工作)。 游戏还远远没有结束,但我认为这种体验会很有用。 我将告诉您我从中学到的知识以及从头开始制作游戏时的一些观察。

Skillbox建议:两年实践课程“我是PRO Web开发人员”

我们提醒您: 对于所有“ Habr”读者来说,使用“ Habr”促销代码注册任何Skillbox课程时均可享受10,000卢布的折扣。

为什么生锈?


我选择这种语言是因为我听到了很多关于它的好东西,并且看到它在游戏开发领域越来越流行。 在编写游戏之前,我几乎没有在Rust中开发简单应用程序的经验。 这足以在编写游戏时感到一定的自由。

为什么是游戏,又是什么样的游戏?


制作游戏很有趣! 由于更多原因,我希望这样做,但是对于“家庭”项目,我选择的主题与我的日常工作不太相关。 什么样的游戏? 我想做一些类似网球模拟器的事情,将城市天际线,动物园大亨,监狱建筑师和网球本身结合在一起。 总的来说,结果是一场有关网球学院的比赛,人们来此比赛。

技术培训


我想使用Rust,但是我不完全知道我需要如何“从头开始”。 我不想编写像素着色器并使用拖放功能,所以我一直在寻找最灵活的解决方案。

找到了我与您分享的有用资源:


我探索了几个Rust游戏引擎,最终选择了Piston和ggez。 在上一个项目中工作时,我遇到了他们。 最后,我选择了ggez,因为它似乎更适合实现小型2D游戏。 对于新手开发人员(或第一次与Rust合作的人),Piston的模块化结构过于复杂。

游戏结构


我花了一些时间思考这个项目的架构。 第一步是建造“土地”,人民和网球场。 人们必须在法院四处走动并等待。 玩家必须具备随着时间而提高的技能。 另外,应该有一个允许您添加新朋友和法院的编辑器,但这不再是免费的。

考虑一切,我开始工作。

游戏创作


开始于:圆圈和抽象

我以ggez为例,在屏幕上画了一个圆圈。 好厉害 现在是一些抽象。 在我看来,忽略游戏对象的想法真是太好了。 必须按照此处指示的方式渲染和更新每个对象:

// the game object trait trait GameObject { fn update(&mut self, _ctx: &mut Context) -> GameResult<()>; fn draw(&mut self, ctx: &mut Context) -> GameResult<()>; } // a specific game object - Circle struct Circle { position: Point2, } impl Circle { fn new(position: Point2) -> Circle { Circle { position } } } impl GameObject for Circle { fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { Ok(()) } fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { let circle = graphics::Mesh::new_circle(ctx, graphics::DrawMode::Fill, self.position, 100.0, 2.0)?; graphics::draw(ctx, &circle, na::Point2::new(0.0, 0.0), 0.0)?; Ok(()) } } 

这段代码使我获得了一个极好的对象列表,可以在同样出色的循环中更新和渲染这些对象。

 mpl event::EventHandler for MainState { fn update(&mut self, context: &mut Context) -> GameResult<()> { // Update all objects for object in self.objects.iter_mut() { object.update(context)?; } Ok(()) } fn draw(&mut self, context: &mut Context) -> GameResult<()> { graphics::clear(context); // Draw all objects for object in self.objects.iter_mut() { object.draw(context)?; } graphics::present(context); Ok(()) } } 

main.rs是必需的,因为它包含所有代码行。 我花了一些时间来分离文件并优化目录结构。 这是所有事情的开始:

资源->这是所有资产所在的位置(图片)
src
-实体
-game_object.rs
-circle.rs
-main.rs->主循环

人,楼层和图像

下一步是创建一个Person游戏对象并加载图像。 一切都应基于大小为32 * 32的图块。



网球场

研究了网球场的外观后,我决定用4 * 2的瓷砖制作它们。 最初,可以制作此大小的图像,或者一起制作8个单独的图块。 但是后来我意识到只需要两个唯一的磁贴,这就是为什么。

总共,我们有两个这样的图块:1和2。

球场的每个部分都由图块1或图块2组成。它们可以照常布置,也可以上下颠倒180度。



主要施工方式(组装)

在设法完成网站,人物和地图的渲染之后,我意识到还需要一种基本的构建模式。 它的实现是这样的:按下按钮时,将选择对象,然后单击将其放置在正确的位置。 因此,按钮1允许您选择一个球场,按钮2允许您选择一个球员。

但是您仍然需要记住1和2的含义,因此我添加了线框,以便清楚地选择了哪个对象。 这是它的外观。


有关架构和重构的问题

现在我有几个游戏对象:人,法院和地板。 但是,为了使线框起作用,您需要告诉对象的每个实体,对象本身是否处于演示模式,或者是否简单绘制了框架。 这不是很方便。

在我看来,我需要重新考虑体系结构,以便发现一些限制:

  • 显示和更新自身的实体的存在是一个问题,因为该实体将无法“知道”其应呈现的内容-图像和线框;
  • 缺少用于在各个实体之间交换属性和行为的工具(例如is_build_mode属性或呈现行为)。 可以使用继承,尽管在Rust中没有正常的实现方法。 我真正需要的是布局。
  • 需要一种实体之间相互作用的工具来将人员分配到法院;
  • 实体本身是数据和逻辑的混合,很快就失去了控制。

我进行了进一步的研究,发现了ECS架构-实体组件系统 ,该系统在游戏中普遍使用。 以下是ECS的优势:

  • 数据与逻辑分离;
  • 布局而不是继承;
  • 面向数据的体系结构。

ECS具有三个基本概念:

  • 实体-标识符所指的对象类型(可以是玩家,球或其他东西);
  • 组件-实体由它们组成。 一个示例是渲染组件,布局等。 它是一个数据仓库;
  • 系统-它们同时使用对象和组件,还包含基于此数据的行为和逻辑。 一个示例是一个渲染系统,该系统使用要渲染的组件在所有实体上进行迭代并参与渲染。

经过研究,很明显,ECS解决了以下问题:

  • 对实体的系统组织使用布局而不是继承;
  • 消除由于控制系统导致的代码哈希;
  • 使用is_build_mode之类的方法将线框的逻辑存储在渲染系统中的同一位置。

这是实施ECS之后发生的事情。

资源->这是所有资产所在的位置(图片)
src
-组件
-position.rs
-人
-Tennis_court.rs
-floor.rs
-wireframe.rs
-mouse_tracked.rs
-资源
-mouse.rs
-系统
-rendering.rs
-constants.rs
-utils.rs
-world_factory.rs->世界工厂函数
-main.rs->主循环

分配人到法院


ECS使生活更轻松。 现在,我有了一种系统的方法,将数据添加到实体并基于该数据添加逻辑。 反过来,这使得有可能通过法院组织人员的分配。

我做了什么:

  • 向Person添加有关分配法院的数据;
  • 向TennisCourt添加了有关分布式人员的数据;
  • 添加了CourtChoosingSystem,该系统使您可以分析人员和站点,找到可用的法院并向其分发玩家;
  • 添加了PersonMovementSystem系统,该系统搜索分配给法院的人员,如果不在现场,则在必要时将其发送给人员。


总结一下


我非常喜欢这款简单的游戏。 而且,我很高兴用Rust编写它,因为:

  • Rust提供了您所需的东西;
  • 他有出色的文档,Rust非常优雅。
  • 恒久性很酷;
  • 您不必求助于克隆,复制或其他类似操作,而我在C ++中经常这样做;
  • 选件非常方便工作,还可以完美地处理错误。
  • 如果可以编译该项目,则99%的项目可以正常运行,并且完全可以完成。 在我看来,编译器错误消息是我所看到的最好的消息。

Rust上的游戏开发才刚刚开始。 但是已经有一个稳定而庞大的社区致力于向所有人开放Rust。 因此,我乐观地看待语言的未来,期待我们共同工作的结果。

Skillbox建议:


Source: https://habr.com/ru/post/zh-CN444364/


All Articles