| extends CharacterBody3D
|
| @export var chase_speed: float = 4.0
|
| @export var detection_range: float = 100.0
|
| @export var sprite_scale: float = 5.0
|
| @onready var sight_ray: RayCast3D = $SightRay
|
| @onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
|
| @onready var anim_sprite: AnimatedSprite3D = $AnimatedSprite3D
|
|
|
| var player: Node3D
|
| var can_see_player: bool = false
|
| var nav_ready: bool = false
|
| enum State { IDLE, CHASE, SEARCH }
|
| var state: State = State.IDLE
|
| var last_state: State = State.IDLE
|
|
|
| func _ready() -> void:
|
| scale = Vector3(1, 1, 1)
|
| $CollisionShape3D.scale = Vector3(1, 1, 1)
|
| anim_sprite.scale = Vector3(sprite_scale, sprite_scale, sprite_scale)
|
| global_position.y = 0.5
|
|
|
| player = get_tree().get_first_node_in_group("player")
|
| if player:
|
| print("Tiger found the player: ", player.name)
|
| else:
|
| print("Tiger cnt find player ")
|
| anim_sprite.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
| _play_anim("idle")
|
| await get_tree().physics_frame
|
| await get_tree().physics_frame
|
| if player:
|
| nav_agent.target_position = player.global_position
|
| nav_ready = true
|
| print(" Nav readyTiger pos: ", global_position)
|
|
|
| func _physics_process(delta: float) -> void:
|
| if not player or not nav_ready:
|
| return
|
| _update_sight()
|
| _update_state()
|
| _move(delta)
|
| _update_animation()
|
|
|
| func _update_sight() -> void:
|
| var to_player = player.global_position - global_position
|
| var distance = to_player.length()
|
|
|
| if distance > detection_range:
|
| can_see_player = false
|
| return
|
|
|
| sight_ray.global_position = global_position + Vector3(0, 1.5, 0)
|
| sight_ray.target_position = sight_ray.to_local(
|
| player.global_position + Vector3(0, 1.5, 0)
|
| )
|
| sight_ray.force_raycast_update()
|
|
|
| if sight_ray.is_colliding():
|
| var hit = sight_ray.get_collider()
|
| can_see_player = hit == player
|
| else:
|
| can_see_player = true
|
|
|
| func _update_state() -> void:
|
| match state:
|
| State.IDLE:
|
| if can_see_player:
|
| state = State.CHASE
|
| print("tgr State: CHASE")
|
| State.CHASE:
|
| if can_see_player:
|
| nav_agent.target_position = player.global_position
|
| else:
|
| state = State.SEARCH
|
| print("tgr State: SEARCH")
|
| State.SEARCH:
|
| if can_see_player:
|
| state = State.CHASE
|
| print("tgr State: CHASE")
|
| elif nav_agent.is_navigation_finished():
|
| state = State.IDLE
|
| print("tgr State: IDLE")
|
| func _move(delta: float) -> void:
|
| if state == State.IDLE or nav_agent.is_navigation_finished():
|
| velocity.x = 0
|
| velocity.z = 0
|
| if not is_on_floor():
|
| velocity.y -= 9.8 * delta
|
| move_and_slide()
|
| return
|
|
|
| var next_pos = nav_agent.get_next_path_position()
|
| var diff = next_pos - global_position
|
| diff.y = 0
|
| var distance_to_next = diff.length()
|
|
|
| if distance_to_next < 0.1:
|
| velocity.x = 0
|
| velocity.z = 0
|
| else:
|
| var direction = diff.normalized()
|
| velocity.x = direction.x * chase_speed
|
| velocity.z = direction.z * chase_speed
|
|
|
| if not is_on_floor():
|
| velocity.y -= 9.8 * delta
|
| else:
|
| velocity.y = 0
|
| move_and_slide()
|
| func _update_animation() -> void:
|
| var cam = get_viewport().get_camera_3d()
|
| if cam and velocity.length() > 0.1:
|
| var local_vel = cam.global_transform.basis.inverse() * velocity
|
| anim_sprite.flip_h = local_vel.x < 0
|
| if state == last_state:
|
| return
|
| last_state = state
|
| match state:
|
| State.IDLE:
|
| _play_anim("idle")
|
| State.CHASE, State.SEARCH:
|
| _play_anim("run")
|
| func _play_anim(anim_name: String) -> void:
|
| if anim_sprite.sprite_frames and \
|
| anim_sprite.sprite_frames.has_animation(anim_name) and \
|
| anim_sprite.animation != anim_name:
|
| anim_sprite.play(anim_name)
|