You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
6.2 KiB
155 lines
6.2 KiB
# my-bricks 架构设计(Godot 4 + GDScript)
|
|
|
|
> 面向完全没接触过 Godot 的新手。原则:**每个脚本只做一件事**,场景之间用信号通信,参数全部可配置。
|
|
|
|
## 1. 技术选型
|
|
|
|
- **引擎**:Godot 4.x 稳定版(4.3 或更新,直接官网下载标准版)
|
|
- **语言**:GDScript(Godot 4 内置的是 GDScript 2.0,带类型标注,写起来更像 Python)
|
|
- **维度**:2D 场景,不碰 3D
|
|
- **版本控制**:Git 可选,但从 M1 开始建议每完成一个里程碑提交一次
|
|
|
|
## 2. 项目目录结构
|
|
|
|
```
|
|
my-bricks/
|
|
├── project.godot # 项目配置文件(自动生成)
|
|
├── icon.svg
|
|
├── scenes/ # 场景文件(.tscn,在编辑器里搭)
|
|
│ ├── main.tscn # 主场景:流程控制 + 游戏世界 + UI
|
|
│ ├── ball.tscn
|
|
│ ├── paddle.tscn
|
|
│ ├── brick.tscn
|
|
│ └── ui/
|
|
│ ├── main_menu.tscn
|
|
│ └── hud.tscn
|
|
├── scripts/ # 脚本(与场景同名,附着在根节点上)
|
|
│ ├── main.gd
|
|
│ ├── ball.gd
|
|
│ ├── paddle.gd
|
|
│ ├── brick.gd
|
|
│ ├── level.gd
|
|
│ └── ui/
|
|
│ ├── main_menu.gd
|
|
│ └── hud.gd
|
|
├── data/ # 数据驱动的关卡定义
|
|
│ └── level_1.gd
|
|
└── assets/
|
|
├── audio/
|
|
└── images/
|
|
```
|
|
|
|
新手习惯建议:**场景和脚本放不同目录**(scenes/ 与 scripts/),文件名一一对应,找起来不迷路。
|
|
|
|
## 3. 场景树与节点职责
|
|
|
|
主场景结构(每个框 = 一个节点,注释 = 职责):
|
|
|
|
```
|
|
Main (Node2D) # main.gd
|
|
│ # 游戏状态机、关卡切换、
|
|
│ # 分数与生命的"权威数据"
|
|
├── World (Node2D)
|
|
│ ├── Level (Node2D) # level.gd:读关卡数据,生成砖块
|
|
│ │ └── Brick * n (StaticBody2D) # brick.gd:血量、被击碎
|
|
│ ├── Paddle (CharacterBody2D) # paddle.gd:输入、移动
|
|
│ ├── Ball (CharacterBody2D) # ball.gd:发射、反弹、出界判定
|
|
│ └── LoseZone (Area2D) # 底部一条"死亡线",球碰触发扣命
|
|
├── Walls (StaticBody2D) # 左右 + 天花板(或代码夹逼,二选一)
|
|
└── UI (CanvasLayer) # 界面不受相机影响
|
|
├── HUD # hud.gd:分数/生命/提示文本
|
|
└── MainMenu # main_menu.gd:开始/退出按钮
|
|
```
|
|
|
|
**职责划分(重要)**:分数、生命、当前关卡**只有 Main 知道**;砖块不知道分数谁管,它只发"我碎了"的信号。这样以后加音效、粒子、成就,都只是多连一个信号,不用改核心逻辑。
|
|
|
|
## 4. 信号设计(解耦的关键)
|
|
|
|
用信号代替"到处 get_parent 找对象":
|
|
|
|
```
|
|
brick.gd: signal destroyed(points: int) # 砖块被击碎(带上分值)
|
|
ball.gd: signal lost # 球出界
|
|
signal brick_hit # 命中砖块(用于计连击/提速)
|
|
main.gd: 负责连接以上信号 → 加分、扣命、切关卡
|
|
```
|
|
|
|
典型写法(在 Main 里连接):
|
|
|
|
```gdscript
|
|
$World/Ball.brick_hit.connect(_on_ball_hit_brick)
|
|
$World/Ball.lost.connect(_on_ball_lost)
|
|
```
|
|
|
|
**为什么用信号**:`brick` 不需要知道"谁在计分",只广播"我碎了"。Main 收到后加分、判断是否过关。组件互不依赖,新手改起来不容易改坏别处。
|
|
|
|
## 5. 物理层设置(一次配好)
|
|
|
|
项目设置 → 物理层,给两层命名:
|
|
- **Layer 1 = World**:挡板、墙壁
|
|
- **Layer 2 = Brick**:所有砖块
|
|
|
|
球(Ball)的碰撞 mask 勾选 1 + 2:既能撞挡板/墙,也能撞砖块。
|
|
砖块之间互不碰撞,挡板不会被砖块挡住——物理交互简单可控。
|
|
|
|
## 6. 碰撞方案(新手建议)
|
|
|
|
- **球 / 挡板 / 砖块** 用 `CharacterBody2D` + `move_and_collide()`:
|
|
- 球在 `_physics_process` 里每帧手动移动,碰撞时读 `collision.get_normal()` 做镜面反射。
|
|
- 优点:速度完全由自己控制,行为可预测、好调试;比 `RigidBody2D`(模拟物理)少很多"球为什么乱飞"的玄学。
|
|
- **出界检测** 用 `Area2D`(LoseZone):Area 是"探测"不是"碰撞",球经过它 `body_entered` 信号触发扣命,最合适。
|
|
|
|
> 新手容易踩的坑:`move_and_collide` 必须在 `_physics_process`(物理帧)里调用,放进 `_process`(渲染帧)会导致表现不稳定。
|
|
|
|
## 7. 关卡数据格式
|
|
|
|
`data/level_1.gd`(用脚本存常量,最简单直观):
|
|
|
|
```gdscript
|
|
# 字符 → 砖块类型: "." = 空位, "1"/"2"/"3" = 1/2/3 血, "U" = 不可破坏
|
|
const LAYOUT: Array[String] = [
|
|
"1111111111",
|
|
"2222222222",
|
|
"3333333333",
|
|
"1.2.3.2.1",
|
|
]
|
|
```
|
|
|
|
`level.gd` 逐行逐字符读,按坐标实例化砖块。**改关卡 = 改字符串**,代码一行不用动。
|
|
|
|
## 8. 游戏状态机(简单版)
|
|
|
|
Main 里一个枚举 + `match` 分发:
|
|
|
|
```gdscript
|
|
enum GameState { MENU, PLAYING, LEVEL_CLEAR, GAME_OVER }
|
|
var state: GameState = GameState.MENU
|
|
|
|
func _process(_delta):
|
|
match state:
|
|
GameState.PLAYING:
|
|
_handle_playing()
|
|
GameState.GAME_OVER:
|
|
_handle_game_over()
|
|
```
|
|
|
|
比散落的 `if game_over: ...` 清晰得多,后期加暂停状态也容易。
|
|
|
|
## 9. 常用新手写法速查
|
|
|
|
| 场景 | 写法 |
|
|
|------|------|
|
|
| 引用节点 | `@onready var ball = $World/Ball` |
|
|
| 编辑器可调参数 | `@export var ball_speed := 400.0` |
|
|
| 每帧更新 | `func _process(delta)` 或 `func _physics_process(delta)` |
|
|
| 物理移动 | `move_and_collide(velocity * delta)` |
|
|
| 连接信号 | `node.signal_name.connect(_on_signal)` |
|
|
|
|
## 10. 关键风险与对策
|
|
|
|
| 风险 | 对策 |
|
|
|------|------|
|
|
| 球撞挡板后直上直下、不可控 | M2 优先做"按击中位置分档反弹角度" |
|
|
| 球速太快/太慢导致手感差 | 球速、加速系数全部 `@export`,随时在编辑器调 |
|
|
| 一个场景里塞太多逻辑 | 每完成一个功能检查职责是否单一,超标就拆场景/拆脚本 |
|
|
| 调试困难 | 球速调慢 + `print()` 打碰撞日志,配合 Godot 断点调试 |
|
|
|