extends Node class_name ImpactSpawner static var bullet_hole_mat: StandardMaterial3D = null static var crater_mat: StandardMaterial3D = null static var plasma_mat: StandardMaterial3D = null static var slash_mat: StandardMaterial3D = null static func _ensure_mats(): if bullet_hole_mat != null: return var make_tex = func(colors: Array, offsets: Array, size: int) -> Texture2D: var g = Gradient.new() # For Godot 4 Gradient, we set offsets and colors directly var p_offsets = PackedFloat32Array() var p_colors = PackedColorArray() for o in offsets: p_offsets.append(o) for c in colors: p_colors.append(c) g.offsets = p_offsets g.colors = p_colors # Hard color steps: reads as inked cel splats instead of soft airbrush g.interpolation_mode = Gradient.GRADIENT_INTERPOLATE_CONSTANT var t = GradientTexture2D.new() t.gradient = g t.fill = GradientTexture2D.FILL_RADIAL t.fill_from = Vector2(0.5, 0.5) t.fill_to = Vector2(0.5, 0.0) t.width = size t.height = size return t var tex_b = make_tex.call( [Color(0.01, 0.01, 0.01, 1.0), Color(0.1, 0.1, 0.1, 0.8), Color(0.1, 0.1, 0.1, 0.0)], [0.0, 0.3, 1.0], 32) bullet_hole_mat = StandardMaterial3D.new() bullet_hole_mat.albedo_texture = tex_b bullet_hole_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA var tex_c = make_tex.call( [Color(0.05, 0.05, 0.05, 0.9), Color(0.1, 0.1, 0.1, 0.6), Color(0.1, 0.1, 0.1, 0.0)], [0.0, 0.5, 1.0], 128) crater_mat = StandardMaterial3D.new() crater_mat.albedo_texture = tex_c crater_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA var tex_p = make_tex.call( [Color(0.8, 0.9, 1.0, 1.0), Color(0.2, 0.6, 1.0, 0.8), Color(0.1, 0.3, 0.8, 0.0)], [0.0, 0.2, 1.0], 64) plasma_mat = StandardMaterial3D.new() plasma_mat.albedo_texture = tex_p plasma_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA plasma_mat.emission_enabled = true plasma_mat.emission_texture = tex_p plasma_mat.emission_energy_multiplier = 2.0 var tex_s = make_tex.call( [Color(0.9, 0.9, 0.9, 1.0), Color(0.8, 0.8, 0.8, 0.8), Color(0.8, 0.8, 0.8, 0.0)], [0.0, 0.5, 1.0], 64) slash_mat = StandardMaterial3D.new() slash_mat.albedo_texture = tex_s slash_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA static func spawn(scene_tree: SceneTree, type: String, pos: Vector3, normal: Vector3, size: float = 0.2): _ensure_mats() # Positional impact tick (pooled; safe at fire-rate frequency) if type == "bullet": var am = scene_tree.root.get_node_or_null("AudioManager") if am and am.has_sound("bullet_impact"): am.play_3d("bullet_impact", pos, -6.0) var mi = MeshInstance3D.new() var qm = QuadMesh.new() if type == "slash": qm.size = Vector2(size * 4.0, size * 0.4) else: qm.size = Vector2(size, size) var mat: StandardMaterial3D if type == "bullet": mat = bullet_hole_mat.duplicate() elif type == "crater": mat = crater_mat.duplicate() elif type == "plasma": mat = plasma_mat.duplicate() elif type == "slash": mat = slash_mat.duplicate() qm.material = mat mi.mesh = qm scene_tree.current_scene.add_child(mi) mi.global_position = pos + normal * 0.01 if abs(normal.dot(Vector3.UP)) < 0.999: mi.look_at(mi.global_position - normal, Vector3.UP) else: mi.look_at(mi.global_position - normal, Vector3.RIGHT) var duration = 5.0 if type == "crater" else 3.0 var tween = mi.create_tween() tween.tween_interval(duration) tween.tween_property(mat, "albedo_color:a", 0.0, 1.0) tween.tween_callback(mi.queue_free)