extends RefCounted const MAX_STAINS := 160 static func impact(world: Node3D, point: Vector3, direction: Vector3) -> void: # CPU particles work in both Forward+ and WebGL Compatibility. var particles := CPUParticles3D.new() particles.name = "BloodSpray" particles.amount = 28 particles.lifetime = 0.65 particles.one_shot = true particles.explosiveness = 1.0 particles.direction = direction.normalized() particles.spread = 65 particles.initial_velocity_min = 1.3 particles.initial_velocity_max = 3.5 particles.gravity = Vector3(0, -9.8, 0) particles.scale_amount_min = 0.5 particles.scale_amount_max = 1.5 var mesh := SphereMesh.new() mesh.radius = 0.018 mesh.height = 0.036 var material := StandardMaterial3D.new() material.albedo_color = Color(0.45, 0.015, 0.025) material.roughness = 0.35 mesh.material = material particles.mesh = mesh world.add_child(particles) particles.global_position = point particles.emitting = true world.get_tree().create_timer(1.3).timeout.connect(particles.queue_free) var query := PhysicsRayQueryParameters3D.create(point + Vector3.UP * 0.05, point + Vector3.DOWN * 8, 1) var hit := world.get_world_3d().direct_space_state.intersect_ray(query) if hit.is_empty() or hit.normal.dot(Vector3.UP) < 0.7: return var width := randf_range(0.55, 1.15) var stain: Node3D if RenderingServer.get_current_rendering_method() == "gl_compatibility": # WebGL has no projected decals: textured horizontal patch on floor. var patch := MeshInstance3D.new() var plane := PlaneMesh.new() plane.size = Vector2(width, width) var mat := StandardMaterial3D.new() mat.albedo_texture = load("res://assets/effects/blood.png") mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED mat.roughness = 0.9 plane.material = mat patch.mesh = plane patch.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF stain = patch else: var decal := Decal.new() decal.texture_albedo = load("res://assets/effects/blood.png") decal.size = Vector3(width, 0.25, width) decal.cull_mask = 1 decal.upper_fade = 0 decal.lower_fade = 0 stain = decal stain.name = "BloodStain" world.add_child(stain) stain.global_position = hit.position + Vector3.UP * 0.012 stain.rotation.y = randf_range(0, TAU) stain.add_to_group("blood_stains") var stains := world.get_tree().get_nodes_in_group("blood_stains") if stains.size() > MAX_STAINS: stains[0].queue_free()