1. 核心玩法设计
开心消消乐——核心玩法
本章你将掌握
可以学到的 Godot 技能
| 技能 | 说明 |
|---|---|
| 网格数据结构 | 用二维数组管理棋盘状态,存储每格的方块类型 |
| BFS 匹配检测 | 广度优先搜索找出所有相连的同色方块 |
| Tween 动画 | 用 Tween 实现方块交换、下落、消除的流畅动画 |
| 连锁消除 | 消除后下落,再次检测,实现连锁爆炸效果 |
| 关卡数据设计 | 用 JSON/Resource 定义关卡目标和障碍物 |
| 触摸/鼠标输入 | 处理点击和滑动手势,兼容手机和电脑 |
| 特殊方块逻辑 | 炸弹、彩虹方块等特殊效果的实现 |
关键 Node 节点
MatchThreeGame(Node2D)
├── GameBoard(Node2D) ← 棋盘容器
│ └── Gem(TextureRect)×N ← 每个方块(动态生成)
├── GameTimer(Timer) ← 关卡计时
├── UI(CanvasLayer)
│ ├── ScoreLabel(Label)
│ ├── TargetLabel(Label) ← 关卡目标
│ ├── MovesLabel(Label) ← 剩余步数
│ ├── LevelCompletePanel(Control)
│ └── GameOverPanel(Control)
├── ParticleEffects(Node2D) ← 消除粒子特效
│ └── BurstParticles(CPUParticles2D)
└── AudioPlayer(AudioStreamPlayer)游戏核心节点
点击节点可跳转到对应文档查看详细说明。
游戏系统结构图
┌─────────────────────────────────────────────────────┐
│ 三消游戏系统架构 │
├─────────────────────────────────────────────────────┤
│ │
│ 输入系统 棋盘系统 特效系统 │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ │
│ │点击/滑动│──────▶│ 选择检测 │ │ Tween动画│ │
│ └────────┘ └────┬─────┘ └──────────┘ │
│ │ │
│ ┌────▼─────┐ ┌──────────┐ │
│ │ 交换逻辑 │─────▶│ 粒子特效 │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ ┌────▼─────┐ ┌──────────┐ │
│ │ 匹配检测 │─────▶│ 计分系统 │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ ┌────▼─────┐ ┌──────────┐ │
│ │ 下落填充 │─────▶│ 关卡系统 │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────┘1.1 什么是三消游戏?
你一定玩过"消消乐"这类游戏——屏幕上摆满了五颜六色的方块,你交换相邻的两个方块,如果有三个或更多相同颜色的方块排成一行或一列,它们就会"嘭"地一下消除掉,上面的方块掉下来填补空位,新的方块从顶部冒出来。
简单来说,三消游戏就像整理衣柜:你把衣服(方块)按颜色排列,排好的拿走,空出来的位置再塞进新衣服,继续整理。
三消游戏的"五步循环"
每一次成功的消除,都会经历以下五个步骤:
| 步骤 | 名称 | 比喻 | 说明 |
|---|---|---|---|
| 1 | 交换 | 挑两件衣服换位置 | 玩家点击两个相邻方块,交换它们的位置 |
| 2 | 匹配检测 | 检查有没有三件同色的 | 系统扫描整个棋盘,找横排或竖排的连续同色方块 |
| 3 | 消除 | 把排好的衣服收起来 | 匹配到的方块消失,产生分数 |
| 4 | 下落 | 上面的衣服掉下来 | 上方的方块因重力向下掉落,填补空位 |
| 5 | 连锁 | 掉下来后又排好了 | 下落后可能产生新的匹配,回到步骤2继续 |
这个循环会一直持续,直到棋盘上没有新的匹配为止。这就是为什么有时候你只交换了一次,却能看到一大片方块"连锁爆炸"——那是因为每次消除后下落的方块,又碰巧凑成了新的三消。
1.2 为什么要做三消游戏?
三消游戏是最适合入门的游戏类型之一,原因有三:
- 规则极简:玩家一秒钟就能学会——"交换两个方块,凑三个一样的就行"
- 技术全面:虽然规则简单,但实现起来涉及数组操作、动画系统、状态机、粒子特效等核心游戏开发技术
- 乐趣十足:连锁消除的爽感让人停不下来,做好后连你自己都会忍不住多玩几局
1.3 设计原则
在动手写代码之前,我们先确定几个关键的设计决策。这些决策会影响整个项目的架构。
棋盘尺寸
| 方案 | 说明 | 优劣 |
|---|---|---|
| 6x6 | 小棋盘,适合手机竖屏 | 游戏节奏快,但策略空间小 |
| 8x8 | 经典尺寸,大多数三消游戏采用 | 平衡了策略性和可玩性 |
| 10x10 | 大棋盘,策略性强 | 手机上看着拥挤,新手容易眼花 |
我们选择 8x8,这是经过无数三消游戏验证的"黄金尺寸"。
方块种类
| 方块种类 | 说明 | 难度影响 |
|---|---|---|
| 4种 | 红蓝绿黄 | 太简单,随便一划就消了 |
| 5种 | 红蓝绿黄紫 | 适中,经典三消游戏的标配 |
| 6种 | 增加橙色 | 较难,不容易凑齐三个 |
我们选择 5种 方块。种类太多会导致很难找到匹配,种类太少又缺乏挑战。
匹配规则
最基本的规则是"三个或以上相同方块排成一行/列即可消除"。但很多经典三消游戏还支持四消和五消产生特殊方块:
- 3消:普通消除
- 4消:产生条纹方块(可以炸掉一整行或一整列)
- 5消直线:产生彩虹球(可以消除棋盘上所有同色方块)
- 5消L/T形:产生炸弹(可以炸掉周围一片区域)
这些特殊方块我们会在第7章详细实现。先把基础的3消做好。
1.4 核心循环的状态机
整个游戏可以用一个"状态机"来管理。什么是状态机?你可以把它想象成一个红绿灯系统:每个时刻只能处于一种状态(红灯、绿灯、黄灯),当满足某个条件时,才从一种状态切换到另一种状态。
我们的游戏有以下状态:
┌─────────┐ 玩家点击方块 ┌─────────┐ 交换完成 ┌─────────┐
│ 等待输入 │ ───────────────→ │ 交换中 │ ────────────→ │ 匹配检测 │
└─────────┘ └─────────┘ └─────────┘
↑ │
│ ┌─────────┐ 下落完成 │
└────────────────── │ 下落中 │ ←─────────────────────┘
└─────────┘
↑
│ 消除完成
┌─────────┐
没有匹配 ←──────── │ 消除中 │
└─────────┘下面我们用代码来定义这些状态。
定义游戏状态枚举
/// <summary>
/// 游戏状态枚举
/// 每种状态代表游戏在某一时刻正在做什么
/// </summary>
public enum GameState
{
/// <summary>等待玩家操作</summary>
Idle, // "我准备好了,你来操作吧"
/// <summary>正在播放交换动画</summary>
Swapping, // "两个方块正在交换位置"
/// <summary>正在检测匹配</summary>
Checking, // "让我看看有没有凑齐三个的"
/// <summary>正在播放消除动画</summary>
Removing, // "匹配到了,正在消除"
/// <summary>正在播放下落动画</summary>
Falling, // "上面的方块正在往下掉"
/// <summary>正在等待用户确认(如暂停菜单)</summary>
Paused // "游戏暂停了"
}## 游戏状态枚举
## 每种状态代表游戏在某一时刻正在做什么
enum GameState {
IDLE, ## 等待玩家操作 —— "我准备好了,你来操作吧"
SWAPPING, ## 正在播放交换动画 —— "两个方块正在交换位置"
CHECKING, ## 正在检测匹配 —— "让我看看有没有凑齐三个的"
REMOVING, ## 正在播放消除动画 —— "匹配到了,正在消除"
FALLING, ## 正在播放下落动画 —— "上面的方块正在往下掉"
PAUSED ## 游戏暂停了
}状态管理器
状态管理器负责控制状态之间的切换。它就像一个交通警察,决定什么时候可以"放行"(切换状态),什么时候必须"等待"。
using Godot;
public partial class GameManager : Node
{
// 当前游戏状态,默认为"等待输入"
private GameState _currentState = GameState.Idle;
/// <summary>
/// 当前游戏状态
/// 只有在 Idle 状态下才允许玩家操作
/// </summary>
public GameState CurrentState
{
get => _currentState;
private set
{
var oldState = _currentState;
_currentState = value;
GD.Print($"状态切换: {oldState} → {_currentState}");
OnStateChanged(oldState, _currentState);
}
}
/// <summary>
/// 状态切换时的回调
/// </summary>
private void OnStateChanged(GameState oldState, GameState newState)
{
switch (newState)
{
case GameState.Idle:
// 回到空闲状态,等待玩家操作
GD.Print("等待玩家操作...");
break;
case GameState.Swapping:
// 开始交换动画
GD.Print("播放交换动画...");
break;
case GameState.Checking:
// 检查匹配
GD.Print("检测匹配...");
break;
case GameState.Removing:
// 消除匹配的方块
GD.Print("消除方块...");
break;
case GameState.Falling:
// 方块下落
GD.Print("方块下落...");
break;
}
}
public override void _Ready()
{
GD.Print("开心消消乐 —— 游戏初始化完成");
CurrentState = GameState.Idle;
}
}extends Node
## 当前游戏状态,默认为"等待输入"
var current_state: int = GameState.IDLE
## 当前游戏状态的 getter/setter
## 只有在 IDLE 状态下才允许玩家操作
var state: int:
get:
return current_state
set(value):
var old_state := current_state
current_state = value
print("状态切换: %s → %s" % [old_state, current_state])
_on_state_changed(old_state, current_state)
## 状态切换时的回调
func _on_state_changed(old_state: int, new_state: int) -> void:
match new_state:
GameState.IDLE:
# 回到空闲状态,等待玩家操作
print("等待玩家操作...")
GameState.SWAPPING:
# 开始交换动画
print("播放交换动画...")
GameState.CHECKING:
# 检查匹配
print("检测匹配...")
GameState.REMOVING:
# 消除匹配的方块
print("消除方块...")
GameState.FALLING:
# 方块下落
print("方块下落...")
func _ready() -> void:
print("开心消消乐 —— 游戏初始化完成")
state = GameState.IDLE1.5 方块的数据结构
在棋盘上,每个方块需要记录两件事:
- 它的类型(颜色):是红色、蓝色、还是其他颜色?
- 它在棋盘上的位置:第几行第几列?
我们用一个枚举来表示方块的颜色类型。
/// <summary>
/// 方块类型枚举
/// 每种类型对应一种颜色
/// </summary>
public enum PieceType
{
/// <summary>红色方块</summary>
Red,
/// <summary>蓝色方块</summary>
Blue,
/// <summary>绿色方块</summary>
Green,
/// <summary>黄色方块</summary>
Yellow,
/// <summary>紫色方块</summary>
Purple,
/// <summary>空位(没有方块)</summary>
Empty
}## 方块类型枚举
## 每种类型对应一种颜色
enum PieceType {
RED, ## 红色方块
BLUE, ## 蓝色方块
GREEN, ## 绿色方块
YELLOW, ## 黄色方块
PURPLE, ## 紫色方块
EMPTY ## 空位(没有方块)
}1.6 本章小结
本章我们了解了三消游戏的核心概念:
| 概念 | 说明 |
|---|---|
| 五步循环 | 交换 → 匹配检测 → 消除 → 下落 → 连锁 |
| 状态机 | 管理游戏在不同阶段之间的切换 |
| 棋盘 | 8x8 的二维网格 |
| 方块 | 5种颜色,用枚举表示 |
| 游戏状态 | Idle、Swapping、Checking、Removing、Falling、Paused |
下一章,我们将搭建项目的骨架——创建场景结构、配置 GameManager、并准备好基础的节点树。有了这个骨架,后续的棋盘系统、方块移动、计分系统才能一步步往上"长"出来。
