123 lines
3 KiB
GDScript
123 lines
3 KiB
GDScript
class_name PoliceCar
|
|
extends CharacterBody3D
|
|
|
|
const FRAME_IDXS := 10
|
|
|
|
@export var dot_curve: Curve
|
|
@export var max_speed := 8.
|
|
@export var health := 10.
|
|
@export var knockback_mul := 1.
|
|
@export var invulnerable := false
|
|
|
|
@export var shatter_scene: PackedScene
|
|
@export var mesh: Node3D
|
|
|
|
var frame_idx := 0
|
|
var frame_count := 0
|
|
|
|
var knockback := Vector3.ZERO
|
|
|
|
var default_movement := true
|
|
var stunned := false
|
|
|
|
func set_rotation_to_velocity() -> void:
|
|
rotation.y = Vector2(velocity.x, -velocity.z).angle()
|
|
|
|
func exp_lerp(a: Variant, b: Variant, decay: float, dt: float) -> Variant:
|
|
return lerp(a, b, 1 - exp(-decay * dt))
|
|
|
|
func _ready() -> void:
|
|
frame_idx = randi() % FRAME_IDXS
|
|
|
|
func _update_nav_agent() -> void:
|
|
%NavAgent.target_position = Player.instance.global_position
|
|
|
|
func _process_movement(delta: float) -> void:
|
|
var dir := global_position.direction_to(%NavAgent.get_next_path_position())
|
|
dir.y = 0
|
|
|
|
var dot := dir.dot(velocity.normalized())
|
|
var dot_power := dot_curve.sample(dot)
|
|
velocity += dir * 8. * delta * dot_power
|
|
velocity = velocity.limit_length(max_speed)
|
|
|
|
func _process(delta: float) -> void:
|
|
if not default_movement: return
|
|
if stunned: return
|
|
|
|
frame_count = (frame_count + 1) % FRAME_IDXS
|
|
if frame_count == frame_idx:
|
|
_update_nav_agent()
|
|
|
|
if knockback.length() < 0.1:
|
|
_process_movement(delta)
|
|
set_rotation_to_velocity()
|
|
position.y = 0.01
|
|
else:
|
|
velocity = knockback
|
|
knockback = exp_lerp(knockback, Vector3.ZERO, 15., delta)
|
|
|
|
move_and_slide()
|
|
|
|
func kill(damager_pos = Vector3.ZERO) -> void:
|
|
queue_free()
|
|
|
|
var siren: AudioStreamPlayer3D = %Siren
|
|
siren.reparent(get_tree().current_scene)
|
|
var t := siren.create_tween()
|
|
|
|
if randf() < .25:
|
|
t.tween_property(siren, "pitch_scale", .25, .5)
|
|
t.tween_property(siren, "pitch_scale", .35, .25)
|
|
t.tween_property(siren, "pitch_scale", .0, .25)
|
|
else:
|
|
t.tween_property(siren, "pitch_scale", .0, .5)
|
|
|
|
t.finished.connect(siren.queue_free)
|
|
|
|
var meshes: Array[MeshInstance3D]
|
|
if shatter_scene:
|
|
var shatter: Node3D = shatter_scene.instantiate()
|
|
get_tree().current_scene.add_child(shatter)
|
|
shatter.global_transform = mesh.global_transform
|
|
var rbs := shatter.get_child(1).get_children()
|
|
|
|
for rb: RigidBody3D in rbs:
|
|
var dir = damager_pos.direction_to(rb.global_position)
|
|
rb.apply_impulse(dir * 17)
|
|
rb.collision_layer = 0
|
|
meshes.push_back(rb.get_child(1))
|
|
|
|
|
|
var t2 := shatter.create_tween()
|
|
var first := true
|
|
t2.tween_interval(1)
|
|
|
|
for m in meshes:
|
|
var t3 = t2
|
|
if not first: t3 = t2.parallel()
|
|
t3.tween_property(m, "transparency", 1., 0.5)
|
|
first = false
|
|
|
|
t2.finished.connect(shatter.queue_free)
|
|
|
|
|
|
SignalBus.enemy_destroyed.emit()
|
|
|
|
|
|
func hit(proj: Node3D, damage: float) -> bool:
|
|
if invulnerable: return true
|
|
health -= damage
|
|
if health <= 0:
|
|
kill(proj.global_position)
|
|
|
|
else:
|
|
if proj is PlayerProjectile:
|
|
knockback = proj.velocity * 2. * knockback_mul * proj.knockback_mul
|
|
|
|
return true
|
|
|
|
|
|
func _on_hurtbox_body_entered(body: Node3D) -> void:
|
|
if body is Player:
|
|
body.damage(self)
|