This commit is contained in:
Marek Lenczewski
2026-04-02 16:02:24 +02:00
parent e76c66eda6
commit 47f4fe3d90
106 changed files with 434 additions and 204 deletions

5
enemy/boss.gd Normal file
View File

@@ -0,0 +1,5 @@
extends "res://enemy/enemy.gd"
func _ready() -> void:
super._ready()
add_to_group("boss")

1
enemy/boss.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bkehq3dqyp2yd

108
enemy/boss.tscn Normal file
View File

@@ -0,0 +1,108 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://enemy/boss.gd" id="1"]
[ext_resource type="Script" path="res://components/healthbar.gd" id="4"]
[ext_resource type="Script" path="res://enemy/enemy_movement.gd" id="5"]
[ext_resource type="Resource" path="res://resources/stats/boss_stats.tres" id="8"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 0.6
height = 2.0
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"]
radius = 0.6
height = 2.0
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
bg_color = Color(0.3, 0.1, 0.1, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
bg_color = Color(0.2, 0.8, 0.2, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"]
bg_color = Color(0.1, 0.1, 0.3, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
bg_color = Color(0.2, 0.5, 0.9, 1)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.6, 0.1, 0.6, 1)
[sub_resource type="SphereMesh" id="SphereMesh_1"]
radius = 0.75
height = 1.5
material = SubResource("StandardMaterial3D_1")
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
radius = 8.0
[node name="Boss" type="CharacterBody3D"]
script = ExtResource("1")
stats = ExtResource("8")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_1")
[node name="Mesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, 0, 0, 0)
mesh = SubResource("SphereMesh_1")
[node name="HitArea" type="Area3D" parent="."]
collision_layer = 4
collision_mask = 0
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"]
shape = SubResource("CapsuleShape3D_2")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
[node name="EnemyMovement" type="Node" parent="."]
script = ExtResource("5")
[node name="DetectionArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 1
monitoring = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea"]
shape = SubResource("SphereShape3D_1")
[node name="Healthbar" type="Sprite3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.0, 0)
billboard = 1
pixel_size = 0.01
script = ExtResource("4")
[node name="SubViewport" type="SubViewport" parent="Healthbar"]
transparent_bg = true
size = Vector2i(104, 29)
[node name="Border" type="ColorRect" parent="Healthbar/SubViewport"]
offset_right = 104.0
offset_bottom = 29.0
color = Color(1, 0.9, 0.2, 1)
[node name="HealthBar" type="ProgressBar" parent="Healthbar/SubViewport"]
offset_left = 2.0
offset_top = 2.0
offset_right = 102.0
offset_bottom = 12.0
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
max_value = 500.0
value = 500.0
show_percentage = false
[node name="ShieldBar" type="ProgressBar" parent="Healthbar/SubViewport"]
offset_left = 2.0
offset_top = 15.0
offset_right = 102.0
offset_bottom = 27.0
theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg")
theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill")
max_value = 100.0
value = 100.0
show_percentage = false
[connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"]
[connection signal="body_exited" from="DetectionArea" to="." method="_on_detection_area_body_exited"]

36
enemy/enemy.gd Normal file
View File

@@ -0,0 +1,36 @@
extends CharacterBody3D
enum State { IDLE, CHASE, ATTACK, RETURN }
@export var stats: BaseStats
var state: int = State.IDLE
var target: Node3D = null
var spawn_position: Vector3
var portal: Node3D = null
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
func _ready() -> void:
spawn_position = global_position
add_to_group("enemies")
Stats.register(self, stats)
EventBus.entity_died.connect(_on_entity_died)
func _exit_tree() -> void:
Stats.deregister(self)
func _on_entity_died(entity: Node) -> void:
if entity == self:
queue_free()
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y -= gravity * delta
move_and_slide()
func _on_detection_area_body_entered(body: Node3D) -> void:
if body is CharacterBody3D and body.name == "Player":
EventBus.enemy_detected.emit(self, body)
func _on_detection_area_body_exited(_body: Node3D) -> void:
pass

1
enemy/enemy.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bwi75jx0agktd

107
enemy/enemy.tscn Normal file
View File

@@ -0,0 +1,107 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://enemy/enemy.gd" id="1"]
[ext_resource type="Script" path="res://components/healthbar.gd" id="4"]
[ext_resource type="Script" path="res://enemy/enemy_movement.gd" id="5"]
[ext_resource type="Resource" path="res://resources/stats/enemy_stats.tres" id="8"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 0.4
height = 1.5
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"]
radius = 0.4
height = 1.5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
bg_color = Color(0.3, 0.1, 0.1, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
bg_color = Color(0.2, 0.8, 0.2, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"]
bg_color = Color(0.1, 0.1, 0.3, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
bg_color = Color(0.2, 0.5, 0.9, 1)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
albedo_color = Color(0.8, 0.1, 0.1, 1)
[sub_resource type="SphereMesh" id="SphereMesh_1"]
radius = 0.5
height = 1.0
material = SubResource("StandardMaterial3D_1")
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
radius = 8.0
[node name="Enemy" type="CharacterBody3D"]
script = ExtResource("1")
stats = ExtResource("8")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_1")
[node name="Mesh" type="MeshInstance3D" parent="."]
mesh = SubResource("SphereMesh_1")
[node name="HitArea" type="Area3D" parent="."]
collision_layer = 4
collision_mask = 0
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"]
shape = SubResource("CapsuleShape3D_2")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
[node name="EnemyMovement" type="Node" parent="."]
script = ExtResource("5")
[node name="DetectionArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 1
monitoring = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea"]
shape = SubResource("SphereShape3D_1")
[node name="Healthbar" type="Sprite3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
billboard = 1
pixel_size = 0.01
script = ExtResource("4")
[node name="SubViewport" type="SubViewport" parent="Healthbar"]
transparent_bg = true
size = Vector2i(104, 29)
[node name="Border" type="ColorRect" parent="Healthbar/SubViewport"]
offset_right = 104.0
offset_bottom = 29.0
color = Color(1, 0.9, 0.2, 1)
[node name="HealthBar" type="ProgressBar" parent="Healthbar/SubViewport"]
offset_left = 2.0
offset_top = 2.0
offset_right = 102.0
offset_bottom = 12.0
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
max_value = 100.0
value = 100.0
show_percentage = false
[node name="ShieldBar" type="ProgressBar" parent="Healthbar/SubViewport"]
offset_left = 2.0
offset_top = 15.0
offset_right = 102.0
offset_bottom = 27.0
theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg")
theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill")
max_value = 50.0
value = 50.0
show_percentage = false
[connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"]
[connection signal="body_exited" from="DetectionArea" to="." method="_on_detection_area_body_exited"]

62
enemy/enemy_movement.gd Normal file
View File

@@ -0,0 +1,62 @@
extends Node
const SPEED := 3.0
const ATTACK_RANGE := 2.0
const REGEN_FAST := 0.10
const REGEN_SLOW := 0.01
@onready var enemy: CharacterBody3D = get_parent()
@onready var nav_agent: NavigationAgent3D = get_parent().get_node("NavigationAgent3D")
func _physics_process(delta: float) -> void:
match enemy.state:
enemy.State.IDLE:
enemy.velocity.x = 0
enemy.velocity.z = 0
enemy.State.CHASE:
_chase()
enemy.State.RETURN:
_return_to_spawn(delta)
func _chase() -> void:
if not is_instance_valid(enemy.target):
enemy.state = enemy.State.RETURN
return
var dist_to_target := enemy.global_position.distance_to(enemy.target.global_position)
if dist_to_target <= ATTACK_RANGE:
enemy.state = enemy.State.ATTACK
return
nav_agent.target_position = enemy.target.global_position
var next_pos := nav_agent.get_next_path_position()
var direction := (next_pos - enemy.global_position).normalized()
direction.y = 0
enemy.velocity.x = direction.x * SPEED
enemy.velocity.z = direction.z * SPEED
func _return_to_spawn(delta: float) -> void:
var dist := enemy.global_position.distance_to(enemy.spawn_position)
if dist < 1.0:
enemy.state = enemy.State.IDLE
enemy.velocity.x = 0
enemy.velocity.z = 0
return
nav_agent.target_position = enemy.spawn_position
var next_pos := nav_agent.get_next_path_position()
var direction := (next_pos - enemy.global_position).normalized()
direction.y = 0
enemy.velocity.x = direction.x * SPEED
enemy.velocity.z = direction.z * SPEED
_regenerate(delta)
func _regenerate(delta: float) -> void:
var health: float = Stats.get_stat(enemy, "health")
var max_health: float = Stats.get_stat(enemy, "max_health")
if health == null or max_health == null:
return
if health < max_health:
var rate: float = REGEN_FAST
if health >= max_health * 0.99:
rate = REGEN_SLOW
health = min(health + max_health * rate * delta, max_health)
Stats.set_stat(enemy, "health", health)
EventBus.health_changed.emit(enemy, health, max_health)

View File

@@ -0,0 +1 @@
uid://tnx6rbnnngn