diff --git a/editor.scn b/editor.scn index f981287..257901e 100644 --- a/editor.scn +++ b/editor.scn @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc97a5eafcfcdc41b4b8f30c3366395aadcb76856608319e4e96b3af9245ee45 -size 5706 +oid sha256:392d7f7fd33af54d9027b2455b59f74c54b1629a48ba6a8cab3ab2584c9a9d38 +size 6198 diff --git a/player_controller.gd b/player_controller.gd index 9568283..cb556ca 100644 --- a/player_controller.gd +++ b/player_controller.gd @@ -1,5 +1,28 @@ class_name PlayerController extends Node3D +## +## Supported selection input devices. +## +enum SelectMode { + NONE, + MOUSE, +} + +## +## The device being used for selection has changed. +## +signal select_mode_changed(mode: SelectMode) + +## +## Selection of a point on screenspace has started happening. +## +signal selection_started() + +## +## Selection of a point on screenspace has stopped happening. +## +signal selection_stopped() + const _BACKWARD := "player_controller_backward" const _FORWARD := "player_controller_forward" @@ -20,29 +43,54 @@ const _ROTATE_SPEED_BASE := 5.0 const _TRANSFORM_DELTA := 10.0 +@export +var _camera: Camera3D = null + +var _cursor_point := Vector2.ZERO + var _is_drag_panning := false +var _is_selecting := false + +var _select_mode := SelectMode.NONE + +@export +var _selection_area: Control = null + @onready var _target_position := self.position @onready var _target_orientation := self.global_rotation.y +## +## Smoothness applies to the interpolation toward rotation and movement target values. +## @export var movement_smoothing := 0.5 +## +## Whether or not player movement input processed by the controller should be ignored. +## +var frozen := false + func _input(event: InputEvent) -> void: if event is InputEventMouseButton: match event.button_index: MOUSE_BUTTON_MIDDLE: - self._is_drag_panning = event.is_pressed() - Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if self._is_drag_panning else Input.MOUSE_MODE_VISIBLE + self._is_drag_panning = event.is_pressed() and not(self.frozen) - MOUSE_BUTTON_WHEEL_DOWN: - pass + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if\ + self._is_drag_panning else Input.MOUSE_MODE_VISIBLE - MOUSE_BUTTON_WHEEL_UP: - pass + MOUSE_BUTTON_LEFT: + self._is_selecting = event.is_pressed() + + if self._is_selecting: + self.selection_started.emit() + + else: + self.selection_stopped.emit() return @@ -64,29 +112,95 @@ func _input(event: InputEvent) -> void: if event is InputEventScreenDrag: return + if event is InputEventScreenTouch: + return + func _process(delta: float) -> void: - var global_basis := self.global_transform.basis - var camera_settings := GameSettings.camera_settings - var delta_speed := camera_settings.movement_speed_modifier * _MOVE_SPEED_BASE_MODIFIER * delta + if not(self.frozen): + var global_basis := self.global_transform.basis + var camera_settings := GameSettings.camera_settings - self._target_position += delta_speed.y * (-global_basis.z) *\ - (Input.get_action_strength(_FORWARD) - Input.get_action_strength(_BACKWARD)) *\ - (-1.0 if camera_settings.is_y_inverted else 1.0) + var delta_speed :=\ + camera_settings.movement_speed_modifier * _MOVE_SPEED_BASE_MODIFIER * delta - self._target_position += delta_speed.x * (-global_basis.x) *\ - (Input.get_action_strength(_LEFT) - Input.get_action_strength(_RIGHT)) *\ - (-1.0 if camera_settings.is_y_inverted else 1.0) + self._target_position += delta_speed.y * (-global_basis.z) *\ + (Input.get_action_strength(_FORWARD) - Input.get_action_strength(_BACKWARD)) *\ + (-1.0 if camera_settings.is_y_inverted else 1.0) - self._target_orientation += (Input.get_action_strength(_ROTATE_CCW) -\ - Input.get_action_strength(_ROTATE_CW)) * _ROTATE_SPEED_BASE *\ - camera_settings.rotation_speed_modifier * delta + self._target_position += delta_speed.x * (-global_basis.x) *\ + (Input.get_action_strength(_LEFT) - Input.get_action_strength(_RIGHT)) *\ + (-1.0 if camera_settings.is_y_inverted else 1.0) - self.global_transform = self.global_transform.interpolate_with( - Transform3D(Basis(Vector3.UP, self._target_orientation), self._target_position), - delta * _TRANSFORM_DELTA * self.movement_smoothing) + self._target_orientation += (Input.get_action_strength(_ROTATE_CCW) -\ + Input.get_action_strength(_ROTATE_CW)) * _ROTATE_SPEED_BASE *\ + camera_settings.rotation_speed_modifier * delta + + self.global_transform = self.global_transform.interpolate_with( + Transform3D(Basis(Vector3.UP, self._target_orientation), self._target_position), + delta * _TRANSFORM_DELTA * self.movement_smoothing) + + match self._select_mode: + SelectMode.NONE: + self._cursor_point = self.get_viewport().size / 2.0 + + SelectMode.MOUSE: + self._cursor_point = self.get_viewport().get_mouse_position() + +func _ready() -> void: + if self._selection_area != null: + self._selection_area.mouse_entered.connect(func () -> void: + if self._select_mode != SelectMode.MOUSE: + self._select_mode = SelectMode.MOUSE + + self.select_mode_changed.emit(SelectMode.MOUSE)) + + self._selection_area.mouse_exited.connect(func () -> void: + if self._select_mode != SelectMode.NONE: + self._select_mode = SelectMode.NONE + + self.select_mode_changed.emit(SelectMode.NONE)) ## +## Returns [code]true[/code] if the player controller is currently selecting a location on the +## screen, otherwise [code]false[/code]. ## +## *Note* that it is discouraged that this be continuously polled for single-fire events. Instead, +## see [signal selection_started] and [signal selection_stopped]. ## -func is_drag_panning() -> bool: - return self._is_drag_panning +func is_selecting() -> bool: + return self._is_selecting + +## +## Returns the position of the selection cursor with respect to the select current mode being used. +## +## When a mouse input device is being used, this will be its location in screen coordinates. For +## touchscreen interactions, this will be the location of an active gesture. Finally, for all other +## states - including gamepad - this will be the middle of screenspace at all times. +## +func get_cursor_point() -> Vector2: + return self._cursor_point + +## +## Returns the current [enum SelectMode] being used for selections. +## +## The general use-case for this function is to peek through the player controller abstraction and +## see what supported hardware interface is currently being used to select in the world. This is +## useful for handling input-specific behaviors, such as showing a mouse cursor when a mouse input +## device is being used. +## +func get_select_mode() -> int: + return self._select_mode + +## +## Converts and returns the screen coordinates in [code]screen_point[/code] into 2D worldspace +## coordinates relative to an infinite ground plane at a y offset of [code]0.0[/code]. +## +func screen_to_plane(screen_point: Vector2): + if self._camera != null: + var plane_target = Plane(Vector3.UP, 0.0).intersects_ray( + self._camera.global_position, self._camera.project_ray_normal(screen_point)) + + if plane_target is Vector3: + return Vector2(plane_target.x, plane_target.z) + + return null