组件和实体

在本节中,我们将创建组件,学习如何创建实体,并注册所有内容以确保 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: 您可以在 这里 查看本节完整代码。