组件和实体
在本节中,我们将创建组件,学习如何创建实体,并注册所有内容以确保 specs
正常工作。
定义组件
我们先从定义组件开始。之前我们讨论了 Position
(位置组件)、Renderable
(可渲染组件)和 Movement
(动作组件),但暂时我们会跳过动作组件。我们还需要一些组件来标识每个实体。例如,我们需要一个 Wall
(墙)组件,通过它来标识一个实体是墙。
希望这很直观:位置组件存储 x、y 和 z 坐标,用于告诉我们某物在地图上的位置;可渲染组件会接收一个字符串路径,指向一张可以渲染的图片。所有其他组件都是 标记型组件,暂时不包含任何数据。
#![allow(unused)] fn main() { #[allow(dead_code)] pub struct Position { x: u8, y: u8, z: u8, } #[allow(dead_code)] pub struct Renderable { path: String, } pub struct Wall {} pub struct Player {} pub struct Box {} pub struct BoxSpot {} }
在熟悉的 Rust 代码中,我们使用了一些新语法。我们使用了一个强大的 Rust 功能,称为“过程宏”(Procedural Macros
),例如 #[storage(VecStorage)]
。这种宏本质上是一些函数,在编译时会处理某些语法并生成新的语法。
更多内容: 想了解更多关于过程宏的信息,请参阅 这里。
创建实体
实体只是一个与一组组件相关联的数字标识符。因此,我们创建实体的方法是简单地指定它们包含哪些组件。
现在,创建实体的代码如下所示:
#![allow(unused)] fn main() { pub fn create_wall(world: &mut World, position: Position) -> Entity { world.spawn(( Position { z: 10, ..position }, Renderable { path: "/images/wall.png".to_string(), }, Wall {}, )) } pub fn create_floor(world: &mut World, position: Position) -> Entity { world.spawn(( Position { z: 5, ..position }, Renderable { path: "/images/floor.png".to_string(), }, )) } pub fn create_box(world: &mut World, position: Position) -> Entity { world.spawn(( Position { z: 10, ..position }, Renderable { path: "/images/box.png".to_string(), }, Box {}, )) } pub fn create_box_spot(world: &mut World, position: Position) -> Entity { world.spawn(( Position { z: 9, ..position }, Renderable { path: "/images/box_spot.png".to_string(), }, BoxSpot {}, )) } pub fn create_player(world: &mut World, position: Position) -> Entity { world.spawn(( Position { z: 10, ..position }, Renderable { path: "/images/player.png".to_string(), }, Player {}, )) } }
资源
您可能已经注意到,我们在上面的实体创建中引用了要使用的资源。您可以自由创建自己的资源,或者下载我们提供的资源(右键点击图片并选择“另存为”)。
让我们将这些图片添加到项目中。创建一个 resources
文件夹,用于存放所有资源。目前,这些资源仅包括图片,但将来我们可能会添加配置文件或音频文件(继续阅读,您将在 第三章第三节 学习如何播放声音)。在 resources
文件夹下再创建一个 images
文件夹,将我们的 PNG 图片放入其中。您也可以使用不同的文件夹结构,但在本节后续部分使用图片时,请确保路径正确。
项目的目录结构如下所示:
├── resources
│ └── images
│ ├── box.png
│ ├── box_spot.png
│ ├── floor.png
│ ├── player.png
│ └── wall.png
├── src
│ └── main.rs
└── Cargo.toml
创建游戏世界
最后,让我们将所有内容整合在一起。我们需要创建一个 specs::World
对象,将其添加到我们的 Game
结构中,并在主函数中首先初始化它。以下是完整代码,现在运行时仍会显示一个空白窗口,但我们在设置游戏组件和实体方面已经取得了巨大进展!接下来,我们将进入渲染部分,最终在屏幕上看到内容!
pub fn main() -> GameResult { let world = World::new(); // Create a game context and event loop let context_builder = ggez::ContextBuilder::new("rust_sokoban", "sokoban") .window_setup(conf::WindowSetup::default().title("Rust Sokoban!")) .window_mode(conf::WindowMode::default().dimensions(800.0, 600.0)) .add_resource_path(path::PathBuf::from("./resources")); let (context, event_loop) = context_builder.build()?; // Create the game state let game = Game { world }; // Run the main event loop event::run(context, event_loop, game) }
注意:运行时,控制台可能会报告一些关于未使用导入或字段的警告,不用担心这些问题,我们将在后续章节中修复它们。
CODELINK: 您可以在 这里 查看本节完整代码。