278 lines
6.9 KiB
GDScript
278 lines
6.9 KiB
GDScript
@tool
|
|
class_name WeatherKillVolume
|
|
extends Area3D
|
|
|
|
enum VolumeShape {
|
|
BOX,
|
|
SPHERE,
|
|
CYLINDER
|
|
}
|
|
|
|
@export_group("Volume Settings")
|
|
@export var volume_shape: VolumeShape = VolumeShape.BOX:
|
|
set(value):
|
|
volume_shape = value
|
|
_update_collision_shape()
|
|
|
|
@export var volume_size: Vector3 = Vector3(10.0, 5.0, 10.0):
|
|
set(value):
|
|
volume_size = value
|
|
_update_collision_shape()
|
|
|
|
@export var volume_radius: float = 5.0:
|
|
set(value):
|
|
volume_radius = value
|
|
_update_collision_shape()
|
|
|
|
@export_group("Kill Behavior")
|
|
@export var enabled: bool = true
|
|
@export var kill_priority: int = 0
|
|
@export var instant_kill: bool = true
|
|
@export var fade_out_margin: float = 1.0
|
|
@export var fade_out_duration: float = 0.3
|
|
|
|
@export_group("Particle Types")
|
|
@export var block_rain: bool = true
|
|
@export var block_snow: bool = true
|
|
@export var block_all_precipitation: bool = true
|
|
|
|
@export_group("Debug")
|
|
@export var show_debug_gizmo: bool = true
|
|
@export var debug_color: Color = Color(0.2, 0.5, 1.0, 0.3)
|
|
|
|
var _collision_shape: CollisionShape3D
|
|
var _particles_inside: Dictionary = {}
|
|
|
|
|
|
func _ready() -> void:
|
|
_setup_collision()
|
|
|
|
body_entered.connect(_on_body_entered)
|
|
body_exited.connect(_on_body_exited)
|
|
area_entered.connect(_on_area_entered)
|
|
area_exited.connect(_on_area_exited)
|
|
|
|
|
|
func _setup_collision() -> void:
|
|
_collision_shape = CollisionShape3D.new()
|
|
_collision_shape.name = "KillVolumeShape"
|
|
add_child(_collision_shape)
|
|
_update_collision_shape()
|
|
|
|
collision_layer = 0
|
|
collision_mask = 1 << 20
|
|
monitorable = true
|
|
monitoring = true
|
|
|
|
|
|
func _update_collision_shape() -> void:
|
|
if _collision_shape == null:
|
|
return
|
|
|
|
var shape: Shape3D
|
|
|
|
match volume_shape:
|
|
VolumeShape.BOX:
|
|
var box := BoxShape3D.new()
|
|
box.size = volume_size
|
|
shape = box
|
|
|
|
VolumeShape.SPHERE:
|
|
var sphere := SphereShape3D.new()
|
|
sphere.radius = volume_radius
|
|
shape = sphere
|
|
|
|
VolumeShape.CYLINDER:
|
|
var cylinder := CylinderShape3D.new()
|
|
cylinder.radius = volume_radius
|
|
cylinder.height = volume_size.y
|
|
shape = cylinder
|
|
|
|
_collision_shape.shape = shape
|
|
|
|
|
|
func _on_body_entered(body: Node3D) -> void:
|
|
if not enabled:
|
|
return
|
|
|
|
if _is_precipitation_particle(body):
|
|
_handle_particle_enter(body)
|
|
|
|
|
|
func _on_body_exited(body: Node3D) -> void:
|
|
_handle_particle_exit(body)
|
|
|
|
|
|
func _on_area_entered(area: Area3D) -> void:
|
|
if not enabled:
|
|
return
|
|
|
|
if area.has_meta("precipitation_particle"):
|
|
_handle_particle_enter(area)
|
|
|
|
|
|
func _on_area_exited(area: Area3D) -> void:
|
|
_handle_particle_exit(area)
|
|
|
|
|
|
func _is_precipitation_particle(node: Node3D) -> bool:
|
|
if node.has_meta("particle_type"):
|
|
var particle_type: String = node.get_meta("particle_type")
|
|
|
|
if block_all_precipitation:
|
|
return particle_type in ["rain", "snow", "hail", "sleet"]
|
|
|
|
if block_rain and particle_type == "rain":
|
|
return true
|
|
|
|
if block_snow and particle_type == "snow":
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
func _handle_particle_enter(node: Node3D) -> void:
|
|
if instant_kill:
|
|
_kill_particle(node)
|
|
else:
|
|
_particles_inside[node.get_instance_id()] = {
|
|
"node": node,
|
|
"fade_time": 0.0
|
|
}
|
|
|
|
|
|
func _handle_particle_exit(node: Node3D) -> void:
|
|
var id := node.get_instance_id()
|
|
if _particles_inside.has(id):
|
|
_particles_inside.erase(id)
|
|
|
|
|
|
func _kill_particle(node: Node3D) -> void:
|
|
if node.has_method("kill"):
|
|
node.call("kill")
|
|
elif node is GPUParticles3D:
|
|
node.emitting = false
|
|
elif node is CPUParticles3D:
|
|
node.emitting = false
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if instant_kill or _particles_inside.is_empty():
|
|
return
|
|
|
|
var to_remove: Array[int] = []
|
|
|
|
for id in _particles_inside:
|
|
var data: Dictionary = _particles_inside[id]
|
|
var node: Node3D = data["node"]
|
|
|
|
if not is_instance_valid(node):
|
|
to_remove.append(id)
|
|
continue
|
|
|
|
data["fade_time"] += delta
|
|
var fade_progress: float = data["fade_time"] / fade_out_duration
|
|
|
|
if fade_progress >= 1.0:
|
|
_kill_particle(node)
|
|
to_remove.append(id)
|
|
else:
|
|
_apply_fade(node, 1.0 - fade_progress)
|
|
|
|
for id in to_remove:
|
|
_particles_inside.erase(id)
|
|
|
|
|
|
func _apply_fade(node: Node3D, alpha: float) -> void:
|
|
if node.has_method("set_alpha"):
|
|
node.call("set_alpha", alpha)
|
|
elif node is GPUParticles3D or node is CPUParticles3D:
|
|
pass
|
|
|
|
|
|
func is_point_inside(point: Vector3) -> bool:
|
|
var local_point := to_local(point)
|
|
|
|
match volume_shape:
|
|
VolumeShape.BOX:
|
|
var half_size := volume_size * 0.5
|
|
return abs(local_point.x) <= half_size.x and \
|
|
abs(local_point.y) <= half_size.y and \
|
|
abs(local_point.z) <= half_size.z
|
|
|
|
VolumeShape.SPHERE:
|
|
return local_point.length() <= volume_radius
|
|
|
|
VolumeShape.CYLINDER:
|
|
var horizontal_dist := Vector2(local_point.x, local_point.z).length()
|
|
var half_height := volume_size.y * 0.5
|
|
return horizontal_dist <= volume_radius and abs(local_point.y) <= half_height
|
|
|
|
return false
|
|
|
|
|
|
func is_point_in_fade_margin(point: Vector3) -> bool:
|
|
if fade_out_margin <= 0:
|
|
return false
|
|
|
|
var local_point := to_local(point)
|
|
|
|
match volume_shape:
|
|
VolumeShape.BOX:
|
|
var half_size := volume_size * 0.5
|
|
var inner_half := half_size - Vector3(fade_out_margin, fade_out_margin, fade_out_margin)
|
|
|
|
var in_outer: bool = abs(local_point.x) <= half_size.x and \
|
|
abs(local_point.y) <= half_size.y and \
|
|
abs(local_point.z) <= half_size.z
|
|
|
|
var in_inner: bool = abs(local_point.x) <= inner_half.x and \
|
|
abs(local_point.y) <= inner_half.y and \
|
|
abs(local_point.z) <= inner_half.z
|
|
|
|
return in_outer and not in_inner
|
|
|
|
VolumeShape.SPHERE:
|
|
var dist := local_point.length()
|
|
return dist <= volume_radius and dist >= volume_radius - fade_out_margin
|
|
|
|
VolumeShape.CYLINDER:
|
|
var horizontal_dist := Vector2(local_point.x, local_point.z).length()
|
|
var half_height := volume_size.y * 0.5
|
|
|
|
var in_outer: bool = horizontal_dist <= volume_radius and abs(local_point.y) <= half_height
|
|
var in_inner: bool = horizontal_dist <= volume_radius - fade_out_margin and \
|
|
abs(local_point.y) <= half_height - fade_out_margin
|
|
|
|
return in_outer and not in_inner
|
|
|
|
return false
|
|
|
|
|
|
func get_fade_factor(point: Vector3) -> float:
|
|
if not is_point_inside(point):
|
|
return 1.0
|
|
|
|
if not is_point_in_fade_margin(point):
|
|
return 0.0
|
|
|
|
var local_point := to_local(point)
|
|
var min_distance_to_edge := fade_out_margin
|
|
|
|
match volume_shape:
|
|
VolumeShape.BOX:
|
|
var half_size := volume_size * 0.5
|
|
min_distance_to_edge = minf(min_distance_to_edge, half_size.x - abs(local_point.x))
|
|
min_distance_to_edge = minf(min_distance_to_edge, half_size.y - abs(local_point.y))
|
|
min_distance_to_edge = minf(min_distance_to_edge, half_size.z - abs(local_point.z))
|
|
|
|
VolumeShape.SPHERE:
|
|
min_distance_to_edge = volume_radius - local_point.length()
|
|
|
|
VolumeShape.CYLINDER:
|
|
var horizontal_dist := Vector2(local_point.x, local_point.z).length()
|
|
var half_height := volume_size.y * 0.5
|
|
min_distance_to_edge = minf(volume_radius - horizontal_dist, half_height - abs(local_point.y))
|
|
|
|
return clampf(min_distance_to_edge / fade_out_margin, 0.0, 1.0)
|