175 lines
7.3 KiB
GDScript
175 lines
7.3 KiB
GDScript
extends Camera3D
|
|
class_name RTSCamera3D
|
|
|
|
@export var dragging_enabled: bool = true
|
|
@export var panning_enabled: bool = true
|
|
@export var edge_panning_enabled: bool = true
|
|
@export var rotating_enabled: bool = true
|
|
@export var zooming_enabled: bool = true
|
|
|
|
@export var dragging_intersect_plane_normal: Vector3 = Vector3.UP
|
|
@export var dragging_intersect_plane_distance: float = 0.0
|
|
|
|
@export_range(0.0, 1000.0, 10.0) var pan_speed: float = 20.0
|
|
@export_range(0.0, 1000.0, 10.0) var edge_pan_speed: float = 20.0
|
|
@export_range(0, 1.0, 0.01) var edge_trigger_percentage: float = 0.005
|
|
|
|
@export var lock_onto_position: bool = false
|
|
@export var lockon_position: Vector3 = Vector3.ZERO
|
|
|
|
@export var rotate_min_angle: float = 15.0
|
|
@export var rotate_max_angle: float = 80.0
|
|
|
|
@export var min_camera_distance: float = 1.0
|
|
@export var max_camera_distance: float = 50.0
|
|
|
|
@export var bounding_box: Vector2 = Vector2(100.0, 100.0)
|
|
@export var world_center: Vector2 = Vector2.ZERO
|
|
|
|
var dragging: bool = false
|
|
var drag_cam_position: Vector3 = Vector3.ZERO
|
|
var drag_2d_position: Vector2 = Vector2.ZERO
|
|
|
|
var rotating: bool = false
|
|
var orbit_angles: Vector2 = Vector2.ZERO #(deg_to_rad(-60.0), 0.0)
|
|
var temp_orbit_angles: Vector2 = Vector2.ZERO
|
|
var orbit_distance: float = 20.0
|
|
var orbitPosition: Vector3 = Vector3.ZERO
|
|
var rotate_3d_position: Vector3 = Vector3.ZERO
|
|
var rotate_2d_position: Vector2 = Vector2.ZERO
|
|
var rotate_2d_position_old: Vector2 = Vector2.ZERO
|
|
|
|
var panning_move_vector: Vector3 = Vector3.ZERO
|
|
var edge_panning_move_vector: Vector3 = Vector3.ZERO
|
|
|
|
var zoom_3d_position: Vector3 = Vector3.ZERO
|
|
var camera_distance: float = 0.0
|
|
var camera_distance_old: float = 0.0
|
|
var _camera_distance: float = 30.0
|
|
|
|
# TODO: Fix look_at() errors
|
|
|
|
func _ready():
|
|
await get_tree().process_frame
|
|
#set correct camera distance
|
|
rotate_3d_position = get_3d_pos(get_viewport().size / 2.0)
|
|
look_at(lockon_position)
|
|
if lock_onto_position:
|
|
rotate_3d_position = lockon_position
|
|
camera_distance = rotate_3d_position.distance_to(global_position)
|
|
camera_distance_old = camera_distance
|
|
orbit_angles = Vector2(global_rotation.x, global_rotation.y)
|
|
|
|
# This is to prevent edge_panning from panning when
|
|
# the mouse never entered the viewport before
|
|
var mouse_has_entered_once: bool = false
|
|
|
|
func _set_zoom_level(value: float) -> void:
|
|
_camera_distance = clamp(value, min_camera_distance, max_camera_distance)
|
|
|
|
var tween = get_tree().create_tween()
|
|
tween.tween_property(
|
|
self,
|
|
"camera_distance",
|
|
_camera_distance,
|
|
0.15
|
|
)
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
var pos_3d = get_3d_pos(get_viewport().get_mouse_position())
|
|
if pos_3d:
|
|
var pos_3d_rounded = "%.2f, %.2f, %.2f" % [pos_3d.x, pos_3d.y, pos_3d.z]
|
|
|
|
if event.is_action_pressed("drag_camera") and not rotating and not lock_onto_position:
|
|
_camera_distance = camera_distance
|
|
drag_2d_position = get_viewport().get_mouse_position()
|
|
drag_cam_position = position
|
|
dragging = true
|
|
elif event.is_action_released("drag_camera"):
|
|
camera_distance = _camera_distance
|
|
_camera_distance = 0
|
|
dragging = false
|
|
|
|
if event.is_action_pressed("rotate_camera") and not dragging:
|
|
rotate_3d_position = get_3d_pos(get_viewport().size / 2.0)
|
|
if lock_onto_position:
|
|
look_at(lockon_position)
|
|
rotate_3d_position = lockon_position
|
|
rotate_2d_position = get_viewport().get_mouse_position()
|
|
rotate_2d_position_old = get_viewport().get_mouse_position()
|
|
temp_orbit_angles = orbit_angles
|
|
rotating = true
|
|
elif event.is_action_released("rotate_camera"):
|
|
orbit_angles = temp_orbit_angles
|
|
rotating = false
|
|
|
|
if zooming_enabled:
|
|
if event.is_action_pressed("zoom_camera_in") and not dragging:
|
|
_set_zoom_level(camera_distance - 1)
|
|
zoom_3d_position = get_3d_pos(get_viewport().size / 2.0)
|
|
elif event.is_action_pressed("zoom_camera_out") and not dragging:
|
|
_set_zoom_level(camera_distance + 1)
|
|
zoom_3d_position = get_3d_pos(get_viewport().size / 2.0)
|
|
|
|
func get_3d_pos(position2D: Vector2):
|
|
return Plane(dragging_intersect_plane_normal, dragging_intersect_plane_distance).intersects_ray(project_ray_origin(position2D), project_ray_normal(position2D))
|
|
|
|
func _notification(what: int) -> void:
|
|
match what:
|
|
NOTIFICATION_WM_MOUSE_ENTER:
|
|
mouse_has_entered_once = true
|
|
|
|
func _process(delta: float) -> void:
|
|
panning_move_vector = Vector3.ZERO
|
|
edge_panning_move_vector = Vector3.ZERO
|
|
var mouse_position: Vector2 = get_viewport().get_mouse_position()
|
|
|
|
if panning_enabled and not lock_onto_position:
|
|
var camera_move_vector = Vector2(Input.get_action_strength("camera_right") - Input.get_action_strength("camera_left"), Input.get_action_strength("camera_down") - Input.get_action_strength("camera_up"))
|
|
panning_move_vector = Vector3(camera_move_vector.x, 0.0, camera_move_vector.y).rotated(Vector3.UP, rotation.y)
|
|
|
|
if edge_panning_enabled and mouse_has_entered_once and not lock_onto_position:
|
|
var percentage = mouse_position / Vector2(get_viewport().size)
|
|
if percentage.x <= edge_trigger_percentage:
|
|
edge_panning_move_vector = Vector3(-1, 0.0, 0.0).rotated(Vector3.UP, rotation.y)
|
|
elif percentage.x >= 1.0 - edge_trigger_percentage:
|
|
edge_panning_move_vector = Vector3(1, 0.0, 0.0).rotated(Vector3.UP, rotation.y)
|
|
elif percentage.y <= edge_trigger_percentage:
|
|
edge_panning_move_vector = Vector3(0.0, 0.0, -1).rotated(Vector3.UP, rotation.y)
|
|
elif percentage.y >= 1.0 - edge_trigger_percentage:
|
|
edge_panning_move_vector = Vector3(0.0, 0.0, 1).rotated(Vector3.UP, rotation.y)
|
|
|
|
# TODO: When reaching the limits, reset the rotation diff
|
|
# Currently: If you rotate up/down and go out of limits, you have to move back all the overshoot
|
|
# to move the camera back again
|
|
if rotating_enabled and rotating and not dragging:
|
|
rotate_2d_position = get_viewport().get_mouse_position()
|
|
var mouse_diff = rotate_2d_position - rotate_2d_position_old
|
|
temp_orbit_angles = Vector2(orbit_angles.x - mouse_diff.y * 0.01, orbit_angles.y - mouse_diff.x * 0.01)
|
|
temp_orbit_angles.x = clamp(temp_orbit_angles.x, -deg_to_rad(rotate_max_angle), -deg_to_rad(rotate_min_angle))
|
|
var lookRotation = Quaternion.from_euler(Vector3(temp_orbit_angles.x, temp_orbit_angles.y, 0.0))
|
|
var lookDirection = lookRotation * Vector3.FORWARD
|
|
var lookPosition = rotate_3d_position - lookDirection * camera_distance
|
|
look_at_from_position(lookPosition, rotate_3d_position)
|
|
|
|
if not is_equal_approx(camera_distance, camera_distance_old) and not rotating:
|
|
var lookRotation = Quaternion.from_euler(Vector3(orbit_angles.x, orbit_angles.y, 0.0))
|
|
var lookDirection = lookRotation * Vector3.FORWARD
|
|
var lookPosition = rotate_3d_position - lookDirection * camera_distance
|
|
look_at_from_position(lookPosition, rotate_3d_position)
|
|
camera_distance_old = camera_distance
|
|
|
|
# TODO: Always apply zooming and figure out how to reset initial drag position
|
|
# Probably just "stop" and start a new drag when zoom is different?
|
|
# Or always stop/start a new drag?
|
|
if dragging_enabled and dragging and not rotating and not lock_onto_position:
|
|
var new_3d_pos = get_3d_pos(mouse_position)
|
|
if new_3d_pos:
|
|
var drag_world_position = get_3d_pos(drag_2d_position)
|
|
if drag_world_position:
|
|
var mouse_diff = drag_world_position - new_3d_pos
|
|
var new_cam_pos = drag_cam_position + mouse_diff
|
|
position = new_cam_pos
|
|
rotate_3d_position = get_3d_pos(get_viewport().size / 2.0)
|
|
else:
|
|
position += ((panning_move_vector * pan_speed) + (edge_panning_move_vector * edge_pan_speed)) * delta
|