初次提交

This commit is contained in:
2026-07-20 10:56:52 +08:00
commit 7bcc0026e0
462 changed files with 50191 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
## 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