8.2 KiB
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 中追加一行(定义占位蜂鸣,有真实文件时忽略):
# audio_manager.gd
const _SFX_DEFS: Dictionary = {
# ... 已有 ...
"spell_cast": [600.0, 80.0], # 新增:频率 600Hz,80ms
}
然后在需要播放的地方调用:
AudioManager.play("spell_cast", spawn_pos)
BGM 系统(预留接口)
当前无 BGM 系统,添加时建议在 AudioManager 中扩展:
# 建议扩展方向(未实现,供参考)
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(),找到玩家标记部分替换:
# 当前(几何占位)
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 部分:
# 原:使用 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
// 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:
# 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):
# 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 预加载表
# 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。
# 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:
var theme := load("res://assets/ui/main_theme.tres") # 你制作的 Theme 资源
btn.theme = theme
选项 B:迁移到独立 .tscn 场景(推荐,长期)
- 为商店、背包、结算屏分别建
.tscn - 在
_setup_shop_ui()等函数中替换为load("res://scenes/ui/shop.tscn").instantiate() - 通过信号 / 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 |