坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.4

前言

昨天我们创建好了整个tile map

坏蛋Dan:rust基础学习--基于Bevy实现扫雷小游戏day2

但是并没有将它渲染到窗口里


渲染到窗口里

既然是需要渲染到窗口里的,自然就少不了布局和样式设计。

我们的这个插件board_plugin需要暴露以下的入口供自定义:

  • tilp map的属性(宽、高、有多少炸弹)
  • tile之间的间距。
  • 自定义tile的大小或者根据窗口自适应大小
  • 自定义扫雷小游戏内容在窗口中的位置或者直接放到中间,提供偏移值入口。
  • 可选的安全不覆盖起始区域(Optional safe uncovered start zone

这些个选项可以组合成一个资源。

选项 as 资源

那么开搞吧~

先在board_plugin/src/resources文件夹中创建一个board_options.rs文件

这些都是选项,其中Serialize[2]Deserialize[3]这俩都来自serde[4]

SerializeDeserialize的作用是相反的,Serialize方法用于将一个数据结构转换成任何其它数据结构。

这里为啥要用它们呢?因为这些选项是在窗口里交互的,所以得放到runtime才行。

Vec3[5] 一个三维的structx: f32, y: f32, z: f32。这里需要mark下,因为对于我们这个游戏来说2d就够了,所以需要留意下后面是怎么用的,会有什么效果。

然后我们来给这些个选项设置默认值,一般选项都是得有默认值的。

我们之前在附录里学到过一个派生属性Default,我们可以基于这个Default来实现默认值。

不过直接使用Default属性的默认值是编译器固定的,在这里对于我们来说不太友好,所以我们来使用Default这个trait 来自定义默认值

ok,现在选项这块就基本搞定了,不说完整了,但是该有的样子都有了。

然后再把这个资源暴露出去

然后我们来使用,参考WindowDescriptor,我们在main.rs中进行配置,这种不算是业务逻辑,是属于环境初始化(配置)的。

注意这里如果要insert_resource,那么你这个insert进去的东西就得是实现Resource这个trait的。


渲染

接下来就是实现展示这个过程了,这属于业务逻辑相关的了,根据ECS开发思维,这块自然就是放到system里的。

我们回到board_plugin/src/lib.rs文件中改动这个create_board方法。

  1. 获取options,也就是我们之前设置的选项资源
  2. 生成瓦片图以及炸弹(debug模式下终端打印出图)
  3. 然后获取瓦片的size,也就是宽高,自适应的需要计算窗口和图的比例。

这里简单的说下计算的逻辑。

我们的窗口是width: 700height: 800

minmax则是自适应的瓦片的最大值和最小值。

而瓦片的board也就是整个扫雷的大小,是跟着瓦片个数来计算的,这里存的是一个元组,分别代表行多少个瓦片和列多少个瓦片,我们在main.rs中设置的是20 * 20

那么整个扫雷的大小运算逻辑就是 瓦片size * 20 * 20。

那么三组数据都已经有了,如果不是自适应,那就是fix size * 20 * 20

如果是自适应,计算逻辑在adaptative_tile_size方法中。

首先是获取获取每个瓦片的宽高在这个窗口中的比值,也就是用窗口的宽/高 除以 瓦片行/列个数得到的值。接着两者选其最小的作为基础值,这样渲染就不会溢出。不过这里还需要考虑瓦片的最大最小值,如果这个比值小于瓦片最小值,那么返回瓦片最小值,同理大于时返回瓦片最大值(值必须限制在最大值最小值之间,所以称之为夹紧)。

board_size自然就是这个比值 * 瓦片行 * 瓦片列。

Vec2Vec3类似,创建的是二维的{ x: f32, y: f32 }

  1. 定位,参照点也就是坐标轴中心(0, 0),如果是自定义,那就直接拿自定义的作为初始位置。如果不是就是默认中心,中心距离左下角的距离正好就是(-board_size.x / 2, -board_size.y / 2)这里带上负号的原因是我们是参照中心点距离(0, 0)的位置向(0, 0)做平移,自然就是往x, y负方向平移。

  1. 既然选项都已经获取完毕了并且也都计算完了,那就可以开始渲染了。

基于commands.spawn创建一个实体

SpatialBundle:一个bundlebundle这个trait使得实体拥有插入/移除组件的能力,它可以理解为自身带有好几个component的实体模板,可以直接套进来使用它的component,这个bundle里面有几个component

  • Visibility:控制这个实体的显隐
  • ComputedVisibility:通过算法计算出来这个实体是否需要隐/显示,以及是否需要被提取出来用于渲染。
  • Transform:它的属性有些类似css中的transform属性,比如translate、rotate、scale,它是用于描述这个实体的位置。
  • GlobalTransform:和Transform类似,不过它是用来描述这个实体在它所在参考系(reference frame)里的位置。

GlobalTransformTransform的区别在于前者用于get,后者用于set,你想获取这个实体的位置,你就用GlobalTransform,而如果是想替换或者移动这个实体,就用Transform

  • Visibility::VISIBLE:创建一个visibility的组件,它是visible的。
  • Transform::from_translation:创建一个transform组件,除了translation属性不是默认值,其它都是默认值。
  • board_position.into:这个方法来自于Info这个trait。而这个trait一般用法和From这个trait相反。From<a>表示从A变成自己B,而Into</a><a>则是将自己B变成A。来看个例子了解下怎么使用
  • test_from方法中我们将A的实例a变成了B的实例。
  • test_into方法中我们也是将A的实例a变成了b的实例。

两者的不同点在于调用者,test_from中调用from方法的是B,而test_into中调用into方法的是A的实例a

这里有一点需要注意,不能在给B实现From</a><a>的同时给A实现Into<b>,即使理论上可行,依旧是会报错的。

扯远了,回到我们的代码中

在创建完整个扫雷的实体之后,我们插入了一个Name的组件。这个组件有俩字段,hash表示唯一标识符,而name自然就是这个实体在app中的名字。

  • with_children方法接收一个闭包,这些个闭包会被传入add_children方法中。

它会为每一个闭包创建一个ChildBuilder[6]实例,然后存入自身的commands中。

ChildBuilder会把当前的children都构建到实体当中。

PushChildren[7] 用于将children push到当前实体的children里面

有些绕,但是结合代码应该就比较清晰了,这里先是把PushChildren放到了ChildBuilder实例里面,然后这个ChildBuilder的实例被传入spawn_children也就是我们传入的闭包里面,执行完之后再把这个ChildBuilder实例的push_children也就是PushChildren实例拿出来再放到实体的commands里面。

所以实际上ChildBuilder只是个临时工具而已,用来传递PushChildren

那么回到我们的闭包当中,它有一个参数是parent,也就是ChildBuilder实例。

然后我们又调用了spawn这个方法生成一个新的实体

这个spawn和前面的commands.spawn不是同一个,这个是ChildBuilder自己的。它里面调用了commands.spawn方法创建一个实体,然后存储这个实体的idPushChildren实例的children字段里面,这个children字段是一个vector

然后返回这个实体。

  1. 然后设置这个实体的名字:Background,看名字也就知道这是我们扫雷的背景,白色,这里还做了transform操作,为什么呢?因为这个背景的初始位置不对,是以坐标轴中心为背景中心的,所以还得往俩轴正方向迁移才行。

SpriteBundle: 前面说过bundle可以看作是模板,里面包含了好几个组件,这个就不解释里面的东西了,看下都有啥即可。

Sprite[8]: 同上,看下即可。

背景搞好了,该轮到我们的瓦片了,二维结构自然是双层的遍历,这里用迭代器替代for性能会好很多, 之前学迭代器的时候也有说到过,它在编译阶段会被展开而不是for循环处理,所以耗时少很多。

瓦片也是一个实体。

瓦片初始化状态都是gray也就是灰色的,也就是没有被点开的时候。

  • splat:创建一个矩形,这里还需要去掉padding占的空间,也就是内边距。
  • from_xzy:这方法没啥好说的,就是from_transition,但是值是vec3::new(x, y, z)。表示三维空间偏移量。

最后再把我们的Coordinates作为组件插入到瓦片实体里。

那么现在已经是可以渲染到窗口里了。

我们来运行下

正常,不过需要过一遍侦测的所有组件,看下是否都正常。

我这里仅看了Coordinates,因为文档里的版本过低,有些api都已经被废弃,所以我自己摸索了好一会才完成。

现在渲染了,下一步就该轮到实现交互了。


代码

main.rs


lib.rs


coordinates.rs


tile.rs


tile_map.rs


board_options.rs

剩下的几个就不说了,都是导入导出的mod.rs


总结

今天我们实现了把瓦片图渲染到窗口里,但是暂时还不能交互。

参考

  1. ^render-in-screen https://dev.to/qongzi/bevy-minesweeper-part-3-1a9a
  2. ^Serialize https://docs.rs/serde/latest/serde/trait.Serialize.html
  3. ^Deserialize https://docs.rs/serde/latest/serde/trait.Deserialize.html
  4. ^serde https://serde.rs/
  5. ^Vec3 https://docs.rs/bevy/0.9.1/bevy/prelude/struct.Vec3.html
  6. ^ChildBuilder https://docs.rs/bevy/0.9.1/bevy/prelude/struct.ChildBuilder.html
  7. ^PushChildren https://docs.rs/bevy/0.9.1/bevy/prelude/struct.PushChildren.html
  8. ^Sprite https://docs.rs/bevy/0.9.1/bevy/sprite/index.html

编辑于 2023-02-10 14:57・IP 属地广东