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

前言

昨天我们实现了交互,但是没有实现交互的具体逻辑

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

今天我们继续


展示瓦片

我们来拆分实现点击某个瓦片然后触发一系列其它瓦片的逻辑:

  1. 瓦片需要有改变自身状态(指的是从隐藏数字/炸弹到展示)的功能
  2. 瓦片相互触发的逻辑以及最终停下来的逻辑

我们先来处理第一点,这个比较简单。

我们之前留了一个mod没有动,那就是Uncover,它将会被用来实现第一块逻辑。

不过在这之前,我们需要改一下我们之前的代码,现在我们把所有的状态都暴露出来了,我们需要隐藏它们,等到点击的时候再触发。

逻辑一:实现覆盖瓦片

那要怎么覆盖呢?文档里搞了个很暴力的方案,用灰色的块盖住原来瓦片的位置,点击的时候再去掉。

回到我们的board.rs

这里没啥好说的,cover_tiles就是用来覆盖原瓦片的,key是瓦片的坐标,value就是这个用来覆盖瓦片的瓦片。

safe_square_at是我们之前写的方法,返回这个瓦片的周围瓦片的坐标。

之前忘了讲了,这个方法它里面将坐标move了,一般情况下是会报错的,因为这里所有权已经跑到迭代的第一个闭包里了,然而我们给这个Coodinatesstruct实现了CloneCopy两个trait,所以这里move的时候会尝试Copy。如果你移除这俩trait的话就会报错。

另外记住这个adjacent_coverd_tiles方法,它对于我们需要实现的逻辑二来说是非常重要的。

然后我们回到lib.rsspawn_lines方法中,我们在这个方法里创建瓦片,那么瓦片的覆盖瓦片自然也在这里生成。

改动的点有些多,实际上只是copy 一份原瓦片,然后覆盖在它上面。

不过注意这里用的是cmd.with_children,所以这个瓦片的cover正常应该是原瓦片的child

现在我们重新执行下cargo run --features debug

另外你的窗口里的扫雷块应该又变回了灰色

ok,那么覆盖瓦片就完成了,接下来我们来接入昨天写的pressed监听,让它点击的时候去掉覆盖的瓦片。


逻辑一:接入交互

我们来board_plugin/src文件夹下创建events.rs文件

我们在这里实现俩块代码的衔接,数据需要有一个中间变量作为载体,所以这里就有了TileTriggerEvent

然后我们导入到input.rs中,别忘了先导入到lib.rs中。

呃,前面好像没说过EventReader[2]EventWriter[3] ,其实也没啥好说的,一个就是读这个类型的事件,也就是监听,另一个自然就是发送数据。

现在我们重新运行之后点击瓦片就会发送我们的这个载体给监听这个事件的对象。

注意,这个tileTriggerEvent既是数据载体,又是一个event。由于rust特有的struct tuple,这俩结合起来就有些抽象了。

接下来就该实现回调的具体内容了。


逻辑一:实现uncover

和之前一样,具体逻辑自然也是放到systems

我们在systems下创建一个uncover.rs文件。

这里是遍历事件,然后如果回调存在,那就拿出它里面的参数也就是coordinates坐标,然后通过tile_to_uncover这个我们在board.rs中写的方法获取hashmap中对应的实体。拿到实体后通过commands.entity[4] 获取对应的实体,然后插入Uncover这个components,表示它将被揭开。

注意这里的Uncovercomponents/uncover.rs,是组件。

还需要注意这个*entity实际上是返回这个实体的id,然后commands通过id找到这个实体。

相信到这里你已经有些被绕晕了,不过先不要急,这里还有一部没有接上,接上后我们再画图分析执行流程。

另外别忘了从mod.rs中导出。


逻辑二:整体uncover处理

  • Query[5]<(Entity, &Parent), With[6]>:查询是Uncover的实体以及它的parent
  • Query<(&Coordinates, Option<&Bomb>, Option<&BombNeighbor>)>:查询所有Coordinates组件以及Bomb或者BombNeighbor的组件

如何通过query获取数据具体可以看:https://bevy-cheatbook.github.io/programming/queries.html

这里做的事其实也挺好理解

  1. 先是找出uncover的原瓦片,然后删除它们的覆盖瓦片。注意children里迭代出来的entity是覆盖瓦片,而不是原瓦片。
  2. 这个parenttile,也就是tile_coverparent, get方法返回这个实体自身。而parents则是所有带有坐标组件或者炸弹(邻居)组件的实体
  3. 如果是炸弹自身,先mark,但如果是炸弹邻居,就需要对这个炸弹邻居周围的坐标实体进行插入Uncover组件。

那么到这里就万事具备了。

最后就是将这些个systemevent都注册到app里。


插入event和system

回到src/lib.rs中的build方法

现在我们重新跑一次cargo run

现在我们的核心逻辑也处理完毕了,但你可能已经被绕晕了,所以这里会说一下整体的执行流程以及核心原理。


分析

核心原理

其实核心原理很简单

  1. 初始化时同一个坐标中创建两个瓦片,一个瓦片作为覆盖瓦片,一直都是灰色,当点击的时候才会去掉,另一个瓦片则是点击后需要展示的瓦片。
  2. 点击的时候标记这个瓦片为uncover(通过插入Uncover组件),然后监听事件中发现了这个瓦片是uncover,于是将它的覆盖瓦片实体移除。
  3. 移除掉覆盖瓦片的实体后如果自身是一个空的,也就是周围都没有炸弹,那么标记这个瓦片周围的所有瓦片为Uncover
  4. 每一帧都会监听是否有Uncover的(通过Query),如果有就执行上面的流程,直到没有被标记为Uncover的了。

这种实现方式比起递归的方式(我之前用js写的就是递归处理)来说性能强非常多。


执行流程

这里直接以图的方式展示

图可能有些小。。。


代码

这里就不贴修改的代码了,我把代码放到GitHub上了,可以直接拉


总结

今天这块内容是这个游戏的核心,所以画了上面这张图。

到这里其实已经差不多了,后面的内容就是完善和部署了。

参考

  1. ^Bevy Minesweeper: Uncovering tiles https://dev.to/qongzi/bevy-minesweeper-part-6-46jh
  2. ^EventReader https://docs.rs/bevy/0.9.1/bevy/ecs/event/struct.EventReader.html
  3. ^EventWriter https://docs.rs/bevy/0.9.1/bevy/ecs/event/struct.EventWriter.html
  4. ^Commands::entity https://docs.rs/bevy/0.9.1/bevy/ecs/system/struct.Commands.html#method.entity
  5. ^Query https://docs.rs/bevy/0.9.1/bevy/ecs/prelude/struct.Query.html
  6. ^With https://docs.rs/bevy/0.9.1/bevy/ecs/prelude/struct.With.html

编辑于 2023-02-13 23:24・IP 属地广东