extends CharacterBody3D enum State { IDLE, FLYING } const AIR_FRICTION := 0.84 const MIN_SPEED := 0.5 const DAMAGE_MULT := 0.9 const WALL_BOUNCE := 0.5 const WALL_SELF_DMG := 0.6 const HIT_SELF_DMG := 0.4 var state: State = State.IDLE var fly_vel: Vector3 = Vector3.ZERO var health: float = 60.0 var dead: bool = false @onready var mesh_node: MeshInstance3D = $RockMesh var rock_mat: StandardMaterial3D const COLOR_IDLE := Color(0.45, 0.38, 0.30) const COLOR_IMPACT := Color(1.0, 1.0, 1.0) func _ready() -> void: add_to_group("rocks") rock_mat = mesh_node.material_override.duplicate() as StandardMaterial3D mesh_node.material_override = rock_mat func receive_kick(direction: Vector3, force: float) -> void: fly_vel = direction * force fly_vel.y = 0.0 state = State.FLYING func _physics_process(delta: float) -> void: if state == State.IDLE: return _fly(delta) func _fly(delta: float) -> void: var speed_now := Vector2(fly_vel.x, fly_vel.z).length() velocity = fly_vel velocity.y = 0.0 move_and_slide() var handled := false for i in get_slide_collision_count(): var col := get_slide_collision(i) var col3d := col.get_collider() as Node3D if col3d == null: continue if col3d.has_meta("is_wall"): var normal := col.get_normal() normal.y = 0.0 if normal.length() > 0.01: fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE else: fly_vel = Vector3.ZERO _take_damage(speed_now * WALL_SELF_DMG) handled = true break elif col3d.is_in_group("enemies"): var to_enemy := col3d.global_position - global_position to_enemy.y = 0.0 var dir := to_enemy.normalized() if to_enemy.length() > 0.01 else (fly_vel.normalized() if fly_vel.length() > 0.01 else Vector3.FORWARD) col3d.call("_take_hit", int(speed_now * DAMAGE_MULT)) col3d.call("receive_kick", dir, speed_now * 0.65) fly_vel *= 0.45 _take_damage(speed_now * HIT_SELF_DMG) handled = true break if not handled: fly_vel = velocity fly_vel.y = 0.0 fly_vel *= pow(AIR_FRICTION, delta * 60.0) if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED: fly_vel = Vector3.ZERO velocity = Vector3.ZERO state = State.IDLE mesh_node.rotation.y += delta * speed_now * 0.25 func _take_damage(dmg: float) -> void: if dead: return health -= dmg _flash() if health <= 0.0: _die() func _die() -> void: dead = true state = State.IDLE set_physics_process(false) var tw := create_tween() tw.tween_property(self, "scale", Vector3(1.6, 0.1, 1.6), 0.12) tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1) tw.tween_callback(queue_free) func _flash() -> void: var tw := create_tween() tw.tween_property(rock_mat, "albedo_color", COLOR_IMPACT, 0.04) var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 60.0, 0.0, 0.6)) tw.tween_property(rock_mat, "albedo_color", target_color, 0.18)