diff --git a/GEMINI.md b/.agents/AGENTS.md similarity index 100% rename from GEMINI.md rename to .agents/AGENTS.md diff --git a/README.md b/README.md index 63db8ae..14105e2 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ - Fireball: Shift + B - Phone: Tab - Pause menu: Esc +- Zoom (in/out): Scroll Wheel (snaps to First Person on zoom in) diff --git a/game/scenes/Characters/location_player.gd b/game/scenes/Characters/location_player.gd deleted file mode 100644 index 9093129..0000000 --- a/game/scenes/Characters/location_player.gd +++ /dev/null @@ -1,273 +0,0 @@ -extends RigidBody3D -# Initially I used a CharacterBody3D, however, I wanted the player to bounce off -# other objects in the environment and that would have required manual handling -# of collisions. So that's why we're using a RigidBody3D instead. -signal vehicle_entered(vehicle: Node) -signal vehicle_exited(vehicle: Node) - -const MOVE_SPEED := 8.0 -const SPRINT_MOVE_SPEED :=13 -const ACCELLERATION := 30.0 -const DECELLERATION := 40.0 -const JUMP_SPEED := 4.0 -const MAX_NUMBER_OF_JUMPS := 2 -const MIN_FOV := 10.0 -const MAX_FOV := 179.0 -const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1 -const FIREBALL_SPAWN_FORWARD_OFFSET := 1.5 -const FIREBALL_SPAWN_UP_OFFSET := 1.2 -var mouse_sensitivity := 0.005 -var rotation_x := 0.0 -var rotation_y := 0.0 -var current_number_of_jumps := 0 -var _pending_mouse_delta := Vector2.ZERO -var _last_move_forward := Vector3(0, 0, 1) -var _last_move_right := Vector3(1, 0, 0) -var _camera_offset_local := Vector3.ZERO -var _camera_yaw := 0.0 -var _camera_pitch := 0.0 -var _in_vehicle := false -var _vehicle_collision_layer := 0 -var _vehicle_collision_mask := 0 -var _vehicle_original_parent: Node = null -var _light_was_on := false -var _jump_triggered := false -@onready var _flashlight: SpotLight3D = $SpotLight3D -@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer -@onready var _anim_tree: AnimationTree = find_child("AnimationTree", true, false) as AnimationTree -@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D - -@export var camera_follow_speed := 10.0 -@export var anim_idle_name := "Idle" -@export var anim_walk_name := "Walk" -@export var anim_jump_name := "Jump" -@export var anim_run_name := "Run" -@export var anim_walk_speed_threshold := 0.25 -@export var anim_sprint_speed_threshold := 10.0 - -var jump_sound = preload("res://assets/audio/jump.ogg") -var fireball_scene = preload("res://scenes/Projectiles/fireball.tscn") -var audio_player = AudioStreamPlayer.new() - -@export var camera_path: NodePath -@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null -@export var phone_path: NodePath -@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null -var phone_visible := false - -func _ready() -> void: - add_to_group("player") - if _anim_tree: - _anim_tree.active = false - axis_lock_angular_x = true - axis_lock_angular_z = true - angular_damp = 6.0 - contact_monitor = true - max_contacts_reported = 4 - add_child(audio_player) - audio_player.stream = jump_sound - audio_player.volume_db = -20 - if cam: - _camera_offset_local = cam.transform.origin - _camera_pitch = cam.rotation.x - _camera_yaw = global_transform.basis.get_euler().y - cam.set_as_top_level(true) - cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local) - cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) - var move_basis := cam.global_transform.basis if cam else global_transform.basis - var forward := move_basis.z - var right := move_basis.x - forward.y = 0.0 - right.y = 0.0 - if forward.length() > 0.0001: - _last_move_forward = forward.normalized() - if right.length() > 0.0001: - _last_move_right = right.normalized() - _vehicle_collision_layer = collision_layer - _vehicle_collision_mask = collision_mask - Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) - -func _integrate_forces(state): - if _in_vehicle: - linear_velocity = Vector3.ZERO - return - if _pending_mouse_delta != Vector2.ZERO: - rotation_x -= _pending_mouse_delta.y * mouse_sensitivity - rotation_y -= _pending_mouse_delta.x * mouse_sensitivity - rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) - _camera_pitch = rotation_x - rotation.y = rotation_y - _pending_mouse_delta = Vector2.ZERO - - var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") - var forward := Vector3.FORWARD * -1.0 - var right := Vector3.RIGHT - if cam: - forward = cam.global_transform.basis.z - right = cam.global_transform.basis.x - forward.y = 0.0 - right.y = 0.0 - if forward.length() > 0.0001: - forward = forward.normalized() - _last_move_forward = forward - else: - forward = _last_move_forward - if right.length() > 0.0001: - right = right.normalized() - _last_move_right = right - else: - right = _last_move_right - - var dir := (right * input2v.x + forward * input2v.y).normalized() - var target_v := dir * MOVE_SPEED - if Input.is_key_pressed(KEY_SHIFT): - target_v = dir * SPRINT_MOVE_SPEED - - var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION - linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step) - linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step) - - var on_floor = false - for i in state.get_contact_count(): - var normal = state.get_contact_local_normal(i) - if normal.y > 0.5: - on_floor = true - break - - if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1): - current_number_of_jumps = (current_number_of_jumps + 1) % 2 - linear_velocity.y = JUMP_SPEED - audio_player.play() - _jump_triggered = true - - if cam: - var target_yaw := global_transform.basis.get_euler().y - _camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step) - var target_basis := Basis(Vector3.UP, _camera_yaw) - var target_pos := global_position + (target_basis * _camera_offset_local) - cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step) - cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) - - _update_animation(on_floor, state.linear_velocity) - _jump_triggered = false - -func _input(event): - if event.is_action_pressed("player_phone"): - phone_visible = !phone_visible - if phone: - phone.visible = phone_visible - if phone_visible: - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - else: - Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) - return - - if _in_vehicle: - return - - if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: - _pending_mouse_delta += event.relative - - if event is InputEventMouseButton and event.pressed: - if event.button_index == MOUSE_BUTTON_WHEEL_UP: - zoom_camera(1.0 / ZOOM_FACTOR) - elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: - zoom_camera(ZOOM_FACTOR) - - if event.is_action_pressed("player_fireball"): - shoot_fireball() - return - - if event.is_action_pressed("player_light"): - _flashlight.visible = !_flashlight.visible - -func zoom_camera(factor): - if cam == null: - return - var new_fov = cam.fov * factor - cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV) - -func shoot_fireball() -> void: - var direction := get_look_direction() - var origin := get_look_origin() + direction * FIREBALL_SPAWN_FORWARD_OFFSET - var fireball := fireball_scene.instantiate() - get_tree().current_scene.add_child(fireball) - fireball.launch(origin, direction) - -func _update_animation(on_floor: bool, velocity: Vector3) -> void: - if _anim_player == null: - return - var horizontal_speed := Vector3(velocity.x, 0.0, velocity.z).length() - if _jump_triggered and _anim_player.has_animation(anim_jump_name): - if _anim_player.current_animation != anim_jump_name: - _anim_player.play(anim_jump_name) - return - if not on_floor and _anim_player.has_animation(anim_jump_name): - if _anim_player.current_animation != anim_jump_name: - _anim_player.play(anim_jump_name) - return - if on_floor and horizontal_speed > anim_sprint_speed_threshold and _anim_player.has_animation(anim_run_name): - if _anim_player.current_animation != anim_run_name: - _anim_player.play(anim_run_name) - return - if horizontal_speed > anim_walk_speed_threshold and _anim_player.has_animation(anim_walk_name): - if _anim_player.current_animation != anim_walk_name: - _anim_player.play(anim_walk_name) - return - if _anim_player.has_animation(anim_idle_name): - if _anim_player.current_animation != anim_idle_name: - _anim_player.play(anim_idle_name) - -func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void: - _in_vehicle = true - freeze = true - sleeping = true - collision_layer = 0 - collision_mask = 0 - _vehicle_original_parent = get_parent() - _light_was_on = _flashlight.visible - _flashlight.visible = false - if _model_root: - _model_root.visible = false - if seat: - reparent(seat, true) - global_transform = seat.global_transform - if cam: - cam.current = false - if vehicle_camera: - vehicle_camera.current = true - vehicle_entered.emit(_vehicle) - -func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void: - _in_vehicle = false - freeze = false - sleeping = false - collision_layer = _vehicle_collision_layer - collision_mask = _vehicle_collision_mask - if _vehicle_original_parent: - reparent(_vehicle_original_parent, true) - _vehicle_original_parent = null - _flashlight.visible = _light_was_on - if _model_root: - _model_root.visible = true - if exit_point: - global_transform = exit_point.global_transform - if vehicle_camera: - vehicle_camera.current = false - if cam: - cam.current = true - vehicle_exited.emit(null) - - -func is_in_vehicle() -> bool: - return _in_vehicle - - -func get_look_origin() -> Vector3: - return global_position + Vector3.UP * FIREBALL_SPAWN_UP_OFFSET - - -func get_look_direction() -> Vector3: - if cam: - return (-cam.global_transform.basis.z).normalized() - return (-global_basis.z).normalized() diff --git a/game/scenes/Characters/location_player.gd.uid b/game/scenes/Characters/location_player.gd.uid deleted file mode 100644 index df1190e..0000000 --- a/game/scenes/Characters/location_player.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://kgqaeqappow3 diff --git a/game/scenes/Characters/location_player.tscn b/game/scenes/Characters/location_player.tscn index 84e4288..692078b 100644 --- a/game/scenes/Characters/location_player.tscn +++ b/game/scenes/Characters/location_player.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3] -[ext_resource type="Script" path="res://scenes/Characters/location_player.gd" id="1_player_script"] +[ext_resource type="Script" path="res://scenes/player.gd" id="1_player_script"] [ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="2_model"] [sub_resource type="SphereShape3D" id="SphereShape3D_player"] diff --git a/game/scenes/Interaction/teleport_state.gd b/game/scenes/Interaction/teleport_state.gd index e3206df..6f81677 100644 --- a/game/scenes/Interaction/teleport_state.gd +++ b/game/scenes/Interaction/teleport_state.gd @@ -1,6 +1,7 @@ extends Node var _requested_spawn_name: StringName = &"" +var camera_zoom_dist: float = 3.0 func request_spawn(spawn_name: StringName) -> void: diff --git a/game/scenes/player.gd b/game/scenes/player.gd index 4d8076b..d0e274d 100644 --- a/game/scenes/player.gd +++ b/game/scenes/player.gd @@ -11,9 +11,11 @@ const ACCELLERATION := 30.0 const DECELLERATION := 40.0 const JUMP_SPEED := 4.0 const MAX_NUMBER_OF_JUMPS := 2 -const MIN_FOV := 10.0 -const MAX_FOV := 179.0 -const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1 +const DEFAULT_ZOOM_DIST := 3.0 +const MIN_ZOOM_DIST := 1.5 +const MAX_ZOOM_DIST := 8.0 +const ZOOM_STEP := 0.5 +var _zoom_dist := DEFAULT_ZOOM_DIST const FIREBALL_SPAWN_FORWARD_OFFSET := 1.5 const FIREBALL_SPAWN_UP_OFFSET := 1.2 var mouse_sensitivity := 0.005 @@ -68,10 +70,12 @@ func _ready() -> void: audio_player.stream = jump_sound audio_player.volume_db = -20 if cam: - _camera_offset_local = cam.transform.origin + if TeleportState != null: + _zoom_dist = TeleportState.camera_zoom_dist _camera_pitch = cam.rotation.x _camera_yaw = global_transform.basis.get_euler().y cam.set_as_top_level(true) + update_camera_offset() cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local) cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) var move_basis := cam.global_transform.basis if cam else global_transform.basis @@ -156,6 +160,9 @@ func _integrate_forces(state): cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step) cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) + if _model_root: + _model_root.visible = (_zoom_dist > 0.0) and not _in_vehicle + _update_animation(on_floor, state.linear_velocity) _jump_triggered = false @@ -178,9 +185,9 @@ func _input(event): if event is InputEventMouseButton and event.pressed: if event.button_index == MOUSE_BUTTON_WHEEL_UP: - zoom_camera(1.0 / ZOOM_FACTOR) # Zoom in + zoom_camera(true) # Zoom in elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: - zoom_camera(ZOOM_FACTOR) # Zoom out + zoom_camera(false) # Zoom out if event.is_action_pressed("player_fireball"): shoot_fireball() @@ -189,11 +196,33 @@ func _input(event): if event.is_action_pressed("player_light"): _flashlight.visible = !_flashlight.visible -func zoom_camera(factor): +func zoom_camera(zoom_in: bool) -> void: if cam == null: return - var new_fov = cam.fov * factor - cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV) + if zoom_in: + if _zoom_dist == 0.0: + return + elif _zoom_dist <= MIN_ZOOM_DIST: + _zoom_dist = 0.0 + else: + _zoom_dist = max(MIN_ZOOM_DIST, _zoom_dist - ZOOM_STEP) + else: + if _zoom_dist == 0.0: + _zoom_dist = MIN_ZOOM_DIST + else: + _zoom_dist = min(MAX_ZOOM_DIST, _zoom_dist + ZOOM_STEP) + + if TeleportState != null: + TeleportState.camera_zoom_dist = _zoom_dist + + update_camera_offset() + +func update_camera_offset() -> void: + if _zoom_dist == 0.0: + _camera_offset_local = Vector3(0.0, 1.6, 0.0) + else: + var height_offset := 0.25 * _zoom_dist + _camera_offset_local = Vector3(0.5, 1.6 + height_offset, _zoom_dist) func shoot_fireball() -> void: var direction := get_look_direction() @@ -284,7 +313,7 @@ func is_in_vehicle() -> bool: func get_look_origin() -> Vector3: - return global_position + Vector3.UP * 1.2 + return global_position + Vector3.UP * FIREBALL_SPAWN_UP_OFFSET func get_look_direction() -> Vector3: