魔法地图
关于本教程
本教程是免费且开源的,所有代码均使用 MIT 许可证 - 因此您可以随意使用。 我希望您会喜欢本教程,并制作出色的游戏!
如果您喜欢这个教程并希望我继续写作,请考虑支持我的 Patreon。
在 Roguelike 游戏中,一个非常常见的物品是魔法地图卷轴。 你阅读它,地下城就会被揭示出来。 更精美的 Roguelike 游戏为此提供了漂亮的图形效果。 在本章中,我们将从使其工作开始 - 然后使其变得漂亮!
添加魔法地图组件
我们拥有了所需的一切,除了一个指示物品是魔法地图卷轴(或任何其他物品,真的)的指示器。 因此,在 components.rs
中,我们将为其添加一个组件:
#![allow(unused)] fn main() { #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct MagicMapper {} }
与往常一样,我们需要在 main.rs
和 saveload_system.rs
中注册它。 我们将前往 spawners.rs
并为其创建一个新函数,并将其添加到战利品表中:
#![allow(unused)] fn main() { fn magic_mapping_scroll(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position{ x, y }) .with(Renderable{ glyph: rltk::to_cp437(')'), fg: RGB::named(rltk::CYAN3), bg: RGB::named(rltk::BLACK), render_order: 2 }) .with(Name{ name : "Scroll of Magic Mapping".to_string() }) .with(Item{}) .with(MagicMapper{}) .with(Consumable{}) .marked::<SimpleMarker<SerializeMe>>() .build(); } }
以及战利品表:
#![allow(unused)] fn main() { fn room_table(map_depth: i32) -> RandomTable { RandomTable::new() .add("Goblin", 10) .add("Orc", 1 + map_depth) .add("Health Potion", 7) .add("Fireball Scroll", 2 + map_depth) .add("Confusion Scroll", 2 + map_depth) .add("Magic Missile Scroll", 4) .add("Dagger", 3) .add("Shield", 3) .add("Longsword", map_depth - 1) .add("Tower Shield", map_depth - 1) .add("Rations", 10) .add("Magic Mapping Scroll", 400) } }
请注意,我们赋予了它 400 的权重 - 绝对是荒谬的。 我们稍后会修复它,现在我们真的想生成卷轴以便我们可以测试它! 最后,我们将其添加到实际的生成函数中:
#![allow(unused)] fn main() { match spawn.1.as_ref() { "Goblin" => goblin(ecs, x, y), "Orc" => orc(ecs, x, y), "Health Potion" => health_potion(ecs, x, y), "Fireball Scroll" => fireball_scroll(ecs, x, y), "Confusion Scroll" => confusion_scroll(ecs, x, y), "Magic Missile Scroll" => magic_missile_scroll(ecs, x, y), "Dagger" => dagger(ecs, x, y), "Shield" => shield(ecs, x, y), "Longsword" => longsword(ecs, x, y), "Tower Shield" => tower_shield(ecs, x, y), "Rations" => rations(ecs, x, y), "Magic Mapping Scroll" => magic_mapping_scroll(ecs, x, y), _ => {} } }
如果您现在运行 cargo run
,您可能会发现可以拾取的卷轴 - 但它们不会做任何事情。
映射关卡 - 简单版本
我们将修改 inventory_system.rs
以检测您是否刚刚使用了魔法地图卷轴,并显示整个地图:
#![allow(unused)] fn main() { // 如果是魔法地图卷轴... let is_mapper = magic_mapper.get(useitem.item); match is_mapper { None => {} Some(_) => { used_item = true; for r in map.revealed_tiles.iter_mut() { *r = true; } gamelog.entries.push("The map is revealed to you!".to_string()); } } }
还有一些框架更改(请参阅源代码); 我们已经做过很多次了,我认为不需要再次在这里重复。 如果您现在 cargo run
该项目,找到一个卷轴(它们无处不在)并使用它 - 地图会立即显示:
让它更漂亮
虽然那里展示的代码是有效的,但它在视觉上并不吸引人。 在游戏中加入一些花絮,让用户时不时地对 ASCII 终端的美丽感到惊喜是件好事! 我们将再次修改 inventory_system.rs
:
#![allow(unused)] fn main() { // 如果是魔法地图卷轴... let is_mapper = magic_mapper.get(useitem.item); match is_mapper { None => {} Some(_) => { used_item = true; gamelog.entries.push("The map is revealed to you!".to_string()); *runstate = RunState::MagicMapReveal{ row : 0}; } } }
请注意,我们没有修改地图,而是将游戏状态更改为映射模式。 我们实际上还不支持这样做,所以让我们进入 main.rs
中的状态映射器并修改 PlayerTurn
以处理它:
#![allow(unused)] fn main() { RunState::PlayerTurn => { self.systems.dispatch(&self.ecs); self.ecs.maintain(); match *self.ecs.fetch::<RunState>() { RunState::MagicMapReveal{ .. } => newrunstate = RunState::MagicMapReveal{ row: 0 }, _ => newrunstate = RunState::MonsterTurn } } }
当我们在这里时,让我们将状态添加到 RunState
:
#![allow(unused)] fn main() { #[derive(PartialEq, Copy, Clone)] pub enum RunState { AwaitingInput, PreRun, PlayerTurn, MonsterTurn, ShowInventory, ShowDropItem, ShowTargeting { range : i32, item : Entity}, MainMenu { menu_selection : gui::MainMenuSelection }, SaveGame, NextLevel, ShowRemoveItem, GameOver, MagicMapReveal { row : i32 } } }
我们还在 tick 循环中为新状态添加了一些逻辑:
#![allow(unused)] fn main() { RunState::MagicMapReveal{row} => { let mut map = self.ecs.fetch_mut::<Map>(); for x in 0..MAPWIDTH { let idx = map.xy_idx(x as i32,row); map.revealed_tiles[idx] = true; } if row as usize == MAPHEIGHT-1 { newrunstate = RunState::MonsterTurn; } else { newrunstate = RunState::MagicMapReveal{ row: row+1 }; } } }
这非常简单:它显示当前行上的瓦片,然后如果我们没有到达地图底部 - 它会增加行数。 如果我们到达了底部,它会返回到我们之前的位置 - MonsterTurn
。 如果您现在 cargo run
,找到魔法地图卷轴并使用它,地图会很好地淡入:
记住要降低生成优先级!
在 spawners.rs
中,我们目前正在到处生成魔法地图卷轴。 这可能不是我们想要的! 编辑生成表以使其具有更低的优先级:
#![allow(unused)] fn main() { fn room_table(map_depth: i32) -> RandomTable { RandomTable::new() .add("Goblin", 10) .add("Orc", 1 + map_depth) .add("Health Potion", 7) .add("Fireball Scroll", 2 + map_depth) .add("Confusion Scroll", 2 + map_depth) .add("Magic Missile Scroll", 4) .add("Dagger", 3) .add("Shield", 3) .add("Longsword", map_depth - 1) .add("Tower Shield", map_depth - 1) .add("Rations", 10) .add("Magic Mapping Scroll", 2) } }
总结
这是一个相对快速的章节,但我们现在拥有了 Roguelike 类型的另一个主要元素:魔法地图。
本章的源代码可以在这里找到
在您的浏览器中使用 Web Assembly 运行本章的示例 (需要 WebGL2)
版权所有 (C) 2019, Herbert Wolverson.