@tool extends Node3D class_name Explosion @export_range(0.1,5,0.01,"or_greater") var falloff_exponent: float = 1.2 @export_range(10,100000,10,"or_greater") var impulse: int = 15000 @export_range(0.1,100,0.1,"or_greater") var character_velocity: float = 13 @export_range(0.1,10,0.1,"or_greater") var radius: float = 5: set(newVal): if is_instance_valid(blast_visual): blast_visual.radius = newVal radius = newVal get: return radius @export_flags_3d_physics var collision_mask: int = 15 @onready var shape_rid: RID @onready var shape_params: PhysicsShapeQueryParameters3D @onready var ray_params: PhysicsRayQueryParameters3D @onready var blast_visual: CSGSphere3D = null @export var rumble_amount: float = 10 func _ready() -> void: if Engine.is_editor_hint(): set_physics_process(false) blast_visual = CSGSphere3D.new() blast_visual.material = load("res://assets/materials/mat_blast_debug.tres") blast_visual.radius = radius blast_visual.radial_segments = 32 blast_visual.rings = 16 add_child(blast_visual) if not Engine.is_editor_hint(): set_physics_process(true) # Initialize sphere shape shape_rid = PhysicsServer3D.sphere_shape_create() PhysicsServer3D.shape_set_data(shape_rid, radius) # Initialize shape query params shape_params = PhysicsShapeQueryParameters3D.new() shape_params.shape_rid = shape_rid shape_params.collision_mask = collision_mask # Initialize ray params ray_params = PhysicsRayQueryParameters3D.new() ray_params.collision_mask = collision_mask ray_params.collide_with_areas = false ray_params.collide_with_bodies = true ray_params.hit_from_inside = true ray_params.hit_back_faces = true for child in get_children(): if child is GPUParticles3D: child.one_shot = true child.emitting = true var t = $light.create_tween() t.tween_property($light,'light_energy',0,0.6) CameraRumble.rumble += rumble_amount func _exit_tree() -> void: if not Engine.is_editor_hint(): PhysicsServer3D.free_rid(shape_rid) func _physics_process(_delta: float) -> void: set_physics_process(false) shape_params.transform = global_transform ray_params.from = shape_params.transform.origin var space = get_world_3d().direct_space_state var results = space.intersect_shape(shape_params, 32) var hit: Array[RigidBody3D] = [] var excludes: Array[RID] = [] for res in results: var c = res.get("collider", null) if is_instance_valid(c) and c is RigidBody3D: hit.append(c) excludes.append(res.get("rid")) var ray_excludes = excludes.duplicate() for i in range(0,hit.size()): var p: RigidBody3D = hit[i] var rid: RID = excludes[i] #print("At index ", i, " looking at hit ", p.to_string(), " with RID ", rid.get_id()) var pos: Vector3 = p.global_position var dir: Vector3 = (pos - ray_params.from).normalized() ray_params.to = pos #ray_params.from + (dir * radius) # Remove index from exclude-list ray_excludes.erase(rid) ray_params.exclude = ray_excludes #print("\t", ray_excludes.has(rid)) var rayRes = space.intersect_ray(ray_params) ray_excludes.append(rid) # Re-add index to exclude list #print("\t", rayRes) if rayRes.has("rid") and rayRes.get("rid").get_id() == rid.get_id(): # If we hit the same object #call_deferred("place_dot", p, rayRes.get("position")) var hitPos: Vector3 = rayRes.get("position") var factor: float = falloff(ray_params.from.distance_to(rayRes.get("position"))) var bodyState: PhysicsDirectBodyState3D = PhysicsServer3D.body_get_direct_state(rid) if is_instance_valid(bodyState): bodyState.sleeping = false # Force objects to wake up bodyState.apply_impulse(dir * impulse, hitPos - pos) func falloff(dist: float) -> float: return max((1.0 - pow(dist / radius, falloff_exponent)), 0) func place_dot(parent: Node3D, location: Vector3) -> void: var dot = CSGSphere3D.new() dot.radius = 0.1 parent.add_child(dot) dot.global_position = location