21、《游戏开发:从数据驱动到成品发布》

《游戏开发:从数据驱动到成品发布》

数据驱动的游戏设计

运行游戏时,你会看到熟悉的怪物和物品,而且这次生成代码不会有冗余,你已经拥有了一个数据驱动的地下城。你还能直接从数据文件添加新怪物,并更改它们出现的关卡,无需编程。

例如,打开 resources/template.ron 文件,添加一种新的治疗药剂:

Template(
    entity_type: Item,
    name : "Weak Healing Potion", glyph : '!', levels : [ 0, 1, 2 ],
    provides: Some([ ("Healing", 2) ]),
    frequency: 2
)

运行游戏,你会发现一些药剂变成了 “弱治疗药剂”,这就是数据驱动设计的强大之处,能轻松对游戏进行修改。

扩展战斗系统

当前的战斗系统很简单,一个实体攻击另一个实体时总是造成 1 点伤害。随着冒险者在地下城中深入探索,这种战斗系统没有提供足够的难度曲线。

为了解决这个问题,我们要让不同实体造成不同程度的伤害。具体来说,要调整游戏,使玩家找到更好的武器时能造成更多伤害,一些怪物凭借更大的爪子对玩家造成更多伤害。

武器和爪子的伤害
  1. 打开 spawner/template.rs ,在 Template 结构体中添加 base_damage 字段:
#[derive(Clone, Deserialize, Debug)]
pub struct Template {
    pub entity_type : EntityType,
    pub levels : HashSet<usize>,
    pub frequency : i32,
    pub name : String,
    pub glyph : char,
    pub provides : Option<Vec<(String, i32)>>,
    pub hp : Option<i32>,
    pub base_damage : Option<i32>
}
  1. 打开 resources/template.ron ,添加怪物的伤害统计信息,并调整怪物的关卡分布,让更危险的怪物在游戏后期出现:
Template(
    entity_type: Enemy,
    name : "Goblin", glyph : 'g', levels : [ 0 ],
    hp : Some(1),
    frequency: 3,
    base_damage: Some(1)
),
Template(
    entity_type: Enemy,
    name : "Orc", glyph : 'o', levels : [ 0, 1, 2 ],
    hp : Some(2),
    frequency: 2,
    base_damage: Some(1)
),
Template(
    entity_type: Enemy,
    name : "Ogre", glyph : 'O', levels : [ 1, 2 ],
    hp : Some(5),
    frequency: 1,
    base_damage: Some(2)
),
Template(
    entity_type: Enemy,
    name : "Ettin", glyph : 'E', levels : [ 2 ],
    hp : Some(10),
    frequency: 1,
    base_damage: Some(3)
)

这样就实现了难度曲线,随着冒险者探索地下城关卡,怪物会逐渐变得更危险:
| 关卡 | 怪物(基础伤害) |
| ---- | ---- |
| 0 | 哥布林(1)、兽人(2) |
| 1 | 兽人(2)、食人魔(2) |
| 2 | 兽人(2)、食人魔(2)、双头怪(3) |

  1. 在实体列表中添加武器。武器是能造成伤害的物品,添加时包含新的 base_damage 字段:
Template(
    entity_type: Item,
    name : "Rusty Sword", glyph: '/', levels: [ 0, 1, 2 ],
    frequency: 1,
    base_damage: Some(1)
)
伤害组件
  1. 打开 components.rs ,添加新的组件类型:
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Damage(pub i32);
  1. 为了识别物品是武器,在 components.rs 文件中添加另一个组件:
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Weapon;
  1. 让玩家能造成伤害。打开 spawner/mod.rs ,在 spawn_player 函数创建玩家时添加 Damage 组件:
pub fn spawn_player(ecs : &mut World, pos : Point) {
    ecs.push(
        (Player{map_level: 0},
        pos,
        Render{
            color: ColorPair::new(WHITE, BLACK),
            glyph : to_cp437('@')
        },
        Health{ current: 10, max: 10 },
        FieldOfView::new(8),
        Damage(1)
        )
    );
}
  1. 扩展 spawn_entity 函数以处理额外的伤害信息。打开 spawner/template.rs ,找到 spawn_entity 函数,在效果生成代码后插入以下代码:
if let Some(damage) = &template.base_damage {
    commands.add_component(entity, Damage(*damage));
    if template.entity_type == EntityType::Item {
        commands.add_component(entity, Weapon{});
    }
}

这个函数会判断是否有伤害值,若有则添加到组件中,再检查实体是否为物品,若是则添加 Weapon 组件。

造成伤害

打开 systems/combat.rs ,战斗系统需要访问 Damage Carried 组件:

#[system]
#[read_component(WantsToAttack)]
#[read_component(Player)]
#[write_component(Health)]
#[read_component(Damage)]
#[read_component(Carried)]

由于计算攻击者的伤害输出需要其数据,所以要扩展受害者列表计算器,使其也返回攻击者的 Entity

let victims : Vec<(Entity, Entity, Entity)> = attackers
    .iter(ecs)
    .map(|(entity, attack)| (*entity, attack.attacker, attack.victim) )
    .collect();
victims.iter().for_each(|(message, attacker, victim)| {

之前是将受害者的生命值减 1,现在要检查攻击者并计算其造成的伤害:

let base_damage = if let Ok(v) = ecs.entry_ref(*attacker) {
    if let Ok(dmg) = v.get_component::<Damage>() {
        dmg.0
    } else {
        0
    }
} else {
    0
};

let weapon_damage : i32 = <(&Carried, &Damage)>::query().iter(ecs)
    .filter(|(carried, _)| carried.0 == *attacker)
    .map(|(_, dmg)| dmg.0)
    .sum();

let final_damage = base_damage + weapon_damage;
if let Ok(mut health) = ecs
    .entry_mut(*victim)
    .unwrap()
    .get_component_mut::<Health>()
{
    health.current -= final_damage;

运行游戏,在地下城中找到一把生锈的剑,捡起它,你造成的伤害将从 1 点变为 2 点。同时,怪物现在对玩家造成不同的伤害,使与地下城居民的战斗更有吸引力。

解决玩家武器携带问题

当冒险者捡起一把生锈的剑时会使用它,捡起第二把时也会使用。但捡起第三把剑时就不合理了。

为了让玩家一次只装备一把武器,打开 systems/player_input.rs ,添加 #[read_component(Weapon)] 到系统的组件规范中,并调整物品收集代码:

let mut items = <(Entity, &Item, &Point)>::query();
items.iter(ecs)
    .filter(|(_entity, _item, &item_pos)| item_pos == player_pos)
    .for_each(|(entity, _item, _item_pos)| {
        commands.remove_component::<Point>(*entity);
        commands.add_component(*entity, Carried(player));
        if let Ok(e) = ecs.entry_ref(*entity) {
            if e.get_component::<Weapon>().is_ok() {
                <(Entity, &Carried, &Weapon)>::query()
                    .iter(ecs)
                    .filter(|(_, c, _)| c.0 == player)
                    .for_each(|(e, c, w)| {
                        commands.remove(*e);
                    })
            }
        }
    });

这个代码会检查选中的物品是否为武器,若是则删除玩家已持有的其他武器。

添加更多武器

现在游戏支持多种武器类型,需要添加一些图形在地图上表示它们。打开 resources/template.ron ,添加更多类型的剑:

Template(
    entity_type: Item,
    name : "Rusty Sword", glyph: 's', levels: [ 0, 1, 2 ],
    frequency: 1,
    base_damage: Some(1)
),
Template(
    entity_type: Item,
    name : "Shiny Sword", glyph: 'S', levels: [ 0, 1, 2 ],
    frequency: 1,
    base_damage: Some(2)
),
Template(
    entity_type: Item,
    name : "Huge Sword", glyph: '/', levels: [ 1, 2 ],
    frequency: 1,
    base_damage: Some(3)
)

游戏现在有三种剑,其中一种只在最后两个关卡出现。运行游戏,你会在地图上找到各种武器。

游戏打包与发布

在之前的开发中,我们是在调试模式下编译和运行游戏。调试模式会给可执行文件添加额外信息,方便调试,但会禁用一些能让游戏运行更快的编译器优化。由于玩家不需要调试游戏,我们可以在发布版构建中利用这些优化。

启用发布模式和链接时优化
  • 可以使用 cargo run --release 命令在发布模式下运行游戏。运行后你会发现游戏更快、响应更灵敏。不过要测试确保游戏仍能正常运行,因为优化有时可能产生意外结果。
  • 还可以使用链接时优化(LTO)进一步优化游戏。正常编译只优化每个模块的内容,而 LTO 会分析整个程序,包括所有依赖项。要启用 LTO,打开 Cargo.toml ,在末尾添加以下部分:
[profile.release]
lto = "thin"

启用 LTO 后,每次在发布模式下编译或运行游戏时,LTO 都会优化完整的二进制文件。虽然编译和链接速度会变慢,但性能提升有时很显著。再次使用 cargo run --release 运行游戏,会看到进一步的性能提升,但不如启用发布模式时明显。

游戏分发
  1. 先运行 cargo clean 命令,清除所有当前编译的代码。因为 Rust 支持增量编译,有时会在可执行文件中添加内容而不是完全重新构建。
  2. 项目清理后,输入 cargo build --release 以发布模式编译整个游戏。
  3. 在计算机其他位置创建一个新目录来存储打包的游戏。需要从项目目录复制一些文件和目录到新目录:
    • 游戏可执行文件,位于 target/release 目录,名称与 Cargo.toml 中的项目名称相同。如果在 dungeoncrawl 项目中工作,在类 UNIX 系统上查找 dungeoncrawl ,在 Windows 上查找 dungeoncrawl.exe
    • resources 文件夹完整复制到新目录。

Rust 会将所有依赖项静态链接到可执行文件中,所以不需要任何编译后的依赖项,也不需要额外的文件或运行时。最终目录结构如下:

resources/
dungeoncrawl (或 dungeoncrawl.exe)
dungeonfont.png
MyDungeonCrawler
terminal8x8.png
template.ron

从新目录运行游戏,一切应正常运行。若有问题,需验证是否复制了所有必要的文件和文件夹。最后,压缩游戏目录并与朋友分享压缩文件,也可以在 Itch 等网站上分享游戏。

个性化地下城爬行游戏

作为游戏开发者,调整游戏并使其个性化很重要。你可以尝试以下项目想法:
- 添加或修改怪物和物品 :直接在数据文件中添加新的怪物和物品,或者修改现有怪物和物品的属性。
- 改变游戏主题 :使用不同的图块集彻底改变游戏的感觉。
- 添加计分机制 :设计一套计分规则,根据玩家的行为和成就给予相应分数。
- 转为实时游戏 :如果不喜欢回合制游戏,可以对大部分系统进行微调,并添加类似 Flappy Dragon 的计时代码,使其成为实时游戏。
- 添加动画效果 :回顾 Flappy Dragon 的额外内容,尝试为游戏添加动画。
- 使用更高级的引擎 :研究 Amethyst Engine Bevy 等基于 ECS 的引擎,为更高级的游戏开发提供框架。

最重要的是,思考你想开发的游戏,遵循设计建议,从简单开始,逐步构建梦想中的游戏。

额外内容与总结

开发者还计划发布一些额外内容,包括 Web Assembly(WASM),可将游戏发布到网页上在浏览器中玩;粒子效果,为游戏添加更多交互式图形;游戏保存功能,学习如何序列化 ECS 以便轻松保存和恢复游戏。

恭喜你,你已经完成了从学习 Rust 的 “Hello, World” 到开发 Flappy Dragon 和地下城爬行游戏的旅程。现在你有能力创建有趣的游戏,并与世界分享你的创意。希望这不是你游戏开发或 Rust 学习之旅的终点。

《游戏开发:从数据驱动到成品发布》

游戏设计文档辅助开发

在游戏开发过程中,一份好的游戏设计文档能起到很大的帮助。它可以让开发者聚焦核心游戏理念,避免在周边功能上花费过多时间,还能克服写作障碍,助力完成游戏。

记录创意灵感

在玩游戏和创作的过程中,灵感可能随时涌现,比如在洗澡、工作或者睡觉的时候。所以要准备一个记事本(实体或电子的都可以),随时记录灵感。记录时不需要太详细,只要能让自己想起当时的想法就行。例如 “Pong,但有激光” 这样的记录就很容易让人记住创意。

可以使用各种笔记系统,像 EMACS Org Mode、Evernote、Google Keep、Microsoft OneNote 等,甚至用一本纸质笔记本和一支笔也没问题。并且最好按照游戏创意来整理笔记,先写一个包含基本游戏创意的顶层笔记,再在下面记录相关的具体想法。

其他额外资源

除了上述的游戏开发内容,还提供了一些额外的资源,帮助开发者更好地进行游戏开发。

ASCII/Codepage 437 图表

提供了 ASCII/Codepage 437 图表,这个图表有助于开发者在进行基于终端的渲染原型设计时使用。它可以帮助开发者更方便地选择合适的字符来呈现游戏元素,提升游戏在终端界面的视觉效果。

Rust 语法速查表

还有一份 Rust 语法速查表,它总结了很多 Rust 语言的语法。对于开发者来说,这是一个很好的参考工具,在开发过程中遇到语法问题时,可以快速查阅,提高开发效率。

总结与展望

通过一系列的学习和实践,你已经从最初学习 Rust 的 “Hello, World” 开始,逐步掌握了开发 Flappy Dragon 和地下城爬行游戏的技能。现在的你能够创建出具有 procedurally generated maps(程序生成地图)、健康系统、近战战斗、物品、战利品和数据驱动设计等功能的游戏。

在这个过程中,你学会了使用数据文件来定义游戏的实体和组件,通过数据驱动的方式可以更轻松地对游戏进行修改和扩展。同时,你也对战斗系统进行了扩展,让游戏的战斗更具挑战性和趣味性。还学会了如何将游戏打包并发布,以及如何让游戏更符合自己的个性化需求。

未来,开发者计划发布的 Web Assembly(WASM)、粒子效果和游戏保存功能等额外内容,将进一步丰富游戏的功能和玩法。希望你能继续在游戏开发和 Rust 学习的道路上前行,不断发挥自己的创意,创造出更多有趣的游戏。

以下是一个简单的 mermaid 流程图,展示游戏开发的大致流程:

graph LR
    A[学习 Rust 基础] --> B[开发简单游戏(如 Flappy Dragon)]
    B --> C[开发地下城爬行游戏]
    C --> D[数据驱动设计与战斗系统扩展]
    D --> E[游戏打包与发布]
    E --> F[个性化游戏开发]
    F --> G[利用额外资源持续优化]

总之,游戏开发是一个充满挑战和乐趣的过程,现在你已经具备了一定的能力和知识,大胆地去尝试和创新吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值