Implemented physics interpolation

This commit is contained in:
2024-02-27 21:05:55 +01:00
parent ca7fa120ea
commit 489a14061a
23 changed files with 226 additions and 68 deletions

View File

@ -1,5 +1,5 @@
class_name Character
extends CharacterBody3D
extends Node3D
signal controlled(Controller)

View 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

View File

@ -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)

View File

@ -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")])

View File

@ -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:

View File

@ -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)

View File

@ -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

View 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

View 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")

View File

@ -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:

View File

@ -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)

View File

@ -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: