Files
spellforge/docs/handbook/03_asset_replacement.md
T
2026-07-20 10:56:52 +08:00

8.2 KiB
Raw Blame History

03 资源替换指南 (Asset Replacement Guide)

当前项目使用程序化占位(几何体 + 蜂鸣音),所有接口已就绪。本文说明如何无缝替换为正式美术资源,不需要修改任何业务逻辑代码


A. 音效替换

当前状态

AudioManagerres://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)即可,立体声也支持
时长 音效 < 3sBGM 循环
响度 建议 -14 LUFS 标准化后导入

3. Godot 导入设置(在编辑器 FileSystem 面板右键文件 → Import

设置 推荐值
Loop 音效:OffBGMOn
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],   # 新增:频率 600Hz80ms
}

然后在需要播放的地方调用:

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 datadamage_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 = trueemitting = 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 场景(推荐,长期)

  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