组件和实体
嗨,少年!看你骨骼惊奇接下来就开始一起学习怎么结合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: 你可以在 这里找到本小节完整的代码.