组件和实体

嗨,少年!看你骨骼惊奇接下来就开始一起学习怎么结合specs创建、注册组件和实体。

定义组件

我们先从定义组件开始。先前我们提到过位置组件可渲染组件动作组件(这个后面再讲哈)。我们需要用一些组件标识实体,比如可以让一个实体包含墙组件标识它是墙。

可以直接简单的说:位置组件其实就是用来存储地图坐标的x、y、z值的可以用来定位;渲染组件就是使用字符串存储一个需要绘制的图片的路径;另外一些组件基本都是 标记型组件并不存储数据。


#![allow(unused)]
fn main() {
#[derive(Debug, Component, Clone, Copy)]
#[storage(VecStorage)]
pub struct Position {
    x: u8,
    y: u8,
    z: u8,
}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Renderable {
    path: String,
}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Wall {}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Player {}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Box {}

#[derive(Component)]
#[storage(VecStorage)]
pub struct BoxSpot {}

}

#[storage(VecStorage)]这原来没见过是不是? 恭喜你,少年!你已经使用到了一个Rust很强大的功能过程宏。这种宏是一些可以在代码编译时对代码进行处理并生成新代码的特殊函数。

MORE: 如果你想更深入的了解宏,可以看 这里.

注册组件

specs中使用组件前需要先注册组件,就像这样:

把组件注册到world


#![allow(unused)]
fn main() {
pub fn register_components(world: &mut World) {
    world.register::<Position>();
    world.register::<Renderable>();
    world.register::<Player>();
    world.register::<Wall>();
    world.register::<Box>();
    world.register::<BoxSpot>();
}

}

创建实体

实体就是代表一系列组件,所以我们创建实体的方法就是简单地指定它们包含哪些组件。就像这个样子:


#![allow(unused)]
fn main() {
pub fn create_wall(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 10, ..position })
        .with(Renderable {
            path: "/images/wall.png".to_string(),
        })
        .with(Wall {})
        .build();
}

pub fn create_floor(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 5, ..position })
        .with(Renderable {
            path: "/images/floor.png".to_string(),
        })
        .build();
}

pub fn create_box(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 10, ..position })
        .with(Renderable {
            path: "/images/box.png".to_string(),
        })
        .with(Box {})
        .build();
}

pub fn create_box_spot(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 9, ..position })
        .with(Renderable {
            path: "/images/box_spot.png".to_string(),
        })
        .with(BoxSpot {})
        .build();
}

pub fn create_player(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 10, ..position })
        .with(Renderable {
            path: "/images/player.png".to_string(),
        })
        .with(Player {})
        .build();
}

}

素材

睿智如你应该已经注意到了,我们还引用了些用于创建实体的素材,就是图片什么的。当然你要是觉得我们准备的素材不好看,也可以使用自己的素材。我们准备的素材就在下面了,你可以右键另存为下载到电脑上:

地板 墙 玩家 箱子 方形斑点

接下来把这些图片放到我们的项目中。在项目目录中新建resources目录,用于存放项目需要用到的资源,目前我们只有图片资源需要存储,以后还会有配置文件啊,音频文件(第三章的第3小节会用到)啊什么的。为了区分不同的资源文件,在resources目录下再新建一个images目录,用于存放我们的png图片。你也可以按照自己的喜欢命名目录,除了只要你开心就好,还要记得在代码中引用这些资源时要写出正确的路径。一波操作下来后,我们项目的目录结构大概是这个样子地:

├── resources
│   └── images
│       ├── box.png
│       ├── box_spot.png
│       ├── floor.png
│       ├── player.png
│       └── wall.png
├── src
│   └── main.rs
└── Cargo.toml

创建游戏世界(World)

最后,当然只是本小节的最后,接下来在main函数的第一行就创建一个specs::World对象,把先前创建的实体还有素材都整合到一起。

// Rust sokoban
// main.rs



use ggez::{conf, event, Context, GameResult};
use specs::{Builder, Component, VecStorage, World, WorldExt};


use std::path;

// Components
#[derive(Debug, Component, Clone, Copy)]
#[storage(VecStorage)]
pub struct Position {
    x: u8,
    y: u8,
    z: u8,
}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Renderable {
    path: String,
}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Wall {}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Player {}

#[derive(Component)]
#[storage(VecStorage)]
pub struct Box {}

#[derive(Component)]
#[storage(VecStorage)]
pub struct BoxSpot {}

// This struct will hold all our game state
// For now there is nothing to be held, but we'll add
// things shortly.
struct Game {
    world: World,
}

impl event::EventHandler<ggez::GameError> for Game {
    fn update(&mut self, _context: &mut Context) -> GameResult {
        Ok(())
    }

    fn draw(&mut self, _context: &mut Context) -> GameResult {
        Ok(())
    }
}

// Register components with the world
pub fn register_components(world: &mut World) {
    world.register::<Position>();
    world.register::<Renderable>();
    world.register::<Player>();
    world.register::<Wall>();
    world.register::<Box>();
    world.register::<BoxSpot>();
}

// Create a wall entity
pub fn create_wall(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 10, ..position })
        .with(Renderable {
            path: "/images/wall.png".to_string(),
        })
        .with(Wall {})
        .build();
}

pub fn create_floor(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 5, ..position })
        .with(Renderable {
            path: "/images/floor.png".to_string(),
        })
        .build();
}

pub fn create_box(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 10, ..position })
        .with(Renderable {
            path: "/images/box.png".to_string(),
        })
        .with(Box {})
        .build();
}

pub fn create_box_spot(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 9, ..position })
        .with(Renderable {
            path: "/images/box_spot.png".to_string(),
        })
        .with(BoxSpot {})
        .build();
}

pub fn create_player(world: &mut World, position: Position) {
    world
        .create_entity()
        .with(Position { z: 10, ..position })
        .with(Renderable {
            path: "/images/player.png".to_string(),
        })
        .with(Player {})
        .build();
}

pub fn main() -> GameResult {
    let mut world = World::new();
    register_components(&mut world);

    // 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)
}

然后你就可以执行cargo run运行看下效果,当你满怀期待却发现看到的依然是一个空白的窗口,控制台里可能还多了些警告信息。这是因为我们还没有编写渲染的代码也就是还没有绘制这些实体。少侠,莫急!下一节,我们就开始绘制。到时这些因为引入而没有使用的警告也就自然消失了。

CODELINK: 你可以在 这里找到本小节完整的代码.