extends CharacterBody3D ## --- Paramètres à régler dans l'inspecteur --- @export var player: Node3D # Référence au joueur (à assigner dans l'inspecteur ou via groupe) @export var flee_enabled: bool = false # Si false, l'animal ne fuit jamais, il se balade juste dans sa zone @export var move_speed: float = 4.8 @export var acceleration: float = 12 @export var rotation_speed: float = 5 @export_group("Zone de confinement") @export var zone_center: Vector3 = Vector3.ZERO # Centre de la zone (en coordonnées globales) @export var zone_size: Vector2 = Vector2(30.0, 30.0) # Largeur (x) et profondeur (z) du rectangle @export var zone_rotation_degrees: float = 0.0 # Rotation du rectangle autour de l'axe Y @export var zone_margin: float = 1.5 # Marge de sécurité avant le bord @export_group("Comportement idle") @export var wander_enabled: bool = true @export var wander_radius: float = 5.0 @export var wander_interval: float = 3.0 var _wander_target: Vector3 var _wander_timer: float = 0.0 var _state: String = "idle" # "idle" ou "flee" # --- NOUVEAU : Variables pour gérer le déblocage sur les bords --- var _stuck_timer: float = 0.0 var _sidestep_direction: float = 1.0 # 1.0 pour droite, -1.0 pour gauche @onready var anim_player: AnimationPlayer = $foxes/AnimationPlayer func _ready() -> void: _wander_target = global_position if player == null: var players := get_tree().get_nodes_in_group("player") if players.size() > 0: player = players[0] func _physics_process(delta: float) -> void: if not player == null: # --- Détermine l'état --- if flee_enabled: _state = "flee" else: _state = "idle" var desired_direction: Vector3 if _state == "flee": desired_direction = _get_flee_direction(delta) else: desired_direction = _get_wander_direction(delta) _apply_movement(desired_direction, delta) _update_animation() func _get_flee_direction(delta: float) -> Vector3: # Direction opposée au joueur (sur le plan horizontal) var away := (global_position - player.global_position) away.y = 0.0 away = away.normalized() var target_pos := global_position + away * 5.0 var clamped_target = _clamp_to_zone(target_pos) # Détection de blocage : si la cible calculée est quasiment la même que notre position # ou si on est bloqué contre un bord (la cible clampée diffère fortement de la cible brute) if target_pos.distance_to(clamped_target) > 0.5: _stuck_timer += delta if _stuck_timer > 0.3: # Si bloqué pendant plus de 0.3 secondes # On choisit aléatoirement ou on alterne gauche/droite pour longer le bord if randf() < 0.02: _sidestep_direction *= -1.0 # On décale la fuite perpendiculairement (à gauche ou à droite de la fuite) var perpendicular = Vector3(-away.z, 0.0, away.x) * _sidestep_target_sign() away = (away + perpendicular).normalized() clamped_target = _clamp_to_zone(global_position + away * 5.0) else: _stuck_timer = 0.0 return (clamped_target - global_position).normalized() func _sidestep_target_sign() -> float: return _sidestep_direction func _get_wander_direction(delta: float) -> Vector3: if not wander_enabled: return Vector3.ZERO _wander_timer -= delta if _wander_timer <= 0.0 or global_position.distance_to(_wander_target) < 0.5: _wander_timer = wander_interval _wander_target = _pick_random_point_in_zone() var dir := (_wander_target - global_position) dir.y = 0.0 if dir.length() < 0.1: return Vector3.ZERO return dir.normalized() func _pick_random_point_in_zone() -> Vector3: var angle := randf() * TAU var radius := randf() * wander_radius var offset := Vector3(cos(angle), 0.0, sin(angle)) * radius var point := global_position + offset return _clamp_to_zone(point) func _clamp_to_zone(point: Vector3) -> Vector3: var half_width := (zone_size.x * 0.5) - zone_margin var half_depth := (zone_size.y * 0.5) - zone_margin var rot_rad := deg_to_rad(zone_rotation_degrees) var local_point := (point - zone_center).rotated(Vector3.UP, -rot_rad) local_point.x = clamp(local_point.x, -half_width, half_width) local_point.z = clamp(local_point.z, -half_depth, half_depth) return zone_center + local_point.rotated(Vector3.UP, rot_rad) func _apply_movement(direction: Vector3, delta: float) -> void: var speed := move_speed if _state == "flee": speed *= 1.6 var target_velocity := direction * speed target_velocity.y = velocity.y velocity.x = move_toward(velocity.x, target_velocity.x, acceleration * delta) velocity.z = move_toward(velocity.z, target_velocity.z, acceleration * delta) if not is_on_floor(): velocity.y -= 9.8 * delta if direction.length() > 0.1: var target_angle := atan2(direction.x, direction.z) rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta) move_and_slide() func _update_animation() -> void: if anim_player.current_animation != "dog_rig_Canter": anim_player.play("dog_rig_Canter") func _on_area_3d_body_entered(body: Node3D) -> void: if body.is_in_group("player"): #if body.can_run: body.fox_grabbed += 1 queue_free()