From f19cb0e796a6b19a27de90f51a353517c5f653af Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Wed, 23 Sep 2026 17:17:19 +0200 Subject: [PATCH] Start work on camera lock --- game/player/Camera/CameraHolder.gd | 60 ++++++- .../InputManagementComponent.gd | 1 + game/player/PlayerCharacter.tscn | 5 + game/player/StateMachine/AimState.gd | 148 ++++++++++++++++++ game/player/StateMachine/AimState.gd.uid | 1 + game/player/StateMachine/IdleState.gd | 4 + game/player/StateMachine/PlayerCharacter.gd | 10 ++ game/player/StateMachine/WalkState.gd | 3 + game/player/actions/AimTarget.gd | 21 +++ game/player/actions/AimTarget.gd.uid | 1 + project.godot | 17 +- scenes/main.tscn | 19 ++- 12 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 game/player/StateMachine/AimState.gd create mode 100644 game/player/StateMachine/AimState.gd.uid create mode 100644 game/player/actions/AimTarget.gd create mode 100644 game/player/actions/AimTarget.gd.uid diff --git a/game/player/Camera/CameraHolder.gd b/game/player/Camera/CameraHolder.gd index 0fcab76..9ece542 100644 --- a/game/player/Camera/CameraHolder.gd +++ b/game/player/Camera/CameraHolder.gd @@ -20,6 +20,16 @@ var active : bool = true : set = set_active var cam_aimed : bool = false #if true, cam goes into "aim/shooter mode", above the play char shoulder @export var aim_cam_pos : Vector3 = Vector3(1.4, 0.9, 0.0) var aim_cam_pos_side : bool = true #false = left, true = right +@export var aim_cam_transition_speed : float = 10.0 +var cam_offset : Vector3 = Vector3.ZERO #decalage actuel de la camera (interpole) +@export_range(0.1, 1.0, 0.01) var aim_distance_multiplier : float = 0.6 #1.0 = meme distance que hors visee, 0.5 = moitie moins loin +@export var min_cam_distance : float = 0.8 #distance minimale entre le pivot et la camera +var cam_distance_mult : float = 1.0 #multiplicateur actuel (interpole) + + +@export_group("Lock-on variables") +@export var lock_on_speed : float = 8.0 +var lock_target : Node3D = null #keybinding variables var aim_cam_action : StringName @@ -49,8 +59,14 @@ func set_active(state : bool) -> void: func _input(event: InputEvent) -> void: #change cam mode (default, aim) - if event.is_action_pressed(aim_cam_action): - cam_aimed = !cam_aimed + #if event.is_action_pressed(aim_cam_action): + #cam_aimed = !cam_aimed + + if event is InputEventMouseMotion: + if has_lock_target(): return + var viewport_transform: Transform2D = get_tree().root.get_final_transform() + var mouse_motion = event.xformed_by(viewport_transform).relative + rotate_from_vector(mouse_motion * mouse_sensibility) if event is InputEventMouseMotion: var viewport_transform: Transform2D = get_tree().root.get_final_transform() @@ -59,11 +75,25 @@ func _input(event: InputEvent) -> void: func _process(delta: float) -> void: #position the cam according to its mode (default, aim (with left or right side)) - if !cam_aimed: cam.position = Vector3(0.0, 0.0, spring_arm.get_hit_length()) - else: cam.position = Vector3(aim_cam_pos.x, aim_cam_pos.y, spring_arm.get_hit_length()) + var target_offset : Vector3 = Vector3.ZERO + var target_mult : float = 1.0 + if cam_aimed: + var side : float = 1.0 if aim_cam_pos_side else -1.0 #true = droite, false = gauche + target_offset = Vector3(aim_cam_pos.x * side, aim_cam_pos.y, aim_cam_pos.z) + target_mult = aim_distance_multiplier - var camera_motion = Input.get_vector(cam_look_left_action, cam_look_right_action, cam_look_up_action, cam_look_down_action) - rotate_from_vector(camera_motion * joystick_sensibility) + var weight : float = clampf(aim_cam_transition_speed * delta, 0.0, 1.0) + cam_offset = cam_offset.lerp(target_offset, weight) + cam_distance_mult = lerpf(cam_distance_mult, target_mult, weight) + + var distance : float = maxf(spring_arm.get_hit_length() * cam_distance_mult + cam_offset.z, min_cam_distance) + cam.position = Vector3(cam_offset.x, cam_offset.y, distance) + + if has_lock_target(): + lock_on_handling(delta) + else: + var camera_motion = Input.get_vector(cam_look_left_action, cam_look_right_action, cam_look_up_action, cam_look_down_action) + rotate_from_vector(camera_motion * joystick_sensibility) #handle zoom zoom_handling(delta) @@ -80,4 +110,22 @@ func zoom_handling(delta : float) -> void: #zoom in/out cam, and clamp zoom value between min and max zoom values spring_arm.spring_length += Input.get_axis(cam_zoom_in_action, cam_zoom_out_action) * zoom_speed * delta spring_arm.spring_length = clamp(spring_arm.spring_length, min_spring_length, max_spring_length) + +func switch_aim_side() -> void: + aim_cam_pos_side = !aim_cam_pos_side +func has_lock_target() -> bool: + return is_instance_valid(lock_target) + +func lock_on_handling(delta : float) -> void: + #tourne progressivement la camera vers la cible verrouillee + var to_target : Vector3 = lock_target.global_position - global_position + var flat_dist : float = Vector2(to_target.x, to_target.z).length() + if flat_dist < 0.01: return + + var target_yaw : float = atan2(-to_target.x, -to_target.z) + var target_pitch : float = clampf(atan2(to_target.y, flat_dist), deg_to_rad(min_limit_x), deg_to_rad(max_limit_x)) + + var weight : float = clampf(lock_on_speed * delta, 0.0, 1.0) + rotation.y = lerp_angle(rotation.y, target_yaw, weight) + rotation.x = lerp_angle(rotation.x, target_pitch, weight) diff --git a/game/player/InputManagementComponent/InputManagementComponent.gd b/game/player/InputManagementComponent/InputManagementComponent.gd index 00ddbdd..c2eeba7 100644 --- a/game/player/InputManagementComponent/InputManagementComponent.gd +++ b/game/player/InputManagementComponent/InputManagementComponent.gd @@ -36,6 +36,7 @@ func attribute_keybinds() -> void: play_char_ref.move_right_action = move_right_action play_char_ref.run_action = run_action play_char_ref.jump_action = jump_action + play_char_ref.aim_cam_action = aim_cam_action cam_holder_ref.aim_cam_action = aim_cam_action cam_holder_ref.cam_zoom_in_action = cam_zoom_in_action diff --git a/game/player/PlayerCharacter.tscn b/game/player/PlayerCharacter.tscn index 5c8b71f..1c2f513 100644 --- a/game/player/PlayerCharacter.tscn +++ b/game/player/PlayerCharacter.tscn @@ -7,6 +7,7 @@ [ext_resource type="Script" uid="uid://bf6hjwfa74ttj" path="res://game/player/StateMachine/IdleState.gd" id="4_38lxl"] [ext_resource type="Script" uid="uid://2agliol5dulc" path="res://game/player/StateMachine/WalkState.gd" id="4_lg056"] [ext_resource type="Script" uid="uid://dr5p8ygj25w6x" path="res://game/player/StateMachine/JumpState.gd" id="6_lwgph"] +[ext_resource type="Script" uid="uid://dn5idrvu68k83" path="res://game/player/StateMachine/AimState.gd" id="7_kxr4l"] [sub_resource type="Curve" id="Curve_kxr4l"] _limits = [0.0, 10.0, 0.0, 1.0] @@ -57,6 +58,7 @@ mesh = SubResource("CapsuleMesh_3agn1") unique_name_in_owner = true transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, -0.009075999, 1.6803148, 0.01983509) fov_val = 30.0 +aim_cam_pos = Vector3(1.4, 0.9, 0) [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1800756522] unique_name_in_owner = true @@ -77,5 +79,8 @@ script = ExtResource("4_lg056") [node name="JumpState" type="Node" parent="StateMachine" unique_id=711160985] script = ExtResource("6_lwgph") +[node name="AimState" type="Node" parent="StateMachine" unique_id=296560188] +script = ExtResource("7_kxr4l") + [node name="InputManagementComponent" type="Node" parent="." unique_id=1981951371] script = ExtResource("2_r5dqo") diff --git a/game/player/StateMachine/AimState.gd b/game/player/StateMachine/AimState.gd new file mode 100644 index 0000000..8248c50 --- /dev/null +++ b/game/player/StateMachine/AimState.gd @@ -0,0 +1,148 @@ +extends State + +class_name AimState + +var state_name : String = "Aim" + +var play_char : CharacterBody3D + +@export var aim_action : StringName = &"player_aim_camera" +@export var next_target_action : StringName = &"player_aim_next_target" +@export var prev_target_action : StringName = &"player_aim_prev_target" +@export var unlock_action : StringName = &"player_aim_unlock" + +@export_group("Targeting") +@export var throw_range : float = 20.0 +@export var max_aim_angle : float = 35.0 #angle max (deg) entre l'axe camera et la cible pour etre candidate +@export var auto_lock_angle : float = 12.0 #si une cible passe sous cet angle, on se verrouille dessus +@export var lose_range_margin : float = 1.25 #marge avant de perdre une cible deja verrouillee +@export var check_line_of_sight : bool = true +@export_flags_3d_physics var line_of_sight_mask : int = 1 + +@export_group("Movement") +@export var aim_move_speed : float = 3.0 +@export var aim_move_accel : float = 8.0 + +func enter(play_char_ref : CharacterBody3D) -> void: + play_char = play_char_ref + verifications() + +func verifications() -> void: + play_char.floor_snap_length = 1.0 + play_char.cam_holder.cam_aimed = true #adapte si ton CameraHolder gere ca autrement + +func exit() -> void: + release_target() + if play_char: play_char.cam_holder.cam_aimed = false + +func physics_update(delta : float) -> void: + if applies(): return + play_char.gravity_apply(delta) + if input_management(): return + update_target() + move(delta) + +func applies() -> bool: + if !play_char.is_on_floor() and play_char.velocity.y < 0.0: + transitioned.emit(self, "InairState") + return true + return false + +func input_management() -> bool: + if !Input.is_action_pressed(aim_action): + transitioned.emit(self, play_char.walk_or_run if play_char.move_dir else "IdleState") + return true + + if Input.is_action_just_pressed(unlock_action): + release_target() + elif Input.is_action_just_pressed(next_target_action): + cycle_target(1) + elif Input.is_action_just_pressed(prev_target_action): + cycle_target(-1) + return false + +func move(delta : float) -> void: + play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y) + + if play_char.move_dir and play_char.is_on_floor(): + play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * aim_move_speed, aim_move_accel * delta) + play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * aim_move_speed, aim_move_accel * delta) + else: + play_char.velocity.x = lerp(play_char.velocity.x, 0.0, play_char.move_deccel * delta) + play_char.velocity.z = lerp(play_char.velocity.z, 0.0, play_char.move_deccel * delta) + +#---------- targeting ---------- + +func update_target() -> void: + var current : AimTarget = play_char.aim_target + + #verrouillage existant : on le garde tant qu'il est valide + if is_instance_valid(current): + if is_target_valid(current, lose_range_margin): return + release_target() + + #visee libre : auto-lock sur la meilleure cible proche du centre de la camera + var best : AimTarget = null + var best_angle : float = auto_lock_angle + for t in get_candidates(): + var angle : float = get_angle_to_camera(t) - t.priority + if angle < best_angle: + best_angle = angle + best = t + if best: set_target(best) + +func get_candidates() -> Array[AimTarget]: + var result : Array[AimTarget] = [] + for node in get_tree().get_nodes_in_group("aim_targets"): + var t := node as AimTarget + if t and is_target_valid(t, 1.0): result.append(t) + return result + +func is_target_valid(t : AimTarget, range_mult : float) -> bool: + if !t.enabled: return false + if play_char.global_position.distance_to(t.global_position) > throw_range * range_mult: return false + if get_angle_to_camera(t) > max_aim_angle * range_mult: return false + if check_line_of_sight and !has_line_of_sight(t): return false + return true + +func get_angle_to_camera(t : AimTarget) -> float: + var cam : Camera3D = play_char.cam_holder.cam + var to_t : Vector3 = t.global_position - cam.global_position + return rad_to_deg((-cam.global_basis.z).angle_to(to_t)) + +func has_line_of_sight(t : AimTarget) -> bool: + var query := PhysicsRayQueryParameters3D.create( + play_char.cam_holder.cam.global_position, t.global_position, line_of_sight_mask, [play_char.get_rid()]) + var hit : Dictionary = play_char.get_world_3d().direct_space_state.intersect_ray(query) + #pas d'obstacle, ou l'obstacle est le proprietaire de la cible lui-meme + return hit.is_empty() or hit["collider"].is_ancestor_of(t) + +func cycle_target(direction : int) -> void: + #trie les cibles de gauche a droite (par rapport a la camera) et passe a la suivante/precedente + var cam : Camera3D = play_char.cam_holder.cam + var forward : Vector3 = -cam.global_basis.z + var list : Array[AimTarget] = get_candidates() + if list.is_empty(): return + list.sort_custom(func(a : AimTarget, b : AimTarget) -> bool: + return signed_angle(forward, a, cam) < signed_angle(forward, b, cam)) + + var idx : int = list.find(play_char.aim_target) + idx = 0 if idx == -1 else posmod(idx + direction, list.size()) + set_target(list[idx]) + +func signed_angle(forward : Vector3, t : AimTarget, cam : Camera3D) -> float: + var to_t : Vector3 = t.global_position - cam.global_position + return Vector2(forward.x, forward.z).angle_to(Vector2(to_t.x, to_t.z)) + +func set_target(t : AimTarget) -> void: + if play_char.aim_target == t: return + release_target() + play_char.aim_target = t + play_char.cam_holder.lock_target = t + t.set_locked(true) + +func release_target() -> void: + if !play_char: return + if is_instance_valid(play_char.aim_target): play_char.aim_target.set_locked(false) + play_char.aim_target = null + play_char.cam_holder.lock_target = null diff --git a/game/player/StateMachine/AimState.gd.uid b/game/player/StateMachine/AimState.gd.uid new file mode 100644 index 0000000..c7b18cf --- /dev/null +++ b/game/player/StateMachine/AimState.gd.uid @@ -0,0 +1 @@ +uid://dn5idrvu68k83 diff --git a/game/player/StateMachine/IdleState.gd b/game/player/StateMachine/IdleState.gd index a815290..35d30f4 100644 --- a/game/player/StateMachine/IdleState.gd +++ b/game/player/StateMachine/IdleState.gd @@ -47,6 +47,10 @@ func input_management() -> void: if Input.is_action_pressed(play_char.jump_action) if play_char.auto_jump else Input.is_action_just_pressed(play_char.jump_action): transitioned.emit(self, "JumpState") + #if Input.is_action_just_pressed("player_aim_camera"): + if Input.is_action_just_pressed(play_char.aim_cam_action): + transitioned.emit(self, "AimState") + if Input.is_action_just_pressed(play_char.run_action): if play_char.walk_or_run == "WalkState": play_char.walk_or_run = "RunState" elif play_char.walk_or_run == "RunState": play_char.walk_or_run = "WalkState" diff --git a/game/player/StateMachine/PlayerCharacter.gd b/game/player/StateMachine/PlayerCharacter.gd index 2a0af13..71b72f6 100644 --- a/game/player/StateMachine/PlayerCharacter.gd +++ b/game/player/StateMachine/PlayerCharacter.gd @@ -59,6 +59,7 @@ var move_left_action : StringName var move_right_action : StringName var run_action : StringName var jump_action : StringName +var aim_cam_action : StringName @export_group("Model variables") @export var model_rot_speed : float = 6.5 @@ -81,6 +82,8 @@ var jump_action : StringName #@onready var jump_particles = preload("../../PlayerCharacter/VFX/jump_particles_scene.tscn") #@onready var land_particles = preload("../../PlayerCharacter/VFX/land_particles_scene.tscn") +var aim_target : AimTarget = null + func _ready(): #set move variables, and value references move_speed = walk_speed @@ -106,6 +109,13 @@ func modify_model_orientation(delta : float): var dir_target_angle : float + #lock-on : le modele fait face a la cible + if is_instance_valid(aim_target): + var to_target : Vector3 = aim_target.global_position - global_position + dir_target_angle = -Vector2(to_target.x, to_target.z).orthogonal().angle() + visual_root.rotation.y = rotate_toward(visual_root.rotation.y, dir_target_angle, model_rot_speed * delta) + return + #follow mode (model must follow the camera rotation) #if the cam is in angled/aim mode if cam_holder.cam_aimed and follow_cam_pos_when_aimed: diff --git a/game/player/StateMachine/WalkState.gd b/game/player/StateMachine/WalkState.gd index c28be48..014df71 100644 --- a/game/player/StateMachine/WalkState.gd +++ b/game/player/StateMachine/WalkState.gd @@ -48,6 +48,9 @@ func applies() -> void: func input_management() -> void: if Input.is_action_pressed(play_char.jump_action) if play_char.auto_jump else Input.is_action_just_pressed(play_char.jump_action) : transitioned.emit(self, "JumpState") + + if Input.is_action_just_pressed(play_char.aim_cam_action): + transitioned.emit(self, "AimState") if Input.is_action_just_pressed(play_char.run_action): play_char.walk_or_run = "RunState" diff --git a/game/player/actions/AimTarget.gd b/game/player/actions/AimTarget.gd new file mode 100644 index 0000000..5bfaa77 --- /dev/null +++ b/game/player/actions/AimTarget.gd @@ -0,0 +1,21 @@ +extends Marker3D + +class_name AimTarget + +signal locked_changed(is_locked : bool) + +@export var enabled : bool = true +@export var priority : float = 0.0 #plus c'est haut, plus la cible est favorisée +@export var indicator : Node3D #optionnel : reticule/sprite affiché quand la cible est verrouillée + +var is_locked : bool = false + +func _ready() -> void: + add_to_group("aim_targets") + if indicator: indicator.visible = false + +func set_locked(value : bool) -> void: + if is_locked == value: return + is_locked = value + if indicator: indicator.visible = value + locked_changed.emit(value) diff --git a/game/player/actions/AimTarget.gd.uid b/game/player/actions/AimTarget.gd.uid new file mode 100644 index 0000000..026c670 --- /dev/null +++ b/game/player/actions/AimTarget.gd.uid @@ -0,0 +1 @@ +uid://ce2wj6d8xb055 diff --git a/project.godot b/project.godot index bbc95a1..70a11c6 100644 --- a/project.godot +++ b/project.godot @@ -109,7 +109,7 @@ pause={ player_aim_camera={ "deadzone": 0.2, "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(251, 16),"global_position":Vector2(263, 76),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) -, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":true,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null) ] } player_zoom_camera_in={ @@ -144,6 +144,21 @@ player_crouch={ "events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":true,"script":null) ] } +player_aim_next_target={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":true,"script":null) +] +} +player_aim_prev_target={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null) +] +} +player_aim_unlock={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) +] +} [physics] diff --git a/scenes/main.tscn b/scenes/main.tscn index 7040ae4..5e50541 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -5,6 +5,7 @@ [ext_resource type="Texture2D" uid="uid://cm5m7nr7dugjd" path="res://assets/textures/grids/Purple/texture_09.png" id="3_tefeu"] [ext_resource type="Texture2D" uid="uid://djg8y25y06h5n" path="res://assets/textures/grids/Green/texture_09.png" id="4_o6xl0"] [ext_resource type="Texture2D" uid="uid://hu5rfl6qr2nv" path="res://assets/textures/grids/Red/texture_09.png" id="5_o6xl0"] +[ext_resource type="Script" uid="uid://ce2wj6d8xb055" path="res://game/player/actions/AimTarget.gd" id="6_85g3d"] [ext_resource type="PackedScene" uid="uid://dwxpt0s7vn36k" path="res://game/player/PlayerCharacter.tscn" id="6_o6xl0"] [ext_resource type="PackedScene" uid="uid://d1mcef8gl66j" path="res://scenes/pause_menu.tscn" id="6_tefeu"] [ext_resource type="PackedScene" uid="uid://di22cs2qposdm" path="res://game/ui/ui.tscn" id="8_tipki"] @@ -79,23 +80,35 @@ use_collision = true size = Vector3(3, 3, 3) [node name="target" type="CSGBox3D" parent="map" unique_id=1890903851] -transform = Transform3D(0.99887395, 0, -0.04744184, 0, 1, 0, 0.04744184, 0, 0.99887395, 7.7646155, 2.1005468, -0.10642481) +transform = Transform3D(0.99887395, 0, -0.04744184, 0, 1, 0, 0.04744184, 0, 0.99887395, 7.7646155, 2.4045455, -0.10642481) material_override = SubResource("StandardMaterial3D_tipki") use_collision = true size = Vector3(0.5, 0.5, 0.5) +[node name="AimTarget" type="Marker3D" parent="map/target" unique_id=807674215] +transform = Transform3D(1, 0, 3.7252903e-09, 0, 1, 0, -3.7252903e-09, 0, 1, 0, 0.030708313, 0) +script = ExtResource("6_85g3d") + [node name="target2" type="CSGBox3D" parent="map" unique_id=1489015232] -transform = Transform3D(0.99887395, 0, -0.04744184, 0, 1, 0, 0.04744184, 0, 0.99887395, 7.7646155, 2.1005468, 2.931901) +transform = Transform3D(0.99887395, 0, -0.04744184, 0, 1, 0, 0.04744184, 0, 0.99887395, 7.7646155, 0.20568156, 2.931901) material_override = SubResource("StandardMaterial3D_tipki") use_collision = true size = Vector3(0.5, 0.5, 0.5) +[node name="AimTarget" type="Marker3D" parent="map/target2" unique_id=1618946008] +transform = Transform3D(1, 0, 3.7252903e-09, 0, 1, 0, -3.7252903e-09, 0, 1, 0, 0.030708313, 0) +script = ExtResource("6_85g3d") + [node name="target3" type="CSGBox3D" parent="map" unique_id=1721457742] -transform = Transform3D(0.99887395, 0, -0.04744184, 0, 1, 0, 0.04744184, 0, 0.99887395, 7.7646155, 2.1005468, 5.7028685) +transform = Transform3D(0.99887395, 0, -0.04744184, 0, 1, 0, 0.04744184, 0, 0.99887395, 7.7646155, 1.7272058, 8.399635) material_override = SubResource("StandardMaterial3D_tipki") use_collision = true size = Vector3(0.5, 0.5, 0.5) +[node name="AimTarget" type="Marker3D" parent="map/target3" unique_id=1848628150] +transform = Transform3D(1, 0, 3.7252903e-09, 0, 1, 0, -3.7252903e-09, 0, 1, 0, 0.030708313, 0) +script = ExtResource("6_85g3d") + [node name="Ui" parent="." unique_id=1881560149 instance=ExtResource("8_tipki")] [node name="PauseMenu" parent="." unique_id=871841579 instance=ExtResource("6_tefeu")]