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 | Goblin (1)、Orc (2) |
| 1 | Orc (2)、Ogre (2) |
| 2 | Orc (2)、Ogre (2)、Ettin (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 文件,在效果生成代码之后插入以下代码:
if let Some(damage) = &template.base_damage {
    commands.add_component(entity, Damage(*damage));
    if template.entity_type == EntityType::Item {
        commands.add_component(entity, 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)| {

计算攻击者造成的伤害:

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)
),

运行游戏,你会在地图上找到各种武器。

游戏发布与拓展

很多游戏项目开始时充满激情,但很多最终无法发布可玩版本。通过专注于最小可行产品并遵循设计指南,你离完成并发布 Rust 游戏更近了一步。

为游戏发布做准备

在之前的开发中,我们使用调试模式编译和运行游戏。调试模式会添加额外信息,方便调试,但会禁用一些编译器优化。为了让游戏运行更快,我们可以使用发布模式。

  1. 启用发布模式和链接时优化
    • 使用 cargo run --release 命令以发布模式运行游戏,你会发现游戏变得更快、响应更灵敏。
    • 为了进一步优化游戏,可以启用链接时优化(LTO)。打开 Cargo.toml 文件,在末尾添加以下内容:
[profile.release]
lto = "thin"

启用 LTO 后,再次使用 cargo run --release 命令运行游戏,会看到性能进一步提升。

  1. 分发游戏
    • 运行 cargo clean 命令清除当前编译的代码。
    • 运行 cargo build --release 命令以发布模式编译整个游戏。
    • 创建一个新目录来存储打包后的游戏,将以下文件和目录从项目目录复制到新目录:
      • 游戏可执行文件,位于 target/release 目录,名称与 Cargo.toml 中的项目名称相同。
      • resources 文件夹。
    • 压缩游戏目录并与朋友分享,也可以在 Itch 等网站上分享游戏。
打造属于自己的地下城爬行游戏

你可以尝试以下项目想法来改进游戏:
- 添加不同的怪物和物品,或改变现有怪物和物品。
- 改变主题以“换肤”游戏,使用不同的图块集彻底改变游戏的感觉。
- 添加计分机制。
- 如果你不喜欢回合制游戏,可以对系统进行一些调整,使其适用于实时游戏。
- 查看 Flappy Dragon 的额外内容,尝试为游戏添加动画。
- 研究 Amethyst Engine 和 Bevy 引擎,为更高级的游戏提供框架。

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

另外,开发者还计划发布一些额外内容,如 Web Assembly(WASM)、粒子效果、游戏保存功能等,你可以访问相关网站获取这些内容。

通过以上步骤,你可以打造一个功能丰富、可玩性高的游戏,并将其成功发布,与更多玩家分享。同时,不断拓展和改进游戏,让它持续发展。

游戏开发实战:从数据驱动到游戏发布

额外资源与设计文档辅助

在游戏开发过程中,还有一些额外的资源能帮助我们更好地进行开发,同时设计文档也是规划游戏的重要工具。

ASCII/Codepage 437 图表

ASCII/Codepage 437 图表可以辅助我们进行基于终端的渲染原型设计。在开发过程中,当我们需要使用终端来展示游戏画面时,这个图表能帮助我们准确地选择合适的字符来代表游戏中的元素,比如怪物、物品等,从而快速搭建起游戏的原型。

短游戏设计文档

短游戏设计文档能帮助我们聚焦核心游戏理念,避免在周边功能上花费过多时间,还能克服写作障碍,助力我们完成游戏开发。以下是关于如何利用短游戏设计文档的详细介绍:

  • 记录每一个想法 :在玩游戏和创作游戏的过程中,灵感可能随时涌现。为了不错过这些灵感,我们要随时准备好记录工具,可以是实体笔记本,也可以是电子笔记应用,如 EMACS Org Mode、Evernote、Google Keep 或 Microsoft OneNote 等。当灵感闪现时,简单记录下关键信息,比如“Pong, but with lasers” 这样的记录就很容易让人回忆起当时的想法。对于一些比较抽象的标题,如 “Metaphysical Monsters”,可以适当添加一些注释,以便后续回顾时能理解其含义。

  • 按游戏创意组织笔记 :建议将笔记按照游戏创意进行分类整理。先写下一个包含基本游戏创意的顶层笔记,然后在其下方记录相关的细节想法。这样的组织方式有助于我们清晰地梳理每个游戏创意的发展脉络,避免想法混乱。

总结与展望

通过前面的步骤,我们已经完成了从数据驱动设计到游戏发布的整个流程。从实现数据驱动的地下城,到扩展战斗系统,再到对游戏进行发布和拓展,我们逐步打造出了一个功能丰富、可玩性高的游戏。

在数据驱动设计方面,我们学会了通过数据文件来定义游戏实体和组件,这不仅减少了测试新实体的时间,还能在不修改底层代码的情况下添加新的实体。在战斗系统扩展中,我们让不同实体能够造成不同的伤害,增加了游戏的难度曲线和趣味性。在游戏发布阶段,我们通过启用发布模式和链接时优化,提高了游戏的性能,并学会了如何将游戏打包分发。

未来,我们可以根据自己的想法对游戏进行进一步的拓展和改进。可以按照前面提到的项目想法,如添加新的怪物和物品、改变游戏主题、添加计分机制等,让游戏更加丰富多样。同时,关注开发者计划发布的额外内容,如 Web Assembly(WASM)、粒子效果、游戏保存功能等,将这些新特性融入到游戏中,提升游戏的品质和用户体验。

总之,游戏开发是一个不断探索和创新的过程。我们已经掌握了基本的开发技能和方法,接下来就可以发挥自己的创造力,打造出属于自己的独特游戏,与更多的玩家分享游戏的乐趣。

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

graph LR
    A[数据驱动设计] --> B[扩展战斗系统]
    B --> C[游戏发布与拓展]
    C --> D[额外资源与设计文档辅助]
    D --> E[持续改进与创新]

希望大家在游戏开发的道路上不断前行,创造出令人惊艳的游戏作品!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值