用 Rust + Bevy 打造修仙卡牌游戏:从零上架 Windows 商店的技术复盘

6 阅读5分钟

4v63r2n7b1rmt0cw3getasz2q8_result_.gif

一名独立开发者如何用 Rust 和 Bevy 引擎,打造出拥有四相位终极特效的修仙肉鸽卡牌游戏《九界:渡劫》,并成功登陆 Windows 商店。


一、项目缘起:为什么选择 Rust + Bevy?

作为一名技术博主,我一直想挑战游戏开发。当决定要做一款修仙题材的肉鸽卡牌游戏时,我面临一个关键选择:用什么技术栈?

Unity?太重,而且个人版有启动画面限制。Godot?学习曲线尚可,但性能调优不够透明。最终,我选择了 Rust + Bevy 0.15,这个决定让我受益匪浅。

为什么是 Rust?

  • 内存安全:不用担心悬垂指针和内存泄漏
  • 零成本抽象:高级特性不会带来运行时开销
  • 生态活跃:crates.io 上有丰富的游戏开发库

为什么是 Bevy?

  • ECS 架构:数据驱动的实体组件系统,性能卓越
  • 热重载开发:代码改动即时生效,开发体验丝滑
  • 现代渲染:基于 WGPU,跨平台支持优秀

二、项目架构:模块化设计全览

bevy-card-battler/
├── src/
│   ├── main.rs          # 主入口,窗口初始化
│   ├── lib.rs           # 库入口,模块导出
│   ├── components/      # 组件定义
│   ├── systems/         # 系统实现
│   ├── plugins/         # 插件封装
│   ├── resources/       # 全局资源
│   └── states/          # 游戏状态
├── assets/              # 游戏资源
├── tests/               # 集成测试
└── docs/                # 技术文档

核心设计原则

  1. 插件化:每个功能模块都是独立 Plugin
  2. 状态驱动:使用 Bevy State 管理游戏流程
  3. 资源池化:纹理、材质统一管理

image.png

三、核心亮点:万剑归宗四相位特效

这是游戏最震撼的技能特效,我将其拆分为四个艺术相位:

第一相位:万剑齐鸣 (0% ~ 20%)

飞剑从虚空中"撕裂"而出,斜插向天际。核心是后坐力算法

// 先沉一下,再极速弹射
let progress = strike_t / 0.2;
let recoil = if progress < 0.3 {
    -30.0 * (progress / 0.3)  // 下沉
} else {
    -30.0 + 350.0 * ((progress - 0.3) / 0.7)  // 弹射
};
p.position.y = start_y + recoil;

第二相位:八卦剑轮 (20% ~ 45%)

立体三层圆锥形剑阵,每层有不同的旋转速度和呼吸节奏:

// 三层圆锥结构
for layer in 0..3 {
    let base_angle = layer_angle * layer as f32;
    let radius = layer_radius * (layer + 1) as f32;
​
    // 呼吸颤动效果
    let breathing = (time * 8.0 + layer as f32).sin() * 15.0;
    p.position.x = center_x + (angle + breathing).cos() * radius;
}

第三相位:瞬狱锁定 (45% ~ 55%)

全屏突然静止,飞剑调头指向敌人,背景变暗。关键是减速曲线

// 3次贝塞尔减速
let ease_t = cubic_bezier((strike_t - 0.45) / 0.1);
let slowdown = 1.0 - ease_t * 0.95;  // 降至 5% 速度
p.velocity *= slowdown;

第四相位:极速穿心 (55% ~ 100%)

极长残影流光,切向突刺。使用三次贝塞尔曲线实现轨迹:

// 贝塞尔曲线轨迹
let p0 = current_pos;
let p1 = current_pos + tangent * 200.0;
let p2 = enemy_pos - tangent * 100.0;
let p3 = enemy_pos;
​
let t = (strike_t - 0.55) / 0.45;
let curve_pos = cubic_bezier_point(p0, p1, p2, p3, t);
p.position = curve_pos;

四、程序化粒子系统:告别美术资源依赖

传统游戏开发需要美术提供大量粒子贴图,我用程序化生成解决了这个问题:

水墨写意晕染贴图

// 运行时生成 128x128 晕染贴图
for y in 0..height {
    for x in 0..width {
        let dx = x as f32 - 63.5;
        let dy = y as f32 - 63.5;
        let dist = (dx*dx + dy*dy).sqrt() / 64.0;
​
        // 边缘扰动模拟自然墨迹
        let angle = dy.atan2(dx);
        let noise = (angle * 4.0).sin() * 0.12
                  + (angle * 7.0).cos() * 0.08;
​
        let alpha = (1.0 - dist * dist).powi(2);
        data[idx + 3] = (alpha * 255.0) as u8;
    }
}

灵气烧灼特效(雷击)

// 多层频率噪声模拟雷击分叉
let noise = (angle * 5.0).sin() * 0.15
          + (angle * 13.0).cos() * 0.07
          + (angle * 27.0).sin() * 0.03;
​
// 中心深紫,向外烟熏扩散
scorch_data[i] = (intensity.powf(2.5) * 40.0) as u8;   // R
scorch_data[i+1] = (intensity.powf(3.0) * 15.0) as u8; // G
scorch_data[i+2] = (intensity.powf(1.8) * 70.0) as u8; // B

优势

  • 零美术依赖,全代码生成
  • 动态调整参数,即时预览
  • 体积小,整包仅 50MB

五、AI 模型集成:固有偏差补偿机制

游戏中的 3D 角色模型使用 AI 生成,但 AI 模型存在固有朝向偏差。我实现了一套实时补偿算法:

// 检测玩家与敌人的相对位置
let to_enemy = (enemy_pos - player_pos).normalize();
let to_enemy_angle = to_enemy.y.atan2(to_enemy.x);
​
// 应用固有偏差补偿(AI 模型特性)
const BIAS_COMPENSATION: f32 = std::f32::consts::PI / 2.0;
let target_angle = to_enemy_angle + BIAS_COMPENSATION;
​
// 平滑旋转插值
transform.rotation = Quat::from_rotation_z(
    lerp_angle(current_angle, target_angle, 0.15)
);

这样确保角色始终正确朝向敌人,不受 AI 模型原始朝向影响。


六、TDD 开发实践:17/17 测试全覆盖

为了确保特效质量,我采用 TDD(测试驱动开发)模式:

测试覆盖矩阵

测试类型用例数覆盖内容
时间区间验证4四相位边界检测
后坐力函数2沉降弹射曲线
圆锥结构2三层螺旋排列
呼吸颤动2正弦波偏移
NaN 防护4边界值保护
位置验证3坐标有效性

关键 Bug 修复

Bug #1: 负数 delay 导致 NaN

// ❌ 问题代码
let delay = 0.06 - (i as f32 * 0.015);  // i=11 时 = -0.105// ✅ 修复方案
let delay = (0.06 - (i as f32 * 0.015)).max(0.0);

Bug #2: 实体删除后仍更新 Transform

// ❌ 错误顺序
if p.is_dead() {
    commands.entity(entity).despawn_recursive();
}
// ... 后续代码仍尝试访问 entity// ✅ 正确顺序
// 先更新 Transform
if let Some(mut ec) = commands.get_entity(entity) {
    ec.insert(Transform::from_rotation(...));
}
// 再检查死亡
if p.is_dead() {
    commands.entity(entity).despawn_recursive();
}

七、Windows 商店上架经验

屏幕截图(1).png

打包配置

# Cargo.toml
[profile.release]
opt-level = 3      # 最高优化
lto = "fat"        # 链接时优化
codegen-units = 1  # 单编译单元
strip = true       # 去除调试符号

MSIX 打包脚本

# build_msix.bat
cargo build --release
makemsix pack ...

上架清单

  • ✅ 应用图标(多尺寸)
  • ✅ 截图(4-8 张)
  • ✅ 隐私政策(本地存储声明)
  • ✅ 内容评级
  • ✅ 年龄分级

八、性能优化技巧

1. 精准控制 Bevy 特性

# 移除默认特性,按需加载
bevy = { version = "0.15", default-features = false, features = [
    "animation", "bevy_asset", "bevy_audio", "bevy_render",
    "multi_threaded", "png", "jpeg", "vorbis", "mp3",
    "tonemapping_luts", "bevy_pbr", "bevy_picking", "bevy_state"
] }

2. 渲染设置优化

RenderPlugin {
    render_creation: WgpuSettings {
        power_preference: PowerPreference::HighPerformance,
        ..default()
    }.into(),
    ..default()
}

3. 日志系统分层

// 文件 + 控制台双层日志
let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, "jiujie.log");
let filter = EnvFilter::try_from_default_env()
    .unwrap_or_else(|_| "wgpu=error,bevy=info,jiujie=debug".into());

九、技术栈总结

技术版本用途
Rust2021 Edition核心语言
Bevy0.15游戏引擎
rand0.8随机数生成
serde1.0序列化
image0.25图像处理
winit0.30窗口管理

十、未来规划

  • 移植到 Steam 平台
  • 添加云存档同步
  • 实现成就系统
  • 支持模组加载
  • 推出 Linux 版本