Improved third person camera
This commit is contained in:
parent
4e632608e1
commit
ffe3142937
@ -7,6 +7,7 @@ var account: Account
|
|||||||
var camera: Camera
|
var camera: Camera
|
||||||
var player: Player
|
var player: Player
|
||||||
var players: PlayerManager
|
var players: PlayerManager
|
||||||
|
var terrain: Terrain3D
|
||||||
var instance_id: int
|
var instance_id: int
|
||||||
var interacting_with_ui: bool
|
var interacting_with_ui: bool
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=34 format=3 uid="uid://b40y7iuskv1ar"]
|
[gd_scene load_steps=35 format=3 uid="uid://b40y7iuskv1ar"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://Main.gd" id="1_cw3ws"]
|
[ext_resource type="Script" path="res://Main.gd" id="1_cw3ws"]
|
||||||
[ext_resource type="Script" path="res://network/Client.gd" id="2_8hxcx"]
|
[ext_resource type="Script" path="res://network/Client.gd" id="2_8hxcx"]
|
||||||
@ -18,6 +18,7 @@
|
|||||||
[ext_resource type="Script" path="res://world/generator/Generator.gd" id="15_25nmg"]
|
[ext_resource type="Script" path="res://world/generator/Generator.gd" id="15_25nmg"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cb2t7bvvf3gwh" path="res://enemy/slime/Slime.tscn" id="15_hgl78"]
|
[ext_resource type="PackedScene" uid="uid://cb2t7bvvf3gwh" path="res://enemy/slime/Slime.tscn" id="15_hgl78"]
|
||||||
[ext_resource type="Script" path="res://world/PlayerManager.gd" id="16_dp6bj"]
|
[ext_resource type="Script" path="res://world/PlayerManager.gd" id="16_dp6bj"]
|
||||||
|
[ext_resource type="Script" path="res://world/Terrain.gd" id="16_icmfi"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dagn5bf7ou3sd" path="res://ui/UI.tscn" id="17_43qhq"]
|
[ext_resource type="PackedScene" uid="uid://dagn5bf7ou3sd" path="res://ui/UI.tscn" id="17_43qhq"]
|
||||||
[ext_resource type="FastNoiseLite" uid="uid://d3f4lk8q04haa" path="res://world/trees/TreeNoise.tres" id="19_ctwmw"]
|
[ext_resource type="FastNoiseLite" uid="uid://d3f4lk8q04haa" path="res://world/trees/TreeNoise.tres" id="19_ctwmw"]
|
||||||
[ext_resource type="Script" path="res://world/generator/TreeGenerator.gd" id="19_kcwnm"]
|
[ext_resource type="Script" path="res://world/generator/TreeGenerator.gd" id="19_kcwnm"]
|
||||||
@ -32,13 +33,13 @@
|
|||||||
[ext_resource type="Terrain3DMaterial" uid="uid://nhcs8ekjedbb" path="res://world/terrain/Material.tres" id="31_of12o"]
|
[ext_resource type="Terrain3DMaterial" uid="uid://nhcs8ekjedbb" path="res://world/terrain/Material.tres" id="31_of12o"]
|
||||||
[ext_resource type="Terrain3DTextureList" uid="uid://cpq0eq7wr08kf" path="res://world/terrain/TextureList.tres" id="32_d5ywf"]
|
[ext_resource type="Terrain3DTextureList" uid="uid://cpq0eq7wr08kf" path="res://world/terrain/TextureList.tres" id="32_d5ywf"]
|
||||||
|
|
||||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_xy4ll"]
|
|
||||||
seed = 300
|
|
||||||
|
|
||||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_yp2fx"]
|
[sub_resource type="FastNoiseLite" id="FastNoiseLite_yp2fx"]
|
||||||
seed = 100
|
seed = 100
|
||||||
frequency = 0.1
|
frequency = 0.1
|
||||||
|
|
||||||
|
[sub_resource type="FastNoiseLite" id="FastNoiseLite_xy4ll"]
|
||||||
|
seed = 300
|
||||||
|
|
||||||
[node name="Main" type="Node" node_paths=PackedStringArray("ui")]
|
[node name="Main" type="Node" node_paths=PackedStringArray("ui")]
|
||||||
script = ExtResource("1_cw3ws")
|
script = ExtResource("1_cw3ws")
|
||||||
ui = NodePath("UI")
|
ui = NodePath("UI")
|
||||||
@ -108,10 +109,22 @@ autostart = true
|
|||||||
|
|
||||||
[node name="World" type="Node3D" parent="."]
|
[node name="World" type="Node3D" parent="."]
|
||||||
|
|
||||||
[node name="Terrain3D" type="Terrain3D" parent="World"]
|
[node name="Terrain" type="Terrain3D" parent="World"]
|
||||||
storage = ExtResource("30_bvq0o")
|
storage = ExtResource("30_bvq0o")
|
||||||
material = ExtResource("31_of12o")
|
material = ExtResource("31_of12o")
|
||||||
texture_list = ExtResource("32_d5ywf")
|
texture_list = ExtResource("32_d5ywf")
|
||||||
|
script = ExtResource("16_icmfi")
|
||||||
|
|
||||||
|
[node name="Enemies" type="Node3D" parent="World"]
|
||||||
|
visible = false
|
||||||
|
script = ExtResource("15_25nmg")
|
||||||
|
scene = ExtResource("15_hgl78")
|
||||||
|
noise = SubResource("FastNoiseLite_yp2fx")
|
||||||
|
size_x = 100.0
|
||||||
|
size_z = 100.0
|
||||||
|
step = 2.0
|
||||||
|
density = 0.3
|
||||||
|
tilt = 0.15
|
||||||
|
|
||||||
[node name="Houses" type="Node3D" parent="World"]
|
[node name="Houses" type="Node3D" parent="World"]
|
||||||
visible = false
|
visible = false
|
||||||
@ -139,18 +152,7 @@ scale_min = 0.6
|
|||||||
scale_max = 1.1
|
scale_max = 1.1
|
||||||
tilt = 0.15
|
tilt = 0.15
|
||||||
|
|
||||||
[node name="Enemies" type="Node3D" parent="World"]
|
[node name="Camera" parent="World" instance=ExtResource("12_aljdh")]
|
||||||
visible = false
|
|
||||||
script = ExtResource("15_25nmg")
|
|
||||||
scene = ExtResource("15_hgl78")
|
|
||||||
noise = SubResource("FastNoiseLite_yp2fx")
|
|
||||||
size_x = 100.0
|
|
||||||
size_z = 100.0
|
|
||||||
step = 2.0
|
|
||||||
density = 0.3
|
|
||||||
tilt = 0.15
|
|
||||||
|
|
||||||
[node name="Follow" parent="World" instance=ExtResource("12_aljdh")]
|
|
||||||
|
|
||||||
[node name="Environment" type="WorldEnvironment" parent="World"]
|
[node name="Environment" type="WorldEnvironment" parent="World"]
|
||||||
environment = ExtResource("14_kuej8")
|
environment = ExtResource("14_kuej8")
|
||||||
|
@ -1,47 +1,141 @@
|
|||||||
class_name Camera
|
class_name Camera
|
||||||
extends Camera3D
|
extends Camera3D
|
||||||
|
|
||||||
|
@export var center_offset := Vector3(0, 1.4, 0)
|
||||||
|
var center: Vector3
|
||||||
|
var look_offset: Vector3
|
||||||
|
var target_position: Vector3
|
||||||
|
|
||||||
|
@export_group("Collision")
|
||||||
|
@export var min_height_above_ground: float = 0
|
||||||
|
@export_flags_3d_physics var collision_mask := 1
|
||||||
|
var occlusion: Dictionary
|
||||||
|
var occlusion_distance := 0.0
|
||||||
|
|
||||||
|
@export_group("Rotation")
|
||||||
|
@export var sensitivity: float = 0.2
|
||||||
|
@export var min_angle_x: float = -80.0
|
||||||
|
@export var max_angle_x: float = 45.0
|
||||||
|
var angle_x: float
|
||||||
|
var angle_y: float
|
||||||
|
var look_enabled: bool
|
||||||
|
|
||||||
@export_group("Zoom")
|
@export_group("Zoom")
|
||||||
@export var zoom_speed := 0.5
|
@export var zoom_speed := 0.5
|
||||||
@export var zoom_interpolation := 10.0
|
@export var zoom_interpolation := 10.0
|
||||||
@export var zoom_min := 2.0
|
@export var zoom_min := 2.5
|
||||||
@export var zoom_max := 8.0
|
@export var zoom_max := 8.0
|
||||||
|
var distance: float
|
||||||
var target_distance: float
|
var target_distance: float
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
target_distance = position.z
|
distance = 7.5
|
||||||
|
target_distance = distance
|
||||||
|
sensitivity = deg_to_rad(sensitivity)
|
||||||
|
min_angle_x = deg_to_rad(min_angle_x)
|
||||||
|
max_angle_x = deg_to_rad(max_angle_x)
|
||||||
Global.camera = self
|
Global.camera = self
|
||||||
|
update_look_offset()
|
||||||
|
|
||||||
|
func _unhandled_input(event):
|
||||||
|
if !event.is_action("look"):
|
||||||
|
return
|
||||||
|
|
||||||
|
match event.is_pressed():
|
||||||
|
true:
|
||||||
|
DisplayServer.mouse_set_mode(DisplayServer.MOUSE_MODE_CAPTURED)
|
||||||
|
look_enabled = true
|
||||||
|
false:
|
||||||
|
DisplayServer.mouse_set_mode(DisplayServer.MOUSE_MODE_VISIBLE)
|
||||||
|
look_enabled = false
|
||||||
|
|
||||||
func _input(event):
|
func _input(event):
|
||||||
if event.is_action_pressed("zoom_in", true):
|
if event.is_action_pressed("zoom_in", true):
|
||||||
target_distance -= zoom_speed
|
target_distance -= zoom_speed
|
||||||
on_distance_changed()
|
on_distance_changed()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
|
||||||
if event.is_action_pressed("zoom_out", true):
|
if event.is_action_pressed("zoom_out", true):
|
||||||
target_distance += zoom_speed
|
target_distance += zoom_speed
|
||||||
on_distance_changed()
|
on_distance_changed()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
|
||||||
|
if !look_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not event is InputEventMouseMotion:
|
||||||
|
return
|
||||||
|
|
||||||
|
angle_x -= event.relative.y * sensitivity
|
||||||
|
angle_y -= event.relative.x * sensitivity
|
||||||
|
angle_x = clampf(angle_x, min_angle_x, max_angle_x)
|
||||||
|
|
||||||
|
update_look_offset()
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
if !Global.player:
|
||||||
|
return
|
||||||
|
|
||||||
|
center = Global.player.position + center_offset
|
||||||
|
distance = Math.dampf(distance, target_distance, zoom_interpolation * delta)
|
||||||
|
target_position = center + look_offset * distance
|
||||||
|
|
||||||
|
if min_height_above_ground > 0:
|
||||||
|
var result := check_terrain()
|
||||||
|
|
||||||
|
if result && target_position.y - result.position.y < min_height_above_ground:
|
||||||
|
target_position.y = result.position.y + min_height_above_ground
|
||||||
|
|
||||||
|
global_position = target_position
|
||||||
|
check_occlusion()
|
||||||
|
|
||||||
|
if occlusion_distance < distance:
|
||||||
|
global_position = center + look_offset * occlusion_distance
|
||||||
|
|
||||||
|
look_at(center)
|
||||||
|
|
||||||
func on_distance_changed():
|
func on_distance_changed():
|
||||||
target_distance = clampf(target_distance, zoom_min, zoom_max)
|
target_distance = clampf(target_distance, zoom_min, zoom_max)
|
||||||
Global.camera_attributes.dof_blur_far_distance = target_distance + 1.0
|
Global.camera_attributes.dof_blur_far_distance = target_distance + 1.0
|
||||||
|
|
||||||
func is_below_terrain():
|
func update_look_offset():
|
||||||
return %CameraRays.occlusion_point.y > global_position.y
|
look_offset = get_offset(Vector3.BACK)
|
||||||
|
|
||||||
func _process(delta):
|
func get_offset(vec: Vector3) -> Vector3:
|
||||||
var distance := target_distance
|
vec = vec.rotated(Vector3.RIGHT, angle_x)
|
||||||
|
vec = vec.rotated(Vector3.UP, angle_y)
|
||||||
|
return vec
|
||||||
|
|
||||||
if %CameraRays.occlusion_distance < target_distance:
|
func check_terrain() -> Dictionary:
|
||||||
distance = %CameraRays.occlusion_distance - 0.5
|
var space := get_world_3d().direct_space_state
|
||||||
|
var from := Vector3(target_position.x, 100, target_position.z)
|
||||||
|
var to := Vector3(target_position.x, -100, target_position.z)
|
||||||
|
var query := PhysicsRayQueryParameters3D.create(from, to, collision_mask)
|
||||||
|
return space.intersect_ray(query)
|
||||||
|
|
||||||
if is_below_terrain():
|
func check_occlusion():
|
||||||
position.z = distance
|
occlusion_distance = INF
|
||||||
return
|
var rect := get_viewport().get_visible_rect()
|
||||||
|
var space := get_world_3d().direct_space_state
|
||||||
|
send_ray_to_screen(rect.position, space)
|
||||||
|
send_ray_to_screen(Vector2(rect.end.x, rect.position.y), space)
|
||||||
|
send_ray_to_screen(rect.get_center(), space)
|
||||||
|
send_ray_to_screen(Vector2(rect.position.x, rect.end.y), space)
|
||||||
|
send_ray_to_screen(rect.end, space)
|
||||||
|
|
||||||
if abs(distance - position.z) < 0.01:
|
func send_ray_to_screen(point: Vector2, space: PhysicsDirectSpaceState3D):
|
||||||
|
var screen_position := project_position(point, near)
|
||||||
|
var query := PhysicsRayQueryParameters3D.create(center, screen_position, collision_mask)
|
||||||
|
var hit := space.intersect_ray(query)
|
||||||
|
|
||||||
|
if !hit:
|
||||||
return
|
return
|
||||||
|
|
||||||
position.z = Math.dampf(position.z, distance, zoom_interpolation * delta)
|
var hit_point := hit.position as Vector3
|
||||||
|
var hit_distance := (hit_point - center).length()
|
||||||
|
|
||||||
|
if hit_distance < occlusion_distance:
|
||||||
|
occlusion = hit
|
||||||
|
occlusion_distance = hit_distance
|
||||||
|
@ -1,28 +1,12 @@
|
|||||||
[gd_scene load_steps=6 format=3 uid="uid://cpdoq0oh84mfw"]
|
[gd_scene load_steps=3 format=3 uid="uid://cpdoq0oh84mfw"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://camera/FollowPlayer.gd" id="1_48rtd"]
|
|
||||||
[ext_resource type="Script" path="res://camera/CameraPivot.gd" id="2_dylfm"]
|
|
||||||
[ext_resource type="Script" path="res://camera/Camera.gd" id="2_pwdc2"]
|
[ext_resource type="Script" path="res://camera/Camera.gd" id="2_pwdc2"]
|
||||||
[ext_resource type="CameraAttributesPractical" uid="uid://b835orxyqq6w5" path="res://camera/CameraPractical.tres" id="3_olar0"]
|
[ext_resource type="CameraAttributesPractical" uid="uid://b835orxyqq6w5" path="res://camera/CameraPractical.tres" id="3_olar0"]
|
||||||
[ext_resource type="Script" path="res://camera/CameraRays.gd" id="5_5b07p"]
|
|
||||||
|
|
||||||
[node name="Follow" type="Marker3D"]
|
[node name="Camera" type="Camera3D"]
|
||||||
script = ExtResource("1_48rtd")
|
|
||||||
|
|
||||||
[node name="Pivot" type="Marker3D" parent="."]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4, 0)
|
|
||||||
script = ExtResource("2_dylfm")
|
|
||||||
sensitivity = 0.2
|
|
||||||
|
|
||||||
[node name="Camera" type="Camera3D" parent="Pivot"]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7.5)
|
|
||||||
attributes = ExtResource("3_olar0")
|
attributes = ExtResource("3_olar0")
|
||||||
current = true
|
current = true
|
||||||
fov = 45.0
|
fov = 45.0
|
||||||
size = 10.0
|
size = 10.0
|
||||||
far = 120.0
|
far = 120.0
|
||||||
script = ExtResource("2_pwdc2")
|
script = ExtResource("2_pwdc2")
|
||||||
|
|
||||||
[node name="CameraRays" type="Node3D" parent="Pivot"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
script = ExtResource("5_5b07p")
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
|
|
||||||
@export var sensitivity: float
|
|
||||||
@export var min_rotation_x: float = -80.0
|
|
||||||
@export var max_rotation_x: float = 25.0
|
|
||||||
|
|
||||||
var enabled: bool
|
|
||||||
|
|
||||||
func _ready():
|
|
||||||
assert(rotation_order == EULER_ORDER_YXZ)
|
|
||||||
min_rotation_x = deg_to_rad(min_rotation_x)
|
|
||||||
max_rotation_x = deg_to_rad(max_rotation_x)
|
|
||||||
|
|
||||||
func _unhandled_input(event):
|
|
||||||
if event.is_action_pressed("look"):
|
|
||||||
DisplayServer.mouse_set_mode(DisplayServer.MOUSE_MODE_CAPTURED)
|
|
||||||
enabled = true
|
|
||||||
elif event.is_action_released("look"):
|
|
||||||
DisplayServer.mouse_set_mode(DisplayServer.MOUSE_MODE_VISIBLE)
|
|
||||||
enabled = false
|
|
||||||
|
|
||||||
func _input(event):
|
|
||||||
if !enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not event is InputEventMouseMotion:
|
|
||||||
return
|
|
||||||
|
|
||||||
rotation.x += deg_to_rad(-event.relative.y * sensitivity)
|
|
||||||
rotation.y += deg_to_rad(-event.relative.x * sensitivity)
|
|
||||||
|
|
||||||
rotation.x = clampf(rotation.x, min_rotation_x, max_rotation_x)
|
|
@ -1,38 +0,0 @@
|
|||||||
class_name CameraRays
|
|
||||||
extends Node3D
|
|
||||||
|
|
||||||
@export_flags_3d_physics var collision_mask := 1
|
|
||||||
|
|
||||||
var occlusion_distance: float = 1000
|
|
||||||
var occlusion_point: Vector3
|
|
||||||
var pivot: Node3D
|
|
||||||
|
|
||||||
func _ready():
|
|
||||||
pivot = get_parent()
|
|
||||||
|
|
||||||
func _physics_process(_delta):
|
|
||||||
occlusion_distance = Global.camera.far
|
|
||||||
|
|
||||||
var corner: Vector2 = get_viewport().get_visible_rect().size
|
|
||||||
var space_state = get_world_3d().direct_space_state
|
|
||||||
|
|
||||||
send_ray(space_state, Vector2(0, 0))
|
|
||||||
send_ray(space_state, Vector2(corner.x, 0))
|
|
||||||
send_ray(space_state, Vector2(0, corner.y))
|
|
||||||
send_ray(space_state, Vector2(corner.x, corner.y))
|
|
||||||
send_ray(space_state, Vector2(corner.x / 2, corner.y / 2))
|
|
||||||
|
|
||||||
func send_ray(space_state: PhysicsDirectSpaceState3D, pos: Vector2):
|
|
||||||
var from := pivot.global_position
|
|
||||||
var to := Global.camera.project_position(pos, -1)
|
|
||||||
var query = PhysicsRayQueryParameters3D.create(from, to, collision_mask)
|
|
||||||
var result = space_state.intersect_ray(query)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
var hit_point: Vector3 = result.position
|
|
||||||
var diff := hit_point - pivot.global_position
|
|
||||||
var distance := diff.length()
|
|
||||||
|
|
||||||
if distance < occlusion_distance:
|
|
||||||
occlusion_distance = distance
|
|
||||||
occlusion_point = hit_point
|
|
@ -1,14 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
|
|
||||||
@export var interpolate: bool
|
|
||||||
@export var speed: float
|
|
||||||
|
|
||||||
func _process(delta):
|
|
||||||
if Global.player == null:
|
|
||||||
return
|
|
||||||
|
|
||||||
if interpolate:
|
|
||||||
position = Math.damp(position, Global.player.position, speed * delta)
|
|
||||||
else:
|
|
||||||
position = Global.player.position
|
|
||||||
|
|
@ -9,6 +9,9 @@ static func dampf(from: float, to: float, weight: float, smoothing: float = 0.75
|
|||||||
static func damp_angle(from: float, to: float, weight: float, smoothing: float = 0.75):
|
static func damp_angle(from: float, to: float, weight: float, smoothing: float = 0.75):
|
||||||
return lerp_angle(from, to, 1 - exp(-smoothing * weight))
|
return lerp_angle(from, to, 1 - exp(-smoothing * weight))
|
||||||
|
|
||||||
|
static func damp_spherical(from: Vector3, to: Vector3, weight: float, smoothing: float = 0.75):
|
||||||
|
return from.slerp(to, 1 - exp(-smoothing * weight))
|
||||||
|
|
||||||
static func from_to_rotation(from: Vector3, to: Vector3) -> Quaternion:
|
static func from_to_rotation(from: Vector3, to: Vector3) -> Quaternion:
|
||||||
var axis := from.cross(to).normalized()
|
var axis := from.cross(to).normalized()
|
||||||
var angle := from.angle_to(to)
|
var angle := from.angle_to(to)
|
||||||
|
@ -31,7 +31,6 @@ gdscript/warnings/integer_division=0
|
|||||||
|
|
||||||
window/size/viewport_width=1920
|
window/size/viewport_width=1920
|
||||||
window/size/viewport_height=1080
|
window/size/viewport_height=1080
|
||||||
window/size/mode=3
|
|
||||||
window/size/window_width_override=960
|
window/size/window_width_override=960
|
||||||
window/size/window_height_override=540
|
window/size/window_height_override=540
|
||||||
window/stretch/mode="canvas_items"
|
window/stretch/mode="canvas_items"
|
||||||
|
4
client/world/Terrain.gd
Normal file
4
client/world/Terrain.gd
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
extends Terrain3D
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
Global.terrain = self
|
Loading…
Reference in New Issue
Block a user