249 lines
6.3 KiB
GDScript
249 lines
6.3 KiB
GDScript
class_name SprintDustTrail
|
|
extends MultiMeshInstance3D
|
|
|
|
const PUFF_COUNT: int = 12
|
|
const PUFF_SPACING: float = 0.46
|
|
const PUFF_LIFETIME: float = 0.62
|
|
const PUFF_GROW_TIME: float = 0.16
|
|
const PUFF_FADE_TIME: float = 0.22
|
|
const PUFF_BACK_OFFSET: float = 0.14
|
|
const PUFF_SIDE_OFFSET: float = 0.11
|
|
const PUFF_HEIGHT: float = 0.08
|
|
const PUFF_MIN_SCALE: float = 0.22
|
|
|
|
@export var puff_mesh: Mesh
|
|
|
|
var _puff_positions: Array[Vector3] = []
|
|
var _puff_ages := PackedFloat32Array()
|
|
var _puff_widths := PackedFloat32Array()
|
|
var _puff_heights := PackedFloat32Array()
|
|
var _puff_phases := PackedFloat32Array()
|
|
var _next_puff: int = 0
|
|
var _spawn_serial: int = 0
|
|
var _foot_side: float = -1.0
|
|
var _tracking_run: bool = false
|
|
var _previous_source_position: Vector3
|
|
var _distance_since_puff: float = 0.0
|
|
|
|
|
|
func _ready() -> void:
|
|
top_level = true
|
|
global_transform = Transform3D.IDENTITY
|
|
_initialize_pool()
|
|
|
|
|
|
func update_trail(
|
|
delta: float,
|
|
source_position: Vector3,
|
|
movement_velocity: Vector3,
|
|
is_running: bool,
|
|
) -> void:
|
|
_update_existing_puffs(delta)
|
|
custom_aabb = AABB(
|
|
source_position - Vector3(5.0, 2.0, 5.0),
|
|
Vector3(10.0, 5.0, 10.0),
|
|
)
|
|
if not is_running:
|
|
_tracking_run = false
|
|
_distance_since_puff = 0.0
|
|
return
|
|
|
|
var flat_source := Vector3(
|
|
source_position.x,
|
|
source_position.y,
|
|
source_position.z,
|
|
)
|
|
if not _tracking_run:
|
|
_tracking_run = true
|
|
_previous_source_position = flat_source
|
|
_distance_since_puff = 0.0
|
|
return
|
|
|
|
var segment := flat_source - _previous_source_position
|
|
segment.y = 0.0
|
|
var remaining_distance: float = segment.length()
|
|
if remaining_distance <= 0.0001:
|
|
_previous_source_position = flat_source
|
|
return
|
|
|
|
var segment_direction: Vector3 = segment / remaining_distance
|
|
var movement_direction := Vector3(
|
|
movement_velocity.x,
|
|
0.0,
|
|
movement_velocity.z,
|
|
).normalized()
|
|
if movement_direction.is_zero_approx():
|
|
movement_direction = segment_direction
|
|
var cursor: Vector3 = _previous_source_position
|
|
var distance_to_next: float = PUFF_SPACING - _distance_since_puff
|
|
while remaining_distance >= distance_to_next:
|
|
cursor += segment_direction * distance_to_next
|
|
_spawn_puff(cursor, movement_direction)
|
|
remaining_distance -= distance_to_next
|
|
_distance_since_puff = 0.0
|
|
distance_to_next = PUFF_SPACING
|
|
_distance_since_puff += remaining_distance
|
|
_previous_source_position = flat_source
|
|
|
|
|
|
func clear_trail() -> void:
|
|
_tracking_run = false
|
|
_distance_since_puff = 0.0
|
|
for index: int in PUFF_COUNT:
|
|
_puff_ages[index] = PUFF_LIFETIME
|
|
_write_inactive_puff(index)
|
|
|
|
|
|
func emit_landing_burst(
|
|
source_position: Vector3,
|
|
facing_direction: Vector3,
|
|
) -> void:
|
|
var direction := Vector3(
|
|
facing_direction.x,
|
|
0.0,
|
|
facing_direction.z,
|
|
).normalized()
|
|
if direction.is_zero_approx():
|
|
direction = Vector3.FORWARD
|
|
var first_direction: Vector3 = -direction
|
|
for puff_index: int in 5:
|
|
var spread_direction := first_direction.rotated(
|
|
Vector3.UP,
|
|
TAU * float(puff_index) / 5.0,
|
|
)
|
|
var radius: float = lerpf(
|
|
0.38,
|
|
0.46,
|
|
_stable_variation(_spawn_serial + puff_index + 81),
|
|
)
|
|
_spawn_puff_at(
|
|
source_position
|
|
+ spread_direction * radius
|
|
+ Vector3.UP * PUFF_HEIGHT,
|
|
0.95,
|
|
)
|
|
|
|
|
|
func get_active_puff_count() -> int:
|
|
var active_count: int = 0
|
|
for age: float in _puff_ages:
|
|
if age < PUFF_LIFETIME:
|
|
active_count += 1
|
|
return active_count
|
|
|
|
|
|
func get_first_active_puff_position() -> Vector3:
|
|
for index: int in PUFF_COUNT:
|
|
if _puff_ages[index] < PUFF_LIFETIME:
|
|
return _puff_positions[index]
|
|
return Vector3.INF
|
|
|
|
|
|
func _initialize_pool() -> void:
|
|
var pool := MultiMesh.new()
|
|
pool.transform_format = MultiMesh.TRANSFORM_3D
|
|
pool.use_custom_data = true
|
|
pool.instance_count = PUFF_COUNT
|
|
pool.mesh = puff_mesh
|
|
multimesh = pool
|
|
_puff_positions.resize(PUFF_COUNT)
|
|
_puff_ages.resize(PUFF_COUNT)
|
|
_puff_widths.resize(PUFF_COUNT)
|
|
_puff_heights.resize(PUFF_COUNT)
|
|
_puff_phases.resize(PUFF_COUNT)
|
|
clear_trail()
|
|
|
|
|
|
func _spawn_puff(source_position: Vector3, direction: Vector3) -> void:
|
|
var lateral := Vector3(-direction.z, 0.0, direction.x)
|
|
var position := (
|
|
source_position
|
|
- direction * PUFF_BACK_OFFSET
|
|
+ lateral * PUFF_SIDE_OFFSET * _foot_side
|
|
+ Vector3.UP * PUFF_HEIGHT
|
|
)
|
|
_spawn_puff_at(position)
|
|
_foot_side *= -1.0
|
|
|
|
|
|
func _spawn_puff_at(
|
|
position: Vector3,
|
|
size_multiplier: float = 1.0,
|
|
) -> void:
|
|
var variation: float = _stable_variation(_spawn_serial)
|
|
var second_variation: float = _stable_variation(_spawn_serial + 37)
|
|
var index: int = _next_puff
|
|
_puff_positions[index] = position
|
|
_puff_ages[index] = 0.0
|
|
_puff_widths[index] = (
|
|
lerpf(0.34, 0.43, variation) * size_multiplier
|
|
)
|
|
_puff_heights[index] = (
|
|
lerpf(0.23, 0.30, second_variation) * size_multiplier
|
|
)
|
|
_puff_phases[index] = variation
|
|
_write_puff(index, PUFF_MIN_SCALE, 1.0)
|
|
_next_puff = (_next_puff + 1) % PUFF_COUNT
|
|
_spawn_serial += 1
|
|
|
|
|
|
func _update_existing_puffs(delta: float) -> void:
|
|
if multimesh == null:
|
|
return
|
|
for index: int in PUFF_COUNT:
|
|
var age: float = _puff_ages[index]
|
|
if age >= PUFF_LIFETIME:
|
|
continue
|
|
age += delta
|
|
_puff_ages[index] = age
|
|
if age >= PUFF_LIFETIME:
|
|
_write_inactive_puff(index)
|
|
continue
|
|
var visual_scale: float = 1.0
|
|
if age < PUFF_GROW_TIME:
|
|
var grow_weight: float = smoothstep(
|
|
0.0,
|
|
1.0,
|
|
age / PUFF_GROW_TIME,
|
|
)
|
|
visual_scale = lerpf(PUFF_MIN_SCALE, 1.0, grow_weight)
|
|
var visual_alpha: float = 1.0
|
|
if age > PUFF_LIFETIME - PUFF_FADE_TIME:
|
|
var fade_weight: float = smoothstep(
|
|
0.0,
|
|
1.0,
|
|
(age - (PUFF_LIFETIME - PUFF_FADE_TIME))
|
|
/ PUFF_FADE_TIME,
|
|
)
|
|
visual_alpha = 1.0 - fade_weight
|
|
_write_puff(index, visual_scale, visual_alpha)
|
|
|
|
|
|
func _write_puff(
|
|
index: int,
|
|
visual_scale: float,
|
|
visual_alpha: float,
|
|
) -> void:
|
|
var puff_transform := Transform3D.IDENTITY
|
|
puff_transform.origin = _puff_positions[index]
|
|
multimesh.set_instance_transform(index, puff_transform)
|
|
multimesh.set_instance_custom_data(
|
|
index,
|
|
Color(
|
|
_puff_phases[index],
|
|
_puff_widths[index] * visual_scale,
|
|
_puff_heights[index] * visual_scale,
|
|
visual_alpha,
|
|
),
|
|
)
|
|
|
|
|
|
func _write_inactive_puff(index: int) -> void:
|
|
var inactive_transform := Transform3D.IDENTITY
|
|
inactive_transform.origin = _puff_positions[index]
|
|
multimesh.set_instance_transform(index, inactive_transform)
|
|
multimesh.set_instance_custom_data(index, Color(0.0, 0.0, 0.0, 0.0))
|
|
|
|
|
|
func _stable_variation(serial: int) -> float:
|
|
return fposmod(sin(float(serial + 1) * 12.9898) * 43758.5453, 1.0)
|