Refactoring and added unit testing framework

This commit is contained in:
anyreso 2024-04-12 22:23:39 +00:00
parent 06346b2d98
commit 080ce479da
145 changed files with 16250 additions and 470 deletions

Binary file not shown.

160
entities/player/player.gd Normal file
View file

@ -0,0 +1,160 @@
extends RigidBody3D
class_name Player
enum PlayerState { PLAYER_ALIVE, PLAYER_DEAD }
# constants
const max_energy : float = 100.0
const energy_charge_rate : float = 10 # energy per second
const energy_drain_rate : float = 20 # energy per second
const ground_speed : float = 48 / 3.6 # m/s
const jetpack_vertical_force : float = 800
const jetpack_horizontal_force : float = 400
const jetpack_force_factor : float = 2
# parameters
@export var max_floor_angle : float = 60
@export var energy: float = 100.0
var g : float = ProjectSettings.get_setting("physics/3d/default_gravity") # in m/s²
var gravity : Vector3 = g * ProjectSettings.get_setting("physics/3d/default_gravity_vector")
@export var player_id = 1:
set(id):
player_id = id
$PlayerInput.set_multiplayer_authority(id)
@export var player_state = PlayerState.PLAYER_ALIVE
@onready var input : PlayerInput = $PlayerInput
@onready var camera = $SpringArm3D/Camera3D
@onready var hud = $HUD
@onready var iff = $ThirdPerson/IFF
@onready var shape_cast = $ShapeCast3D
@onready var weapon = $SpringArm3D/Inventory/SpaceGun
@onready var animation_player : AnimationPlayer = $AnimationPlayer
@onready var health_component = $HealthComponent
@onready var spring_arm_height = $SpringArm3D.position.y
signal died(player)
signal energy_changed(energy)
var _jumping = false
@onready var _original_weapon_transform : Transform3D = weapon.transform
func _ready():
energy_changed.connect(hud._on_energy_changed)
health_component.health_changed.connect(hud._on_health_changed)
health_component.health_changed.connect(iff._on_health_changed)
health_component.health_changed.emit(health_component.health)
health_component.health_zeroed.connect(_die)
if player_id == multiplayer.get_unique_id():
camera.current = true
remove_child($ThirdPerson)
else:
remove_child($HUD)
input.fired_primary.connect(_fire_primary)
input.jumped.connect(_jump)
func _fire_primary():
if player_state == PlayerState.PLAYER_DEAD:
return
if not weapon.can_fire():
return
var current_weapon_transform = weapon.transform
weapon.transform = _original_weapon_transform
weapon.fire_primary()
weapon.transform = current_weapon_transform
animation_player.stop()
animation_player.play("shoot")
func _jump():
if player_state == PlayerState.PLAYER_DEAD:
return
_jumping = true
func _is_on_floor() -> bool:
return shape_cast.is_colliding()
func _is_skiing() -> bool:
return input.skiing
func _handle_jetpack(delta, direction):
if input.jetting:
if energy > 0:
var up_vector = Vector3.UP * jetpack_vertical_force * jetpack_force_factor
var side_vector = direction * jetpack_horizontal_force * jetpack_force_factor
apply_force(up_vector + side_vector)
energy -= energy_drain_rate * delta
else:
energy += energy_charge_rate * delta
energy = clamp(energy, 0, max_energy)
energy_changed.emit(energy)
func _process(_delta):
if player_state == PlayerState.PLAYER_DEAD:
iff.hide()
return
else:
iff.show()
%SpringArm3D.global_transform.basis = Basis.from_euler(Vector3(input.camera_rotation.y, input.camera_rotation.x, 0.0))
if animation_player.current_animation == "shoot":
pass
else:
animation_player.play("idle")
func _physics_process(delta):
if player_state == PlayerState.PLAYER_DEAD:
return
# retrieve user's direction vector
var _input_dir = input.direction
# compute direction in local space
var _direction = (transform.basis * Vector3(_input_dir.x, 0, _input_dir.y)).normalized()
# adjust direction based on spring arm rotation
_direction = _direction.rotated(Vector3.UP, $SpringArm3D.rotation.y)
_handle_jetpack(delta, _direction)
# handle ski
if _is_skiing():
physics_material_override.friction = 0
else:
physics_material_override.friction = 1
if _is_on_floor():
if not _direction.is_zero_approx() and not _is_skiing():
# retrieve collision normal
var normal = shape_cast.get_collision_normal(0)
# calculate the angle between the ground normal and the up vector
var slope_angle = rad_to_deg(acos(normal.dot(Vector3.UP)))
# check if the slope angle exceeds the maximum slope angle
if slope_angle <= max_floor_angle:
# adjust direction based on the floor normal to align with the slope
_direction = _direction.slide(normal)
linear_velocity = lerp(linear_velocity, _direction * ground_speed, .1)
if _jumping:
linear_velocity.y = sqrt(2 * abs((mass * gravity * delta).y) * 1)
_jumping = false
func _die():
player_state = PlayerState.PLAYER_DEAD
animation_player.stop()
animation_player.play("death")
var tween = create_tween()
tween.tween_interval(2)
tween.tween_callback(func(): died.emit(self))
tween.tween_callback(func(): animation_player.stop())
func respawn(location):
linear_velocity = Vector3()
health_component.heal_full()
position = location
player_state = PlayerState.PLAYER_ALIVE

258
entities/player/player.tscn Normal file
View file

@ -0,0 +1,258 @@
[gd_scene load_steps=18 format=3 uid="uid://cbhx1xme0sb7k"]
[ext_resource type="Script" path="res://entities/player/player.gd" id="1_mk68k"]
[ext_resource type="Shape3D" uid="uid://dkwljsgaflf31" path="res://entities/player/collision_shape.res" id="2_8rtw3"]
[ext_resource type="PackedScene" uid="uid://bcv81ku26xo" path="res://interfaces/hud/hud.tscn" id="3_ccety"]
[ext_resource type="PackedScene" uid="uid://c8co0qa2omjmh" path="res://weapons/space_gun/space_gun.tscn" id="4_lhn5w"]
[ext_resource type="PackedScene" uid="uid://dn1tcakam5egs" path="res://weapons/space_gun/projectile.tscn" id="5_lvaut"]
[ext_resource type="PackedScene" uid="uid://bof3mg7wgxrmn" path="res://components/health_component.tscn" id="5_t6i6e"]
[ext_resource type="Script" path="res://entities/player/player_input.gd" id="6_ymcrr"]
[ext_resource type="PackedScene" uid="uid://dsysi2rd3bu76" path="res://interfaces/hud/iff.tscn" id="7_8hc80"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_clur0"]
resource_local_to_scene = true
bounce = 1.0
absorbent = true
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1hdqa"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_vmqfq"]
material = SubResource("StandardMaterial3D_1hdqa")
radius = 0.25
[sub_resource type="Animation" id="Animation_cb46l"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("SpringArm3D/Inventory/SpaceGun:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0.244668, -0.229311, -0.30332)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("SpringArm3D/Inventory/SpaceGun:rotation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 3.14159, 0)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("SpringArm3D:position")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0.5, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("SpringArm3D:rotation")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_yqgrk"]
resource_name = "death"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("SpringArm3D:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.5),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 0.5, 0), Vector3(0, -0.114794, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("SpringArm3D:rotation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.5),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(-1.35254, 0, 0)]
}
[sub_resource type="Animation" id="Animation_as2v0"]
resource_name = "idle"
length = 3.0
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("SpringArm3D/Inventory/SpaceGun:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1.5),
"transitions": PackedFloat32Array(-2, -2),
"update": 0,
"values": [Vector3(0.244668, -0.229311, -0.30332), Vector3(0.244668, -0.26057, -0.30332)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("SpringArm3D/Inventory/SpaceGun:rotation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1.5),
"transitions": PackedFloat32Array(-2, -2),
"update": 0,
"values": [Vector3(0, 3.14159, 0), Vector3(0.114371, 3.14159, -1.73453e-08)]
}
[sub_resource type="Animation" id="Animation_p84l0"]
resource_name = "shoot"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("SpringArm3D/Inventory/SpaceGun:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(0.5, 1),
"update": 0,
"values": [Vector3(0.244668, -0.204488, -0.268482), Vector3(0.244668, -0.229311, -0.30332)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("SpringArm3D/Inventory/SpaceGun:rotation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(0.5, 1),
"update": 0,
"values": [Vector3(-0.0584511, 3.14159, 8.83593e-09), Vector3(0, 3.14159, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_hg307"]
_data = {
"RESET": SubResource("Animation_cb46l"),
"death": SubResource("Animation_yqgrk"),
"idle": SubResource("Animation_as2v0"),
"shoot": SubResource("Animation_p84l0")
}
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_rqdp6"]
properties/0/path = NodePath(".:linear_velocity")
properties/0/spawn = true
properties/0/replication_mode = 1
properties/1/path = NodePath(".:position")
properties/1/spawn = true
properties/1/replication_mode = 1
properties/2/path = NodePath(".:player_id")
properties/2/spawn = true
properties/2/replication_mode = 2
properties/3/path = NodePath("HealthComponent:health")
properties/3/spawn = true
properties/3/replication_mode = 2
properties/4/path = NodePath(".:player_state")
properties/4/spawn = true
properties/4/replication_mode = 2
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_5j4ew"]
properties/0/path = NodePath(".:direction")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath(".:jetting")
properties/1/spawn = false
properties/1/replication_mode = 2
properties/2/path = NodePath(".:camera_rotation")
properties/2/spawn = false
properties/2/replication_mode = 1
properties/3/path = NodePath(".:skiing")
properties/3/spawn = false
properties/3/replication_mode = 2
[node name="Player" type="RigidBody3D"]
axis_lock_angular_x = true
axis_lock_angular_y = true
axis_lock_angular_z = true
mass = 75.0
physics_material_override = SubResource("PhysicsMaterial_clur0")
continuous_cd = true
script = ExtResource("1_mk68k")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_vmqfq")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = ExtResource("2_8rtw3")
[node name="ShapeCast3D" type="ShapeCast3D" parent="."]
transform = Transform3D(1.05, 0, 0, 0, 1.05, 0, 0, 0, 1.05, 0, 0, 0)
shape = ExtResource("2_8rtw3")
target_position = Vector3(0, 0, 0)
collide_with_areas = true
[node name="HUD" parent="." instance=ExtResource("3_ccety")]
[node name="SpringArm3D" type="SpringArm3D" parent="."]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
spring_length = 0.0
[node name="Camera3D" type="Camera3D" parent="SpringArm3D"]
fov = 100.0
near = 0.1
[node name="Inventory" type="Node3D" parent="SpringArm3D"]
[node name="SpaceGun" parent="SpringArm3D/Inventory" instance=ExtResource("4_lhn5w")]
transform = Transform3D(-1, 0, 2.53518e-06, 0, 1, 0, -2.53518e-06, 0, -1, 0.244668, -0.229311, -0.30332)
PROJECTILE = ExtResource("5_lvaut")
[node name="HealthComponent" parent="." instance=ExtResource("5_t6i6e")]
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_hg307")
}
autoplay = "idle"
playback_default_blend_time = 0.05
[node name="ServerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_rqdp6")
[node name="PlayerInput" type="MultiplayerSynchronizer" parent="."]
root_path = NodePath(".")
replication_config = SubResource("SceneReplicationConfig_5j4ew")
script = ExtResource("6_ymcrr")
[node name="ThirdPerson" type="Node3D" parent="."]
[node name="IFFAttachment" type="Node3D" parent="ThirdPerson"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.27457, 0)
[node name="IFF" parent="ThirdPerson" node_paths=PackedStringArray("attach_point", "player") instance=ExtResource("7_8hc80")]
attach_point = NodePath("../IFFAttachment")
player = NodePath("../..")

View file

@ -0,0 +1,56 @@
extends MultiplayerSynchronizer
class_name PlayerInput
@export var jetting = false
@export var skiing = false
@export var direction = Vector2.ZERO
@export var camera_rotation : Vector2
@export var MOUSE_SENSITIVITY : float = 1.0
signal jumped
signal fired_primary
var _mouse_position: Vector2
func _ready():
var has_authority = is_multiplayer_authority()
set_process(has_authority)
set_process_unhandled_input(has_authority)
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event: InputEvent) -> void:
var mouse_mode = Input.get_mouse_mode()
# isolate mouse events
if event is InputEventMouseMotion:
if mouse_mode == Input.MOUSE_MODE_CAPTURED:
# retrieve mouse position relative to last frame
_mouse_position = event.relative * MOUSE_SENSITIVITY
func _process(delta):
direction = Input.get_vector("left", "right", "forward", "backward")
if Input.is_action_just_pressed("jump_and_jet"):
_jump.rpc()
if Input.is_action_just_pressed("fire_primary"):
_fire_primary.rpc()
jetting = Input.is_action_pressed("jump_and_jet")
skiing = Input.is_action_pressed("ski")
_update_camera(delta)
@rpc("call_local")
func _jump():
jumped.emit()
@rpc("call_local")
func _fire_primary():
fired_primary.emit()
func _update_camera(delta):
# clamp vertical rotation (head motion)
camera_rotation.y -= _mouse_position.y * delta
camera_rotation.y = clamp(camera_rotation.y, deg_to_rad(-90.0),deg_to_rad(90.0))
# wrap horizontal rotation (to prevent accumulation)
camera_rotation.x -= _mouse_position.x * delta
camera_rotation.x = wrapf(camera_rotation.x, deg_to_rad(0.0),deg_to_rad(360.0))
# reset mouse motion until next input event
_mouse_position = Vector2.ZERO