Implemented physics interpolation
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
class_name Character
|
||||
extends CharacterBody3D
|
||||
extends Node3D
|
||||
|
||||
signal controlled(Controller)
|
||||
|
||||
|
9
client/player/CharacterComponent.gd
Normal file
9
client/player/CharacterComponent.gd
Normal file
@ -0,0 +1,9 @@
|
||||
class_name CharacterComponent
|
||||
extends Node
|
||||
|
||||
var character: Character
|
||||
|
||||
func _enter_tree():
|
||||
assert(owner, "owner not set")
|
||||
assert(owner is Character, "owner must be a character")
|
||||
character = owner
|
@ -7,10 +7,16 @@ var id: String
|
||||
## Components
|
||||
var movement: MovementComponent
|
||||
var state: StateComponent
|
||||
var performance: PerformanceComponent
|
||||
var animation: AnimationComponent
|
||||
var physics: CharacterBody3D
|
||||
|
||||
func _enter_tree():
|
||||
movement = $Movement
|
||||
state = $State
|
||||
performance = $Performance
|
||||
animation = $Animation
|
||||
physics = $Physics
|
||||
|
||||
## Name
|
||||
signal name_changed(new_name: String)
|
||||
|
@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=31 format=3 uid="uid://2lcnu3dy54lx"]
|
||||
[gd_scene load_steps=32 format=3 uid="uid://2lcnu3dy54lx"]
|
||||
|
||||
[ext_resource type="Script" path="res://player/Player.gd" id="1_8gebs"]
|
||||
[ext_resource type="PackedScene" uid="uid://c8j7t4yg7anb0" path="res://assets/female/Female.blend" id="2_8nah6"]
|
||||
@ -26,6 +26,7 @@
|
||||
[ext_resource type="AudioStream" uid="uid://cdywep3dxm0y3" path="res://assets/audio/footsteps/Footsteps-Human-Dirt-07.wav" id="19_vvmo0"]
|
||||
[ext_resource type="AudioStream" uid="uid://bp8pka7qhcetq" path="res://assets/audio/footsteps/Footsteps-Human-Dirt-08.wav" id="20_srjtb"]
|
||||
[ext_resource type="AudioStream" uid="uid://bgdodasvt7ier" path="res://assets/audio/footsteps/Footsteps-Human-Dirt-01.wav" id="21_a8ikg"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5yfm1sektkv" path="res://player/performance/PerformanceComponent.tscn" id="22_iqcgj"]
|
||||
[ext_resource type="PackedScene" uid="uid://sx4ein0bju6m" path="res://player/state/StateComponent.tscn" id="28_0i0of"]
|
||||
[ext_resource type="PackedScene" uid="uid://csidusk3jpq0m" path="res://player/voice/VoiceComponent.tscn" id="28_iolce"]
|
||||
|
||||
@ -58,10 +59,16 @@ stream_8/weight = 1.0
|
||||
stream_9/stream = ExtResource("21_a8ikg")
|
||||
stream_9/weight = 1.0
|
||||
|
||||
[node name="Player" type="CharacterBody3D" groups=["player"]]
|
||||
[node name="Player" type="Node3D"]
|
||||
script = ExtResource("1_8gebs")
|
||||
|
||||
[node name="Physics" type="CharacterBody3D" parent="."]
|
||||
collision_layer = 512
|
||||
collision_mask = 769
|
||||
script = ExtResource("1_8gebs")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="Physics"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
|
||||
shape = SubResource("CapsuleShape3D_2f50n")
|
||||
|
||||
[node name="Model" type="Node3D" parent="."]
|
||||
|
||||
@ -97,10 +104,6 @@ bone_idx = 44
|
||||
[node name="Heirloom" parent="Model/Female/Armature/GeneralSkeleton/Weapon" instance=ExtResource("7_u8433")]
|
||||
transform = Transform3D(-4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, 0.197644, 0, 0)
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
|
||||
shape = SubResource("CapsuleShape3D_2f50n")
|
||||
|
||||
[node name="HUD" parent="." node_paths=PackedStringArray("health") instance=ExtResource("7_fwgtd")]
|
||||
layers = 512
|
||||
visibility_range_end = 50.0
|
||||
@ -129,10 +132,14 @@ footsteps = SubResource("AudioStreamRandomizer_4yj1k")
|
||||
[node name="Health" parent="." instance=ExtResource("2_np5ag")]
|
||||
max_value = 100.0
|
||||
|
||||
[node name="Movement" parent="." instance=ExtResource("8_25qd0")]
|
||||
[node name="Movement" parent="." node_paths=PackedStringArray("body") instance=ExtResource("8_25qd0")]
|
||||
body = NodePath("../Physics")
|
||||
|
||||
[node name="Performance" parent="." node_paths=PackedStringArray("animation") instance=ExtResource("22_iqcgj")]
|
||||
animation = NodePath("../Animation")
|
||||
|
||||
[node name="Rotation" parent="." node_paths=PackedStringArray("root") instance=ExtResource("9_agxqu")]
|
||||
root = NodePath("..")
|
||||
root = NodePath("../Model")
|
||||
|
||||
[node name="Skills" parent="." instance=ExtResource("14_6idcf")]
|
||||
skills = Array[Resource("res://skill/Skill.gd")]([ExtResource("2_x58e1"), ExtResource("3_l76ly"), null, ExtResource("5_pnues")])
|
||||
|
@ -1,16 +1,27 @@
|
||||
class_name AnimationComponent
|
||||
extends Node
|
||||
extends CharacterComponent
|
||||
|
||||
@export var skip_frames: int = 0
|
||||
|
||||
var animations: AnimationPlayer
|
||||
var state: StateComponent
|
||||
var accumulated_delta: float
|
||||
var skipped_frames: int
|
||||
|
||||
func _ready():
|
||||
state = owner.get_node("State")
|
||||
state = character.get_node("State")
|
||||
state.transitioned.connect(on_transition)
|
||||
animations = %AnimationPlayer
|
||||
|
||||
func _process(delta):
|
||||
animations.advance(delta)
|
||||
accumulated_delta += delta
|
||||
|
||||
if skipped_frames >= skip_frames:
|
||||
animations.advance(accumulated_delta)
|
||||
accumulated_delta = 0
|
||||
skipped_frames = 0
|
||||
else:
|
||||
skipped_frames += 1
|
||||
|
||||
func on_transition(_from: StateComponent.State, to: StateComponent.State):
|
||||
match to:
|
||||
|
@ -1,22 +1,25 @@
|
||||
class_name ProxyController
|
||||
extends Controller
|
||||
|
||||
@export var interpolation_speed := 5.0
|
||||
@export var interpolation_speed := 20.0
|
||||
|
||||
var player: Player
|
||||
var movement: MovementComponent
|
||||
var server_position: Vector3
|
||||
|
||||
func _init(new_player: Player):
|
||||
player = new_player
|
||||
movement = player.movement
|
||||
name = "Controller"
|
||||
process_physics_priority = 1
|
||||
|
||||
func _ready():
|
||||
server_position = player.position
|
||||
|
||||
func _physics_process(delta: float):
|
||||
if absf(server_position.x - player.position.x) < 0.001 && absf(server_position.z - player.position.z) < 0.001:
|
||||
if absf(server_position.x - movement.physics_position.x) < 0.001 && absf(server_position.z - movement.physics_position.z) < 0.001:
|
||||
return
|
||||
|
||||
|
||||
var time := interpolation_speed * delta
|
||||
player.position.x = Math.dampf(player.position.x, server_position.x, time)
|
||||
player.position.z = Math.dampf(player.position.z, server_position.z, time)
|
||||
movement.physics_position.x = Math.dampf(movement.physics_position.x, server_position.x, time)
|
||||
movement.physics_position.z = Math.dampf(movement.physics_position.z, server_position.z, time)
|
@ -1,24 +1,30 @@
|
||||
class_name MovementComponent
|
||||
extends Node
|
||||
extends CharacterComponent
|
||||
|
||||
static var ticks_per_second := ProjectSettings.get_setting("physics/common/physics_ticks_per_second") as float
|
||||
static var gravity := ProjectSettings.get_setting("physics/3d/default_gravity") as float
|
||||
|
||||
@export var body: CharacterBody3D
|
||||
@export var move_speed := 4.5
|
||||
@export var jump_velocity := 4.5
|
||||
@export var deceleration_speed := 20
|
||||
|
||||
var body: Character
|
||||
var direction: Vector3
|
||||
var gravity: float
|
||||
var physics_position: Vector3
|
||||
|
||||
func _ready():
|
||||
gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
body = owner as Character
|
||||
body.controlled.connect(on_controlled)
|
||||
physics_position = character.position
|
||||
character.controlled.connect(on_controlled)
|
||||
|
||||
func on_controlled(controller: Controller):
|
||||
controller.direction_changed.connect(on_direction_changed)
|
||||
controller.jumped.connect(jump)
|
||||
func _process(delta: float):
|
||||
character.position = Math.damp_vector(character.position, physics_position, ticks_per_second * delta)
|
||||
|
||||
func _physics_process(delta):
|
||||
func _physics_process(delta: float):
|
||||
begin_physics()
|
||||
move(delta)
|
||||
end_physics()
|
||||
|
||||
func move(delta: float):
|
||||
if direction:
|
||||
body.velocity.x = direction.x * move_speed
|
||||
body.velocity.z = direction.z * move_speed
|
||||
@ -34,11 +40,22 @@ func _physics_process(delta):
|
||||
|
||||
body.move_and_slide()
|
||||
|
||||
func on_direction_changed(new_direction: Vector3):
|
||||
direction = new_direction
|
||||
func begin_physics():
|
||||
body.global_position = physics_position
|
||||
|
||||
func end_physics():
|
||||
physics_position = body.global_position
|
||||
body.position = Vector3.ZERO
|
||||
|
||||
func can_jump() -> bool:
|
||||
return body.is_on_floor()
|
||||
|
||||
func jump():
|
||||
body.velocity.y = jump_velocity
|
||||
|
||||
func on_controlled(controller: Controller):
|
||||
controller.direction_changed.connect(on_direction_changed)
|
||||
controller.jumped.connect(jump)
|
||||
|
||||
func on_direction_changed(new_direction: Vector3):
|
||||
direction = new_direction
|
||||
|
72
client/player/performance/PerformanceComponent.gd
Normal file
72
client/player/performance/PerformanceComponent.gd
Normal file
@ -0,0 +1,72 @@
|
||||
class_name PerformanceComponent
|
||||
extends VisibleOnScreenNotifier3D
|
||||
|
||||
const SKIP_FRAMES_INVISIBLE := 16
|
||||
const SKIP_FRAMES_VISIBLE := 8
|
||||
|
||||
static var quality_budget: Array[int] = [
|
||||
10, # 0 skipped frames
|
||||
10, # 1 skipped frame
|
||||
10, # 2 skipped frames
|
||||
10, # 3 skipped frames
|
||||
10, # 4 skipped frames
|
||||
10, # 5 skipped frames
|
||||
10, # 6 skipped frames
|
||||
10, # 7 skipped frames
|
||||
]
|
||||
|
||||
@export var animation: AnimationComponent
|
||||
|
||||
var camera_distance_squared: float
|
||||
var on_screen: bool
|
||||
var skip_frames_visible: int
|
||||
|
||||
func _ready():
|
||||
assert(animation)
|
||||
screen_entered.connect(on_screen_entered)
|
||||
screen_exited.connect(on_screen_exited)
|
||||
|
||||
func on_screen_entered():
|
||||
on_screen = true
|
||||
animation.skip_frames = skip_frames_visible
|
||||
|
||||
func on_screen_exited():
|
||||
on_screen = false
|
||||
animation.skip_frames = SKIP_FRAMES_INVISIBLE
|
||||
|
||||
static func update_all_animations():
|
||||
var visible_players: Array[Player] = []
|
||||
|
||||
for player in Global.players.id_to_player.values():
|
||||
player.performance.camera_distance_squared = player.global_position.distance_squared_to(Global.camera.global_position)
|
||||
|
||||
if player.performance.on_screen:
|
||||
visible_players.append(player)
|
||||
else:
|
||||
player.performance.animation.skip_frames = SKIP_FRAMES_INVISIBLE
|
||||
|
||||
if Global.player:
|
||||
Global.player.performance.camera_distance_squared = 0
|
||||
|
||||
visible_players.sort_custom(PerformanceComponent.distance_sort)
|
||||
|
||||
var skip_frames := 0
|
||||
var count := 0
|
||||
|
||||
for player in visible_players:
|
||||
if skip_frames >= quality_budget.size():
|
||||
player.performance.skip_frames_visible = SKIP_FRAMES_VISIBLE
|
||||
player.animation.skip_frames = SKIP_FRAMES_VISIBLE
|
||||
continue
|
||||
|
||||
player.performance.skip_frames_visible = skip_frames
|
||||
player.animation.skip_frames = skip_frames
|
||||
count += 1
|
||||
|
||||
if count >= quality_budget[skip_frames]:
|
||||
skip_frames += 1
|
||||
count = 0
|
||||
|
||||
static func distance_sort(a: Player, b: Player) -> bool:
|
||||
return a.performance.camera_distance_squared < b.performance.camera_distance_squared
|
||||
|
7
client/player/performance/PerformanceComponent.tscn
Normal file
7
client/player/performance/PerformanceComponent.tscn
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b5yfm1sektkv"]
|
||||
|
||||
[ext_resource type="Script" path="res://player/performance/PerformanceComponent.gd" id="1_vp0dv"]
|
||||
|
||||
[node name="Performance" type="VisibleOnScreenNotifier3D"]
|
||||
aabb = AABB(-1, 0, -1, 2, 2, 2)
|
||||
script = ExtResource("1_vp0dv")
|
@ -1,5 +1,5 @@
|
||||
class_name RotationComponent
|
||||
extends Node
|
||||
extends CharacterComponent
|
||||
|
||||
@export var root: Node3D
|
||||
@export var rotation_speed: float = 15.0
|
||||
@ -8,8 +8,9 @@ var direction: Vector3
|
||||
var angle: float
|
||||
|
||||
func _ready():
|
||||
assert(root, "Rotation root needs to be set")
|
||||
(owner as Character).controlled.connect(on_controlled)
|
||||
assert(root, "rotation root needs to be set")
|
||||
assert(rotation_speed > 0, "rotation speed must be greater than zero")
|
||||
character.controlled.connect(on_controlled)
|
||||
|
||||
func _process(delta):
|
||||
if absf(angle_difference(root.rotation.y, angle)) < 0.001:
|
||||
|
@ -1,12 +1,9 @@
|
||||
class_name SkillsComponent
|
||||
extends Node
|
||||
extends CharacterComponent
|
||||
|
||||
@export var skills: Array[Skill]
|
||||
|
||||
var character: Character
|
||||
|
||||
func _ready():
|
||||
character = owner
|
||||
character.controlled.connect(on_controlled)
|
||||
|
||||
func on_controlled(controller: Controller):
|
||||
@ -25,4 +22,4 @@ func use_skill(slot: int):
|
||||
return
|
||||
|
||||
var scene := skill.scene.instantiate()
|
||||
owner.add_child(scene)
|
||||
character.add_child(scene)
|
||||
|
@ -1,5 +1,5 @@
|
||||
class_name StateComponent
|
||||
extends Node
|
||||
extends CharacterComponent
|
||||
|
||||
enum State {
|
||||
None,
|
||||
@ -17,7 +17,7 @@ var _current: State
|
||||
|
||||
func _ready():
|
||||
current = State.Idle
|
||||
movement = owner.get_node("Movement")
|
||||
movement = character.get_node("Movement")
|
||||
|
||||
func _process(_delta):
|
||||
if current == State.Skill:
|
||||
|
Reference in New Issue
Block a user