Files
spellforge/scripts/autoloads/vfx_manager.gd
T
2026-07-20 10:56:52 +08:00

126 lines
3.9 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## VFXManager — 视觉特效管理(Autoload: VFXManager
## S4:程序化色块占位特效 + 对象池(P6-N72 one_shot=true
extends Node2D
const MAX_ACTIVE_VFX: int = 200
var _vfx_pool: Dictionary = {} # { effect_id: Array[Node2D] }
var _active_count: int = 0
func _ready() -> void:
EventBus.subscribe(EventID.STATUS_APPLIED, _on_status_applied)
func play(effect_id: String, world_pos: Vector2, override_scale: float = 1.0) -> void:
if _active_count >= MAX_ACTIVE_VFX:
return
var node: Node2D = _pool_pop(effect_id)
node.global_position = world_pos
node.scale = Vector2.ONE * override_scale
node.visible = true
_active_count += 1
# 触发一次性粒子爆发
var p: GPUParticles2D = node.get_node_or_null("Particles")
if p:
p.restart()
p.emitting = true
var timer: Timer = node.get_node_or_null("AutoReturn")
if timer:
timer.start()
func _pool_pop(effect_id: String) -> Node2D:
if _vfx_pool.has(effect_id) and not _vfx_pool[effect_id].is_empty():
return _vfx_pool[effect_id].pop_back()
return _instantiate_vfx(effect_id)
func _pool_return(effect_id: String, node: Node2D) -> void:
node.visible = false
_active_count = max(0, _active_count - 1)
if not _vfx_pool.has(effect_id):
_vfx_pool[effect_id] = []
_vfx_pool[effect_id].append(node)
func _instantiate_vfx(effect_id: String) -> Node2D:
# 程序化美术占位:GPUParticles2D 爆裂粒子(放 scenes/vfx/<id>.tscn 可整体替换)
var node := Node2D.new()
node.name = "VFX_" + effect_id
var col: Color = _color_for(effect_id)
var p := GPUParticles2D.new()
p.name = "Particles"
p.amount = _amount_for(effect_id)
p.lifetime = 0.45
p.one_shot = true # P6-N72
p.explosiveness = 0.9 # 一次性爆发
p.local_coords = false
var mat := ParticleProcessMaterial.new()
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
mat.emission_sphere_radius = 4.0
mat.direction = Vector3(0, 0, 0)
mat.spread = 180.0
mat.gravity = Vector3(0, 0, 0)
mat.initial_velocity_min = _speed_for(effect_id) * 0.5
mat.initial_velocity_max = _speed_for(effect_id)
mat.scale_min = 1.5
mat.scale_max = 4.0
# 颜色随生命淡出
var grad := Gradient.new()
grad.set_color(0, Color(col.r, col.g, col.b, col.a))
grad.set_color(1, Color(col.r, col.g, col.b, 0.0))
var gtex := GradientTexture1D.new()
gtex.gradient = grad
mat.color_ramp = gtex
p.process_material = mat
node.add_child(p)
# 自动回收(粒子播完)
var timer := Timer.new()
timer.name = "AutoReturn"
timer.one_shot = true
timer.wait_time = 0.55
timer.timeout.connect(_pool_return.bind(effect_id, node))
node.add_child(timer)
add_child(node)
node.visible = false
return node
## 不同特效的粒子数 / 速度
func _amount_for(effect_id: String) -> int:
match effect_id:
"death_burst": return 24
"hit_spark": return 10
"cast_flash": return 8
_: return 12
func _speed_for(effect_id: String) -> float:
match effect_id:
"death_burst": return 220.0
"hit_spark": return 160.0
_: return 110.0
func _color_for(effect_id: String) -> Color:
match effect_id:
"hit_spark": return Color(1.0, 0.9, 0.3, 0.9)
"status_burn": return Color(1.0, 0.4, 0.1, 0.8)
"status_poison": return Color(0.3, 0.9, 0.2, 0.8)
"cast_flash": return Color(0.6, 0.8, 1.0, 0.7)
"death_burst": return Color(1.0, 0.2, 0.2, 0.8)
_: return Color(1, 1, 1, 0.6)
func _on_status_applied(payload: Dictionary) -> void:
var status_type: int = int(payload.get("status_type", 0))
var typedef: StatusTypeDef = StatusRegistry.get_type(status_type)
if typedef and typedef.vfx_id != "":
var eid: int = int(payload.get("target_id", -1))
var pos: Vector2 = EnemyManager.get_pos_by_id(eid)
if pos != Vector2(-9999.0, -9999.0):
play(typedef.vfx_id, pos)
func get_active_count() -> int:
return _active_count
func reset() -> void:
for key in _vfx_pool:
for n in _vfx_pool[key]:
if is_instance_valid(n):
n.queue_free()
_vfx_pool.clear()
_active_count = 0