一个使用 Rust + Macroquad 开发的高度可扩展贪吃蛇游戏,采用模块化架构设计。
- 丰富的果实系统: 15种不同果实,包括普通、陷阱、功能和特殊类型
- AI蛇系统: 蛇蛋孵化出的AI蛇,增加游戏挑战性
- 多种Buff效果: 护盾、速度、幽灵、冰冻、减速、眩晕、粘液等
- 沙虫模式: 独特的多阶段变身动画,可吞噬AI蛇
- 视觉特效: 粒子系统、血迹、传送门、残影效果
- 音效系统: 程序化生成的WAV音效
- Combo系统: 连续吃果实获得额外分数
# 编译并运行
cargo run --bin snake2d
# 运行测试
cargo test
# 发布构建
cargo build --release
| 按键 |
功能 |
| ↑↓←→ |
移动方向 |
| Space |
暂停/继续 |
| Enter/R |
重新开始 |
| W |
切换穿墙模式 |
src/
├── lib.rs # 库入口,导出所有模块
├── snake2d_v2.rs # 游戏主入口(GameWorld)
├── constants.rs # 游戏常量配置
│
├── types/ # 数据类型定义
│ ├── mod.rs # 模块导出
│ ├── game_state.rs # 游戏状态枚举
│ ├── buff.rs # Buff状态结构体
│ ├── damage.rs # 受伤状态
│ ├── particle.rs # 粒子结构体
│ ├── portal.rs # 传送门结构体
│ ├── combo.rs # Combo状态
│ └── fruit.rs # 果实实例
│
├── game/ # 游戏核心逻辑
│ ├── mod.rs # 模块导出
│ ├── snake.rs # 蛇状态和移动
│ ├── collision.rs # 碰撞检测
│ ├── spawn.rs # 生成逻辑
│ ├── spawn_manager.rs # 声明式果实生成管理器
│ ├── buff_manager.rs # Buff管理
│ ├── bomb_manager.rs # 炸弹逻辑封装
│ ├── damage_manager.rs # 受伤动画
│ ├── fruit_handler.rs # 果实消费处理
│ ├── ai_snake.rs # AI蛇结构体和行为
│ └── ai_manager.rs # AI蛇管理器
│
├── render/ # 渲染模块
│ ├── mod.rs # 模块导出
│ ├── snake_renderer.rs # 蛇渲染
│ ├── fruit_renderer.rs # 果实渲染
│ ├── effect_renderer.rs # 特效渲染
│ ├── hud_renderer.rs # HUD渲染
│ ├── sandworm_renderer.rs # 沙虫渲染
│ └── ai_snake_renderer.rs # AI蛇渲染
│
├── audio/ # 音效模块
│ ├── mod.rs # 模块导出
│ └── sound_manager.rs # 音效管理器
│
└── fruits/ # 果实系统(可扩展)
├── mod.rs # 模块导出和注册表创建
├── fruit_trait.rs # FruitBehavior Trait定义
├── fruit_registry.rs # 果实注册表(含动态权重计算)
├── fruit_context.rs # 扩展的果实上下文(可直接操作游戏资源)
├── normal/ # 普通果实
├── trap/ # 陷阱果实(含炸弹)
├── power/ # 功能果实
└── special/ # 特殊果实(蛇蛋带 on_expire 回调)
- 关注点分离: 游戏逻辑、渲染、音效完全独立
- Trait驱动: 果实系统通过
FruitBehavior Trait 实现可扩展性
- 数据驱动: 果实配置与行为分离,便于调整平衡性
- 声明式架构: 通过
FruitSpawnManager 配置生成规则,无需修改主循环
- 回调封装: 果实的
on_consume/on_expire 直接操作游戏资源
- 属性测试: 使用 proptest 验证核心逻辑正确性
┌─────────────────────────────────────────────────────────┐
│ snake2d_v2.rs │
│ (游戏主循环) │
└─────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ game │ │ render │ │ audio │ │ fruits │
│ (逻辑) │ │ (渲染) │ │ (音效) │ │ (果实) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
└──────────────┴──────────────┴──────────────┘
│
▼
┌─────────────┐
│ types │
│ (数据结构) │
└─────────────┘
│
▼
┌─────────────┐
│ constants │
│ (常量) │
└─────────────┘
- 创建果实结构体
// src/fruits/power/my_fruit.rs
use macroquad::prelude::*;
use crate::fruits::{FruitBehavior, FruitConfig, FruitCategory, FruitContext, ConsumeResult};
use crate::constants::CELL;
pub struct MyFruit {
config: FruitConfig,
}
impl MyFruit {
pub fn new() -> Self {
MyFruit {
config: FruitConfig {
id: "my_fruit", // 唯一标识
name: "我的果实", // 显示名称
category: FruitCategory::Power, // 类别
color: Color { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }, // 基础颜色
lifetime: 10.0, // 存在时长(0=永久)
spawn_weight: 10, // 生成权重(基础值)
unlock_length: 5, // 解锁所需蛇长
immune_to_buffs: false, // 是否受Buff免疫影响
weight_growth: 0, // 权重增长系数(0=不增长)
},
}
}
}
impl FruitBehavior for MyFruit {
fn config(&self) -> &FruitConfig {
&self.config
}
fn on_consume(&self, ctx: &mut FruitContext) -> ConsumeResult {
// 实现消费效果
ctx.snake.push(ctx.snake.last().cloned().unwrap()); // 增长一节
ConsumeResult::AddScore(50)
}
// 可选:自定义渲染
fn render(&self, x: f32, y: f32, time: f32) {
let pulse = (time * 4.0).sin() * 0.2 + 0.8;
let color = Color { a: pulse, ..self.config.color };
draw_rectangle(x, y, CELL, CELL, color);
}
}
- 注册果实
// src/fruits/mod.rs
pub mod my_fruit;
use my_fruit::MyFruit;
pub fn create_fruit_registry() -> FruitRegistry {
let mut registry = FruitRegistry::new();
// ... 其他果实
registry.register(Box::new(MyFruit::new()));
registry
}
- 在 BuffState 中添加字段
// src/types/buff.rs
pub struct BuffState {
// ... 现有字段
pub my_buff_active: bool,
pub my_buff_timer: f32,
}
- 在 buff_manager 中添加更新逻辑
// src/game/buff_manager.rs
impl BuffState {
pub fn update(&mut self, dt: f32) {
// ... 现有逻辑
if self.my_buff_active {
self.my_buff_timer -= dt;
if self.my_buff_timer <= 0.0 {
self.my_buff_active = false;
}
}
}
pub fn activate_my_buff(&mut self) {
self.my_buff_active = true;
self.my_buff_timer = 5.0;
}
}
- 在渲染中添加视觉效果
// src/render/snake_renderer.rs
pub fn draw_snake(...) {
// ... 现有逻辑
if buff.my_buff_active {
// 自定义视觉效果
}
}
// src/render/effect_renderer.rs
pub fn draw_my_effect(snake_body: &[IVec2], time: f32) {
for seg in snake_body {
let x = seg.x as f32 * CELL;
let y = seg.y as f32 * CELL;
// 绘制效果
}
}
项目使用 proptest 进行属性测试,验证核心逻辑的正确性:
# 运行所有测试
cargo test
# 运行特定模块测试
cargo test game::snake
cargo test game::collision
cargo test audio::sound_manager
| 属性 |
描述 |
验证内容 |
| Property 1 |
蛇移动保持长度 |
移动不改变蛇身长度 |
| Property 2 |
碰撞检测正确性 |
自撞检测逻辑正确 |
| Property 3 |
生成位置有效性 |
生成位置不与蛇身/果实重叠 |
| Property 4 |
Buff计时器单调递减 |
计时器正确递减 |
| Property 5 |
WAV格式有效性 |
生成的WAV文件格式正确 |
| Property 6 |
果实解锁条件 |
解锁逻辑正确 |
- 固定时间步长: 游戏逻辑以固定间隔更新,渲染帧率独立
- 插值渲染: 蛇身位置在帧间插值,实现平滑移动
- 粒子池: 粒子系统自动回收过期粒子
- Release构建: 启用LTO和单代码单元优化
| 果实 |
效果 |
| 陷阱 |
蛇缩短2节,受伤动画 |
| 冰冻 |
冻结2秒 |
| 减速 |
移动速度减半5秒 |
| 眩晕 |
方向随机偏移5秒 |
| 粘液 |
移动拖拽效果5秒 |
| 炸弹 |
吞食后炸弹在体内移动,到达中间位置时爆炸,将蛇拦腰炸断(蛇越长出现概率越高) |
| 果实 |
效果 |
| 护盾 |
免疫负面效果10秒 |
| 速度 |
移动速度翻倍5秒 |
| 幽灵 |
可穿过任何物体5秒(墙壁、自身),AI蛇也可穿过玩家,但不免疫Debuff |
| 反转 |
头尾互换 |
| 沙虫 |
触发沙虫全屏吞噬 |
| 恢复 |
瞬间清除所有Debuff(包括炸弹),+20分 |
| 果实 |
效果 |
| 幸运方块 |
随机触发其他果实效果(包括生成AI蛇) |
| 蛇蛋 |
过期时自动孵化AI蛇;吃掉可阻止孵化(+5分) |
- 蛇蛋果实: 蛇蛋过期时自动孵化AI蛇(吃掉蛇蛋可阻止孵化)
- 幸运方块: 负面效果之一是生成AI蛇
- 随机颜色(橙、紫、粉、天蓝、金、洋红)
- 会寻找并吃掉食物和果实
- 吃到食物会增长
- 会受到Buff影响(冰冻、减速、眩晕等)
- 最多同时存在3条AI蛇
| 碰撞类型 |
结果 |
| 玩家蛇头 → AI蛇身体 |
玩家死亡(除非有护盾/幽灵) |
| AI蛇头 → 玩家身体 |
AI蛇死亡,掉落食物 |
| AI蛇头 → 其他AI蛇 |
AI蛇死亡,掉落食物 |
| 沙虫 → AI蛇 |
AI蛇被吞噬(不掉落) |
AI蛇死亡时,身体每节变成一个食物,随机散落在附近,10秒后消失。
MIT License
欢迎提交 Issue 和 Pull Request!