From b439919ad047123a56564c133f3466db7debe624 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Feb 2024 16:28:27 +0100 Subject: [PATCH] Improved component system --- README.md | 13 ++++++++++ client/Main.gd | 18 ++++++++------ client/enemy/Enemy.gd | 10 ++++---- client/enemy/EnemyController.gd | 5 +++- client/enemy/slime/Slime.tscn | 6 +---- client/network/PlayerAdd.gd | 2 +- client/network/PlayerJump.gd | 4 ++-- client/player/Character.gd | 4 ++++ client/player/Player.gd | 23 ++---------------- client/player/Player.tscn | 19 ++------------- client/player/animation/AnimationComponent.gd | 2 +- client/player/controller/Controller.gd | 6 ++++- client/player/controller/PlayerController.gd | 24 ++++++++++--------- client/player/controller/ProxyController.gd | 7 +++--- client/player/hud/HealthBar.gd | 2 +- client/player/movement/MovementComponent.gd | 12 ++++------ client/player/rotation/RotationComponent.gd | 14 ++++------- client/player/skills/SkillsComponent.gd | 15 ++++++++---- client/player/state/StateComponent.gd | 2 +- 19 files changed, 89 insertions(+), 99 deletions(-) create mode 100644 client/player/Character.gd diff --git a/README.md b/README.md index aef4e45..84497ad 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,21 @@ Therefore the `client/assets` directory needs to be downloaded separately. Downl - Cloth attachments - Grass shader - Water shader +- Loading UI - Settings UI +## Components + +This project makes heavy use of composition via components. +They are implemented with the following rules: + +- Components can have **read-only** references to other components. +- Components must not **write** data to another component. +- Components that produce some output must use signals to communicate messages to other components. + +In other words, it's allowed for components to have dependencies, but a component can not know where its output is going to be used in the future. +Therefore, outputs must use a signal to communicate the output to an unknown number of receivers. + ## License Please see the [license documentation](https://akyoto.dev/license). diff --git a/client/Main.gd b/client/Main.gd index 8f513da..8953b1f 100644 --- a/client/Main.gd +++ b/client/Main.gd @@ -1,18 +1,22 @@ extends Node func _ready(): - # %Logout.success.emit() pause(true) %Login.success.connect(on_login) %Logout.success.connect(on_logout) - # %Login.send_login() - Global.username = "Local" - Global.account_id = "test" - Global.auth_token = "test" - %Login.success.emit() - Global.player = %PlayerAdd.spawn_player(Global.account_id) + var args := OS.get_cmdline_args() + var offline := args.has("--offline") + + if offline: + Global.username = "Local" + Global.account_id = "test" + Global.auth_token = "test" + Global.player = %PlayerAdd.spawn_player(Global.account_id) + %Login.success.emit() + else: + %Login.send_login() func _input(event): if event.is_action_pressed("toggle_fullscreen"): diff --git a/client/enemy/Enemy.gd b/client/enemy/Enemy.gd index f52d158..d8742a6 100644 --- a/client/enemy/Enemy.gd +++ b/client/enemy/Enemy.gd @@ -1,7 +1,7 @@ class_name Enemy -extends CharacterBody3D +extends Character -signal direction_changed - -func set_direction(direction: Vector3): - direction_changed.emit(direction) \ No newline at end of file +func _init(): + controller = EnemyController.new() + add_child(controller) + controller.owner = self \ No newline at end of file diff --git a/client/enemy/EnemyController.gd b/client/enemy/EnemyController.gd index 006d316..11ff008 100644 --- a/client/enemy/EnemyController.gd +++ b/client/enemy/EnemyController.gd @@ -3,9 +3,12 @@ extends Controller var enemy: Enemy +func _init(): + name = "Controller" + func _ready(): enemy = owner func _process(_delta): var direction := (Global.player.global_position - enemy.global_position).normalized() - enemy.set_direction(direction) \ No newline at end of file + direction_changed.emit(direction) diff --git a/client/enemy/slime/Slime.tscn b/client/enemy/slime/Slime.tscn index 5c49450..0ed9714 100644 --- a/client/enemy/slime/Slime.tscn +++ b/client/enemy/slime/Slime.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=15 format=3 uid="uid://cb2t7bvvf3gwh"] +[gd_scene load_steps=14 format=3 uid="uid://cb2t7bvvf3gwh"] [ext_resource type="PackedScene" uid="uid://b358op5h1y83m" path="res://assets/slime/Slime.blend" id="1_1h1hj"] [ext_resource type="Script" path="res://enemy/Enemy.gd" id="1_r5888"] @@ -7,7 +7,6 @@ [ext_resource type="PackedScene" uid="uid://6jpnl6c4fdvo" path="res://player/hud/HUDComponent.tscn" id="3_4jtio"] [ext_resource type="PackedScene" uid="uid://bfvudkup0xsq4" path="res://enemy/death/DeathComponent.tscn" id="6_68kgw"] [ext_resource type="PackedScene" uid="uid://dl4vcp04t8iyr" path="res://item/soul/Soul.tscn" id="7_i0rtb"] -[ext_resource type="Script" path="res://enemy/EnemyController.gd" id="8_b7r6l"] [ext_resource type="PackedScene" uid="uid://c8lifxuvpchu3" path="res://player/visibility/VisibilityComponent.tscn" id="9_ssc7p"] [sub_resource type="SphereShape3D" id="SphereShape3D_s5ct7"] @@ -95,6 +94,3 @@ hud = NodePath("../HUD") hud_distance = 50.0 physics_distance = 50.0 render_distance = 100.0 - -[node name="EnemyController" type="Node" parent="."] -script = ExtResource("8_b7r6l") diff --git a/client/network/PlayerAdd.gd b/client/network/PlayerAdd.gd index ef2aadc..a38a726 100644 --- a/client/network/PlayerAdd.gd +++ b/client/network/PlayerAdd.gd @@ -23,7 +23,7 @@ func handle_packet(data: PackedByteArray): var player := spawn_player(player_id) player.position = server_position - player.set_character_name(player_name) + player.set_player_name(player_name) func spawn_player(id: String) -> Player: var player: Player diff --git a/client/network/PlayerJump.gd b/client/network/PlayerJump.gd index 027f56e..da6af83 100644 --- a/client/network/PlayerJump.gd +++ b/client/network/PlayerJump.gd @@ -4,7 +4,7 @@ func _ready(): %PlayerAdd.main_player_spawned.connect(on_main_player_spawned) func on_main_player_spawned(player: Player): - player.jumped.connect(on_jump) + player.controller.jumped.connect(on_jump) func on_jump(): var buffer := StreamPeerBuffer.new() @@ -14,4 +14,4 @@ func on_jump(): func handle_packet(data: PackedByteArray): var player_id := data.get_string_from_ascii() var player := Global.players.get_player(player_id) - player.jump() + player.controller.jumped.emit() diff --git a/client/player/Character.gd b/client/player/Character.gd new file mode 100644 index 0000000..abe9340 --- /dev/null +++ b/client/player/Character.gd @@ -0,0 +1,4 @@ +class_name Character +extends CharacterBody3D + +var controller: Controller \ No newline at end of file diff --git a/client/player/Player.gd b/client/player/Player.gd index d83a586..fb0b38a 100644 --- a/client/player/Player.gd +++ b/client/player/Player.gd @@ -1,29 +1,10 @@ class_name Player -extends CharacterBody3D +extends Character -signal skill_used(skill: Skill) -signal dashed -signal jumped signal name_changed(new_name: String) -signal direction_changed - -@export var skills: Array[Skill] var id: String -var controller: Controller - -func use_skill(slot: int): - if get_node("State").current == StateComponent.State.Skill: - return - - skill_used.emit(skills[slot]) - -func jump(): - jumped.emit() - -func set_direction(direction: Vector3): - direction_changed.emit(direction) func set_player_name(new_name: String): name = new_name - name_changed.emit(new_name) + name_changed.emit(new_name) \ No newline at end of file diff --git a/client/player/Player.tscn b/client/player/Player.tscn index 57eb3cb..8957859 100644 --- a/client/player/Player.tscn +++ b/client/player/Player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=31 format=3 uid="uid://2lcnu3dy54lx"] +[gd_scene load_steps=29 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"] @@ -6,9 +6,7 @@ [ext_resource type="Resource" uid="uid://yaq8ui3f6fwa" path="res://skill/slash/slash.tres" id="2_x58e1"] [ext_resource type="Resource" uid="uid://ba1filjaldakv" path="res://skill/spin/spin.tres" id="3_l76ly"] [ext_resource type="PackedScene" uid="uid://cgqbkj8wbcatv" path="res://assets/hair/PonyTail.blend" id="3_umw6q"] -[ext_resource type="FontFile" uid="uid://b7mov13kwi8u8" path="res://assets/font/ubuntu_nf_regular.ttf" id="4_76ehj"] [ext_resource type="Skin" uid="uid://bbqyiue1vj37f" path="res://assets/hoodie/Hoodie_Skin.tres" id="4_b1tg1"] -[ext_resource type="Resource" uid="uid://1vmnijk8ap6b" path="res://skill/thrust/thrust.tres" id="4_s8cf2"] [ext_resource type="ArrayMesh" uid="uid://dbmluwi2atit" path="res://assets/hoodie/Hoodie_Mesh.res" id="5_mkrgn"] [ext_resource type="Resource" uid="uid://cnusbw23jf672" path="res://skill/dash/dash.tres" id="5_pnues"] [ext_resource type="PackedScene" uid="uid://6jpnl6c4fdvo" path="res://player/hud/HUDComponent.tscn" id="7_fwgtd"] @@ -38,7 +36,6 @@ height = 1.6 collision_layer = 512 collision_mask = 769 script = ExtResource("1_8gebs") -skills = Array[Resource("res://skill/Skill.gd")]([ExtResource("2_x58e1"), ExtResource("3_l76ly"), ExtResource("4_s8cf2"), ExtResource("5_pnues")]) [node name="Model" type="Node3D" parent="."] @@ -77,19 +74,6 @@ transform = Transform3D(-4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, 0.1976 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0) shape = SubResource("CapsuleShape3D_2f50n") -[node name="Label" type="Label3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) -visible = false -pixel_size = 0.0005 -billboard = 1 -double_sided = false -alpha_antialiasing_mode = 1 -outline_modulate = Color(0.0627451, 0.0627451, 0.0627451, 0.498039) -text = "Player" -font = ExtResource("4_76ehj") -font_size = 256 -outline_size = 32 - [node name="HUD" parent="." node_paths=PackedStringArray("health") instance=ExtResource("7_fwgtd")] layers = 512 health = NodePath("../Health") @@ -121,6 +105,7 @@ libraries = { } [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")]) [node name="State" parent="." instance=ExtResource("28_0i0of")] diff --git a/client/player/animation/AnimationComponent.gd b/client/player/animation/AnimationComponent.gd index f37f832..7a93ae8 100644 --- a/client/player/animation/AnimationComponent.gd +++ b/client/player/animation/AnimationComponent.gd @@ -5,7 +5,7 @@ var player: AnimationPlayer var state: StateComponent func _ready(): - state = owner.find_child("State") + state = owner.get_node("State") state.transitioned.connect(on_transition) player = $AnimationPlayer diff --git a/client/player/controller/Controller.gd b/client/player/controller/Controller.gd index 1dd9ea7..1d30f67 100644 --- a/client/player/controller/Controller.gd +++ b/client/player/controller/Controller.gd @@ -1,2 +1,6 @@ class_name Controller -extends Node \ No newline at end of file +extends Node + +signal direction_changed(direction: Vector3) +signal jumped +signal used_skill(slot: int) \ No newline at end of file diff --git a/client/player/controller/PlayerController.gd b/client/player/controller/PlayerController.gd index 4b3383f..384ff8f 100644 --- a/client/player/controller/PlayerController.gd +++ b/client/player/controller/PlayerController.gd @@ -1,32 +1,34 @@ class_name PlayerController extends Controller -## The character that we're controlling. var player: Player - -## We need the movement component to check if we can perform certain actions. var movement: MovementComponent +var skills: SkillsComponent func _init(new_player: Player): player = new_player - movement = player.find_child("Movement") + movement = player.get_node("Movement") + skills = player.get_node("Skills") + name = "Controller" -func _unhandled_input(event): +func _unhandled_input(event: InputEvent): if Global.interacting_with_ui: return - # Calculate the direction + update_direction() + update_actions(event) + +func update_direction(): var move := Input.get_vector("move_left", "move_right", "move_forward", "move_backward") var direction := (Global.camera.global_basis * Vector3(move.x, 0, move.y)) direction.y = 0 direction = direction.normalized() - - # Notify components - player.set_direction(direction) + direction_changed.emit(direction) +func update_actions(event: InputEvent): if event.is_action_pressed("jump") && movement && movement.can_jump(): - player.jump() + jumped.emit() for i in range(4): if event.is_action_pressed("skill_%d" % (i + 1)): - player.use_skill(i) + used_skill.emit(i) diff --git a/client/player/controller/ProxyController.gd b/client/player/controller/ProxyController.gd index c2aff0e..c1989f1 100644 --- a/client/player/controller/ProxyController.gd +++ b/client/player/controller/ProxyController.gd @@ -9,6 +9,7 @@ var server_position: Vector3 func _init(new_player: Player): player = new_player + name = "Controller" func _ready(): server_position = player.position @@ -17,9 +18,9 @@ func _process(_delta): var move := server_position - player.position move.y = 0.0 - if move.length_squared() < 0.01: - player.set_direction(Vector3.ZERO) + if move.length_squared() < 0.02: + direction_changed.emit(Vector3.ZERO) return var direction := Vector3(move.x, 0, move.z).normalized() - player.set_direction(direction) \ No newline at end of file + direction_changed.emit(direction) \ No newline at end of file diff --git a/client/player/hud/HealthBar.gd b/client/player/hud/HealthBar.gd index c9ef7b8..f773c09 100644 --- a/client/player/hud/HealthBar.gd +++ b/client/player/hud/HealthBar.gd @@ -9,7 +9,7 @@ var health: HealthComponent func _ready(): health = owner.health health.value_changed.connect(on_health_changed) - + func on_health_changed(): value = health.value / health.max_value diff --git a/client/player/movement/MovementComponent.gd b/client/player/movement/MovementComponent.gd index 218c4af..cef2295 100644 --- a/client/player/movement/MovementComponent.gd +++ b/client/player/movement/MovementComponent.gd @@ -5,18 +5,14 @@ extends Node @export var jump_velocity := 4.5 @export var deceleration := 0.75 -var body: CharacterBody3D +var body: Character var direction: Vector3 var gravity: float func _ready(): - if owner.has_signal("direction_changed"): - owner.direction_changed.connect(on_direction_changed) - - if owner.has_signal("jumped"): - owner.jumped.connect(jump) - - body = owner as CharacterBody3D + body = owner as Character + body.controller.direction_changed.connect(on_direction_changed) + body.controller.jumped.connect(jump) gravity = ProjectSettings.get_setting("physics/3d/default_gravity") func _physics_process(delta): diff --git a/client/player/rotation/RotationComponent.gd b/client/player/rotation/RotationComponent.gd index 6e616e1..c448071 100644 --- a/client/player/rotation/RotationComponent.gd +++ b/client/player/rotation/RotationComponent.gd @@ -9,17 +9,13 @@ var angle: float func _ready(): assert(root, "Rotation root needs to be set") - - if owner.has_signal("direction_changed"): - owner.direction_changed.connect(on_direction_changed) - - update_configuration_warnings() + owner.controller.direction_changed.connect(on_direction_changed) func _process(delta): - if direction != Vector3.ZERO: - angle = atan2(direction.x, direction.z) - root.rotation.y = lerp_angle(root.rotation.y, angle, rotation_speed * delta) func on_direction_changed(new_direction: Vector3): - direction = new_direction \ No newline at end of file + direction = new_direction + + if direction != Vector3.ZERO: + angle = atan2(direction.x, direction.z) \ No newline at end of file diff --git a/client/player/skills/SkillsComponent.gd b/client/player/skills/SkillsComponent.gd index 5a38e74..fe47e82 100644 --- a/client/player/skills/SkillsComponent.gd +++ b/client/player/skills/SkillsComponent.gd @@ -1,15 +1,20 @@ class_name SkillsComponent extends Node -var player: Player +@export var skills: Array[Skill] func _ready(): - player = owner - player.skill_used.connect(use_skill) + var character := owner as Character + character.controller.used_skill.connect(use_skill) + +func use_skill(slot: int): + if slot < 0 || slot >= skills.size(): + return + + var skill := skills[slot] -func use_skill(skill: Skill): if !skill: return var scene := skill.scene.instantiate() - player.add_child(scene) + owner.add_child(scene) diff --git a/client/player/state/StateComponent.gd b/client/player/state/StateComponent.gd index 84efb26..21ea77f 100644 --- a/client/player/state/StateComponent.gd +++ b/client/player/state/StateComponent.gd @@ -17,7 +17,7 @@ var _current: State func _ready(): current = State.Idle - movement = owner.find_child("Movement") + movement = owner.get_node("Movement") func _process(_delta): if current == State.Skill: