初次提交
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
# 03 资源替换指南 (Asset Replacement Guide)
|
||||
|
||||
> 当前项目使用**程序化占位**(几何体 + 蜂鸣音),所有接口已就绪。本文说明如何无缝替换为正式美术资源,**不需要修改任何业务逻辑代码**。
|
||||
|
||||
---
|
||||
|
||||
## A. 音效替换
|
||||
|
||||
### 当前状态
|
||||
|
||||
`AudioManager` 在 `res://audio/sfx/<id>.ogg`(或 `.wav` / `.mp3`)**不存在**时自动回退到程序化蜂鸣。文件存在即优先加载真实音效。
|
||||
|
||||
### 替换步骤
|
||||
|
||||
**1. 创建目录并放入文件**
|
||||
|
||||
```
|
||||
res://audio/sfx/
|
||||
hit.ogg ← 子弹命中音效
|
||||
kill.ogg ← 敌人死亡
|
||||
hurt.ogg ← 玩家受击
|
||||
wave_complete.ogg ← 波次结算
|
||||
boss.ogg ← Boss 入场
|
||||
shop.ogg ← 商店开启
|
||||
buy.ogg ← 购买法术
|
||||
level_up.ogg ← 升级
|
||||
```
|
||||
|
||||
**2. 确认格式要求**
|
||||
|
||||
| 属性 | 推荐值 |
|
||||
|:---|:---|
|
||||
| 格式 | OGG Vorbis(体积小)/ WAV PCM(低延迟) |
|
||||
| 采样率 | 44100 Hz 或 22050 Hz |
|
||||
| 声道 | 单声道(Mono)即可,立体声也支持 |
|
||||
| 时长 | 音效 < 3s;BGM 循环 |
|
||||
| 响度 | 建议 -14 LUFS 标准化后导入 |
|
||||
|
||||
**3. Godot 导入设置**(在编辑器 FileSystem 面板右键文件 → Import)
|
||||
|
||||
| 设置 | 推荐值 |
|
||||
|:---|:---|
|
||||
| Loop | 音效:`Off`;BGM:`On` |
|
||||
| Compression Mode | `quality` OGG / `PCM` WAV |
|
||||
| Max Texture Size | 不适用(音频) |
|
||||
|
||||
**4. 添加新音效 ID**
|
||||
|
||||
在 `AudioManager._SFX_DEFS` 中追加一行(定义占位蜂鸣,有真实文件时忽略):
|
||||
|
||||
```gdscript
|
||||
# audio_manager.gd
|
||||
const _SFX_DEFS: Dictionary = {
|
||||
# ... 已有 ...
|
||||
"spell_cast": [600.0, 80.0], # 新增:频率 600Hz,80ms
|
||||
}
|
||||
```
|
||||
|
||||
然后在需要播放的地方调用:
|
||||
|
||||
```gdscript
|
||||
AudioManager.play("spell_cast", spawn_pos)
|
||||
```
|
||||
|
||||
### BGM 系统(预留接口)
|
||||
|
||||
当前无 BGM 系统,添加时建议在 `AudioManager` 中扩展:
|
||||
|
||||
```gdscript
|
||||
# 建议扩展方向(未实现,供参考)
|
||||
func play_bgm(track_id: String, fade_sec: float = 1.0) -> void:
|
||||
var path = "res://audio/bgm/%s.ogg" % track_id
|
||||
# 跨淡 + loop...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## B. 敌人 / 玩家 精灵替换
|
||||
|
||||
### 当前状态
|
||||
|
||||
- **玩家**:`combat_s2.gd` 中程序化 `Polygon2D`(青色方块)
|
||||
- **敌人**:`EnemyManager.sync_multimesh()` 驱动 `MultiMeshInstance2D`,使用单位 `QuadMesh` + 颜色覆盖
|
||||
|
||||
### 替换玩家精灵
|
||||
|
||||
`combat_s2.gd` → `_setup_world_view()`,找到玩家标记部分替换:
|
||||
|
||||
```gdscript
|
||||
# 当前(几何占位)
|
||||
var marker := Polygon2D.new()
|
||||
marker.polygon = PackedVector2Array([...])
|
||||
marker.color = Color(0.35, 0.9, 1.0)
|
||||
_player_node.add_child(marker)
|
||||
|
||||
# 替换为精灵(修改为)
|
||||
var marker := Sprite2D.new()
|
||||
marker.texture = load("res://assets/sprites/player.png")
|
||||
marker.scale = Vector2(0.5, 0.5) # 按实际尺寸调整
|
||||
_player_node.add_child(marker)
|
||||
```
|
||||
|
||||
### 替换敌人渲染(MultiMesh + Atlas)
|
||||
|
||||
敌人使用 `MultiMeshInstance2D`,单次 Draw Call 渲染所有实例。替换步骤:
|
||||
|
||||
**步骤 1:准备 Atlas 贴图**
|
||||
|
||||
将所有敌人原型排列在一张贴图上(建议 512×512):
|
||||
|
||||
```
|
||||
atlas_enemies.png
|
||||
┌────┬────┬────┐
|
||||
│BASIC│FAST│ARM.│ 每格 64×64 px
|
||||
├────┼────┼────┤
|
||||
│ELITE│MIN.│BOSS│
|
||||
└────┴────┴────┘
|
||||
```
|
||||
|
||||
**步骤 2:修改 MultiMesh 使用 Atlas**
|
||||
|
||||
`combat_s2.gd` → `_setup_world_view()` 中的敌人 MMI 部分:
|
||||
|
||||
```gdscript
|
||||
# 原:使用 QuadMesh 纯色
|
||||
var quad := QuadMesh.new()
|
||||
quad.size = Vector2(1.0, 1.0)
|
||||
_enemy_mm.mesh = quad
|
||||
|
||||
# 替换:使用 PlaneMesh + Atlas 材质
|
||||
var mesh := PlaneMesh.new()
|
||||
mesh.size = Vector2(1.0, 1.0)
|
||||
var mat := CanvasItemMaterial.new()
|
||||
# 或使用 ShaderMaterial + 自定义 Shader 实现 UV 偏移(按 enemy_type 选择 Atlas 区域)
|
||||
_enemy_mm.mesh = mesh
|
||||
var mmi := MultiMeshInstance2D.new()
|
||||
mmi.multimesh = _enemy_mm
|
||||
mmi.texture = load("res://assets/sprites/atlas_enemies.png")
|
||||
add_child(mmi)
|
||||
```
|
||||
|
||||
**步骤 3(进阶):Shader 按 instance_custom_data 选 UV**
|
||||
|
||||
```glsl
|
||||
// res://shaders/enemy_atlas.gdshader
|
||||
shader_type canvas_item;
|
||||
uniform sampler2D atlas_tex;
|
||||
uniform int cols = 3; // atlas 列数
|
||||
|
||||
void fragment() {
|
||||
// INSTANCE_CUSTOM.x 存储 enemy_type (0-5)
|
||||
float etype = INSTANCE_CUSTOM.x;
|
||||
float row = floor(etype / float(cols));
|
||||
float col = mod(etype, float(cols));
|
||||
vec2 uv = (UV + vec2(col, row)) / vec2(float(cols), 2.0);
|
||||
COLOR = texture(atlas_tex, uv) * COLOR;
|
||||
}
|
||||
```
|
||||
|
||||
在 `EnemyManager.sync_multimesh()` 中写入 custom data:
|
||||
|
||||
```gdscript
|
||||
# enemy_manager.gd sync_multimesh() 内,在 set_instance_transform_2d 之后
|
||||
mm.set_instance_custom_data(i, Color(float(etype) / 255.0, 0, 0, 0))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## C. 子弹精灵替换
|
||||
|
||||
子弹同样使用 `MultiMeshInstance2D`(`_bullet_mm`)。替换为 Atlas 方式与敌人相同,但子弹通常使用 `damage_type` 区分外观。
|
||||
|
||||
`BulletManager.sync_multimesh()` 中写入 custom data(damage_type):
|
||||
|
||||
```gdscript
|
||||
# bullet_manager.gd sync_multimesh() 内追加
|
||||
mm.set_instance_custom_data(i, Color(float(_data[base + 8]) / 255.0, 0, 0, 0))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## D. VFX 特效替换
|
||||
|
||||
### 当前状态
|
||||
|
||||
`VFXManager` 使用程序化彩色矩形作为特效占位(`ColorRect`/`Sprite2D`)。
|
||||
|
||||
### 替换为粒子特效
|
||||
|
||||
`scripts/autoloads/vfx_manager.gd` → `_instantiate_vfx()` 中修改 `_vfx_scenes` 映射:
|
||||
|
||||
**步骤 1:制作粒子场景**
|
||||
|
||||
为每个特效 ID 建一个场景文件(`GPUParticles2D` 为根节点,`one_shot = true`):
|
||||
|
||||
```
|
||||
res://scenes/vfx/
|
||||
hit_spark.tscn ← 命中火花
|
||||
status_burn.tscn ← 燃烧特效
|
||||
status_poison.tscn ← 中毒特效
|
||||
cast_flash.tscn ← 施法闪光
|
||||
death_burst.tscn ← 敌人死亡
|
||||
zone_expire.tscn ← 地面效果消散
|
||||
```
|
||||
|
||||
每个场景设置:
|
||||
- `GPUParticles2D.one_shot = true`(**必须**,P6-N72)
|
||||
- `GPUParticles2D.emitting = false`(代码控制时机)
|
||||
- `lifetime` = 特效持续时长
|
||||
|
||||
**步骤 2:修改 `_vfx_scenes` 预加载表**
|
||||
|
||||
```gdscript
|
||||
# vfx_manager.gd
|
||||
func _ready() -> void:
|
||||
_vfx_scenes = {
|
||||
"hit_spark": preload("res://scenes/vfx/hit_spark.tscn"),
|
||||
"status_burn": preload("res://scenes/vfx/status_burn.tscn"),
|
||||
# ... 其他 ...
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 3:让 VFX 播放时自动启动粒子**
|
||||
|
||||
> **重要**:`one_shot = true` 和 `emitting = false` 须在**场景文件内**就设好(Godot 编辑器中设置属性),这是 P6-N72 强制要求。运行时 `VFXManager.play()` 只负责把节点拖出池并触发 `emitting = true`。
|
||||
|
||||
```gdscript
|
||||
# vfx_manager.gd → play() 内,instance 创建后追加:
|
||||
if node is GPUParticles2D:
|
||||
node.emitting = true # 启动粒子(one_shot 已在场景中设置,这里不必覆盖)
|
||||
```
|
||||
|
||||
### 特效 ID 参考
|
||||
|
||||
| ID | 触发时机 |
|
||||
|:---|:---|
|
||||
| `"hit_spark"` | 子弹命中敌人 |
|
||||
| `"status_burn"` | 施加 BURN 状态 |
|
||||
| `"status_poison"` | 施加 POISON 状态 |
|
||||
| `"cast_flash"` | 施法时玩家周围 |
|
||||
| `"death_burst"` | 敌人死亡 |
|
||||
| `"zone_expire"` | 地面效果消失 |
|
||||
|
||||
---
|
||||
|
||||
## E. UI 界面图片替换
|
||||
|
||||
当前 UI 全为程序化构建(`ColorRect` + `Label` + `Button`),无独立 `.tscn` 场景。替换为美术 UI 的推荐路径:
|
||||
|
||||
**选项 A:保留程序化,只替换 Theme**
|
||||
|
||||
在 `combat_s2.gd` 的各 `_setup_*` 函数中,为 `Button`/`Label` 应用自定义 Theme:
|
||||
|
||||
```gdscript
|
||||
var theme := load("res://assets/ui/main_theme.tres") # 你制作的 Theme 资源
|
||||
btn.theme = theme
|
||||
```
|
||||
|
||||
**选项 B:迁移到独立 .tscn 场景**(推荐,长期)
|
||||
|
||||
1. 为商店、背包、结算屏分别建 `.tscn`
|
||||
2. 在 `_setup_shop_ui()` 等函数中替换为 `load("res://scenes/ui/shop.tscn").instantiate()`
|
||||
3. 通过信号 / Callable 将原有事件(买法术、刷新等)连接到新场景节点
|
||||
|
||||
> **注意**:迁移 UI 时须保留对 `_shop_btns`、`_reroll_btn` 等引用的赋值,否则商店逻辑失效。
|
||||
|
||||
---
|
||||
|
||||
## F. 资源路径约定
|
||||
|
||||
| 类型 | 约定路径 |
|
||||
|:---|:---|
|
||||
| 音效 | `res://audio/sfx/<id>.ogg` |
|
||||
| BGM | `res://audio/bgm/<track>.ogg` |
|
||||
| 玩家精灵 | `res://assets/sprites/player.png` |
|
||||
| 敌人 Atlas | `res://assets/sprites/atlas_enemies.png` |
|
||||
| VFX 场景 | `res://scenes/vfx/<id>.tscn` |
|
||||
| UI Theme | `res://assets/ui/main_theme.tres` |
|
||||
| UI 场景 | `res://scenes/ui/<name>.tscn` |
|
||||
| 字体 | `res://assets/fonts/<name>.ttf` |
|
||||
</content>
|
||||
Reference in New Issue
Block a user