diff --git a/overworld/camera/OverworldCamera.gd b/overworld/camera/OverworldCamera.gd index f8b6575..e992bae 100644 --- a/overworld/camera/OverworldCamera.gd +++ b/overworld/camera/OverworldCamera.gd @@ -7,30 +7,95 @@ const COLLISION_MARGIN:float = 0.3 @export var pivotOffset:Vector3 = Vector3(0, 1.2, 0) @export_category("Orbit") -@export var distance:float = 10.0 +@export var distance:float = 7.0 @export var minDistance:float = 1.5 -@export var orbitSensitivity:float = 120.0 +@export var orbitSensitivity:float = 144.0 +@export var orbitAcceleration:float = 600.0 @export var pitchMin:float = -10.0 @export var pitchMax:float = 70.0 +@export_category("Mouse") +@export var mouseSensitivity:float = 0.3 + +@export_category("Auto-center") +@export var autoReturnDelay:float = 2.0 +@export var autoReturnSpeed:float = 90.0 +@export var autoReturnPitch:float = 30.0 + @export_category("Collision") @export_flags_3d_physics var collisionMask:int = 1 +@export var distanceReturnSpeed:float = 5.0 var _yaw:float = 0.0 var _pitch:float = 30.0 +var _currentDistance:float = 0.0 +var _camVelocity:Vector2 = Vector2.ZERO +var _idleTimer:float = 0.0 +var _mouseDelta:Vector2 = Vector2.ZERO +var _rightMouseHeld:bool = false + +func _ready() -> void: + _currentDistance = distance + +func _input(event:InputEvent) -> void: + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT: + _rightMouseHeld = event.pressed + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if _rightMouseHeld else Input.MOUSE_MODE_VISIBLE + elif event is InputEventMouseMotion and _rightMouseHeld: + _mouseDelta += event.relative func _process(delta:float) -> void: if targetNode == null: return + var xMult:float = -1.0 if SETTINGS.invertCameraX else 1.0 + var yMult:float = 1.0 if SETTINGS.invertCameraY else -1.0 + var orbitInput:Vector2 = Input.get_vector( "camera_orbit_left", "camera_orbit_right", "camera_orbit_up", "camera_orbit_down" ) - var xMult:float = -1.0 if SETTINGS.invertCameraX else 1.0 - var yMult:float = 1.0 if SETTINGS.invertCameraY else -1.0 - _yaw += orbitInput.x * orbitSensitivity * delta * xMult - _pitch += orbitInput.y * orbitSensitivity * delta * yMult + + # Controller input with speed ramp; instant stop on release + var controllerActive:bool = orbitInput.length() > 0.01 + if controllerActive: + _camVelocity = _camVelocity.move_toward( + orbitInput * orbitSensitivity * SETTINGS.cameraSpeedController, orbitAcceleration * delta + ) + _yaw += _camVelocity.x * delta * xMult + _pitch += _camVelocity.y * delta * yMult + else: + _camVelocity = Vector2.ZERO + + # Right-click mouse drag + var mouseActive:bool = _mouseDelta.length_squared() > 0.0 + if mouseActive: + _yaw += _mouseDelta.x * mouseSensitivity * SETTINGS.cameraSpeedMouse * xMult + _pitch += _mouseDelta.y * mouseSensitivity * SETTINGS.cameraSpeedMouse * yMult + _mouseDelta = Vector2.ZERO + + # Auto-center behind player: only when player is moving and camera has been idle + var centerPressed:bool = Input.is_action_just_pressed("center_camera") + if centerPressed: + _idleTimer = autoReturnDelay + elif controllerActive or mouseActive: + _idleTimer = 0.0 + else: + var playerBody := targetNode as CharacterBody3D + if playerBody != null and playerBody.velocity.length_squared() > 0.1: + _idleTimer += delta + else: + _idleTimer = 0.0 + + if _idleTimer >= autoReturnDelay: + _pitch = move_toward(_pitch, autoReturnPitch, autoReturnSpeed * delta) + var behindYaw:float = rad_to_deg(atan2( + targetNode.global_transform.basis.z.x, + targetNode.global_transform.basis.z.z + )) + var yawDiff:float = fposmod(behindYaw - _yaw + 180.0, 360.0) - 180.0 + _yaw += move_toward(0.0, yawDiff, autoReturnSpeed * delta) + _pitch = clamp(_pitch, pitchMin, pitchMax) var pivot:Vector3 = targetNode.global_transform.origin + pivotOffset @@ -42,26 +107,25 @@ func _process(delta:float) -> void: cos(yawRad) * cos(pitchRad) ) - var desired:Vector3 = pivot + dir * distance - var actual:Vector3 = _resolveCollision(pivot, desired) + # Snap in instantly when terrain blocks; restore smoothly when clear + var collisionDist:float = _resolveDistance(pivot, dir) + if collisionDist < _currentDistance: + _currentDistance = collisionDist + else: + _currentDistance = move_toward(_currentDistance, distance, distanceReturnSpeed * delta) - global_transform.origin = actual + global_transform.origin = pivot + dir * _currentDistance look_at(pivot, Vector3.UP) -# Cast a ray from the pivot to the desired camera position. -# If terrain blocks the shot, pull the camera in to just in front of the hit. -func _resolveCollision(pivot:Vector3, desired:Vector3) -> Vector3: +# Ray from pivot outward; returns the safe camera distance along dir. +func _resolveDistance(pivot:Vector3, dir:Vector3) -> float: var space:PhysicsDirectSpaceState3D = get_world_3d().direct_space_state - var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(pivot, desired) + var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(pivot, pivot + dir * distance) query.collision_mask = collisionMask var hit:Dictionary = space.intersect_ray(query) if hit.is_empty(): - return desired + return distance - var hitDir:Vector3 = (desired - pivot).normalized() - var pulled:Vector3 = hit["position"] - hitDir * COLLISION_MARGIN - var pulledDist:float = (pulled - pivot).length() - if pulledDist < minDistance: - return pivot + hitDir * minDistance - return pulled + var hitDist:float = (hit["position"] - pivot).length() - COLLISION_MARGIN + return maxf(hitDist, minDistance) diff --git a/project.godot b/project.godot index ec057ec..ed5929f 100644 --- a/project.godot +++ b/project.godot @@ -158,6 +158,12 @@ tab_next={ , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null) ] } +center_camera={ +"deadzone": 0.5, +"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":71,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":false,"script":null) +] +} ui_accept={ "deadzone": 0.5, "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":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) diff --git a/scene/Settings.gd b/scene/Settings.gd index e99eb3c..6a4548c 100644 --- a/scene/Settings.gd +++ b/scene/Settings.gd @@ -2,3 +2,5 @@ extends Node var invertCameraX:bool = false var invertCameraY:bool = false +var cameraSpeedController:float = 1.0 +var cameraSpeedMouse:float = 1.0 diff --git a/ui/settings/SettingsMenu.gd b/ui/settings/SettingsMenu.gd index 89413c5..0917248 100644 --- a/ui/settings/SettingsMenu.gd +++ b/ui/settings/SettingsMenu.gd @@ -4,6 +4,8 @@ class_name SettingsMenu extends Control @export var tabControls:Array[Control] @export var checkInvertX:CheckBox @export var checkInvertY:CheckBox +@export var sliderControllerSpeed:HSlider +@export var sliderMouseSpeed:HSlider func _ready() -> void: tabs.tab_changed.connect(onTabChanged) @@ -11,6 +13,10 @@ func _ready() -> void: checkInvertY.button_pressed = SETTINGS.invertCameraY checkInvertX.toggled.connect(func(v:bool): SETTINGS.invertCameraX = v) checkInvertY.toggled.connect(func(v:bool): SETTINGS.invertCameraY = v) + sliderControllerSpeed.value = SETTINGS.cameraSpeedController + sliderMouseSpeed.value = SETTINGS.cameraSpeedMouse + sliderControllerSpeed.value_changed.connect(func(v:float): SETTINGS.cameraSpeedController = v) + sliderMouseSpeed.value_changed.connect(func(v:float): SETTINGS.cameraSpeedMouse = v) onTabChanged(tabs.current_tab) func _notification(what:int) -> void: diff --git a/ui/settings/SettingsMenu.tscn b/ui/settings/SettingsMenu.tscn index a9b2566..401bfe9 100644 --- a/ui/settings/SettingsMenu.tscn +++ b/ui/settings/SettingsMenu.tscn @@ -2,7 +2,7 @@ [ext_resource type="Script" uid="uid://efmr0xkbw1py" path="res://ui/settings/SettingsMenu.gd" id="1_4lnig"] -[node name="SettingsMenu" type="Control" node_paths=PackedStringArray("tabs", "tabControls", "checkInvertX", "checkInvertY")] +[node name="SettingsMenu" type="Control" node_paths=PackedStringArray("tabs", "tabControls", "checkInvertX", "checkInvertY", "sliderControllerSpeed", "sliderMouseSpeed")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -14,6 +14,8 @@ tabs = NodePath("VBoxContainer/TabBar") tabControls = [NodePath("VBoxContainer/ScrollContainer/LabelGameplay"), NodePath("VBoxContainer/ScrollContainer/LabelSound"), NodePath("VBoxContainer/ScrollContainer/LabelGraphics"), NodePath("VBoxContainer/ScrollContainer/PanelControls")] checkInvertX = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertX") checkInvertY = NodePath("VBoxContainer/ScrollContainer/PanelControls/CheckInvertY") +sliderControllerSpeed = NodePath("VBoxContainer/ScrollContainer/PanelControls/SliderControllerSpeed") +sliderMouseSpeed = NodePath("VBoxContainer/ScrollContainer/PanelControls/SliderMouseSpeed") [node name="VBoxContainer" type="VBoxContainer" parent="."] layout_mode = 1 @@ -68,3 +70,31 @@ text = "Invert Camera X" layout_mode = 2 focus_mode = 2 text = "Invert Camera Y" + +[node name="LabelCameraSpeed" type="Label" parent="VBoxContainer/ScrollContainer/PanelControls"] +layout_mode = 2 +text = "Camera Speed" + +[node name="LabelControllerSpeed" type="Label" parent="VBoxContainer/ScrollContainer/PanelControls"] +layout_mode = 2 +text = "Controller" + +[node name="SliderControllerSpeed" type="HSlider" parent="VBoxContainer/ScrollContainer/PanelControls"] +layout_mode = 2 +focus_mode = 2 +min_value = 0.25 +max_value = 2.0 +step = 0.25 +value = 1.0 + +[node name="LabelMouseSpeed" type="Label" parent="VBoxContainer/ScrollContainer/PanelControls"] +layout_mode = 2 +text = "Mouse" + +[node name="SliderMouseSpeed" type="HSlider" parent="VBoxContainer/ScrollContainer/PanelControls"] +layout_mode = 2 +focus_mode = 2 +min_value = 0.25 +max_value = 2.0 +step = 0.25 +value = 1.0