update
This commit is contained in:
28
plan.md
28
plan.md
@@ -2,6 +2,7 @@
|
|||||||
## Szenenbaum
|
## Szenenbaum
|
||||||
- Welt
|
- Welt
|
||||||
- Player
|
- Player
|
||||||
|
- Portal
|
||||||
- Gegner
|
- Gegner
|
||||||
- HUD
|
- HUD
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
- Kollision (StaticBody3D, WorldBoundaryShape3D)
|
- Kollision (StaticBody3D, WorldBoundaryShape3D)
|
||||||
- Licht (DirectionalLight3D, 45°, Schatten)
|
- Licht (DirectionalLight3D, 45°, Schatten)
|
||||||
- Spieler (Instanz von player.tscn)
|
- Spieler (Instanz von player.tscn)
|
||||||
|
- Portal (Instanz von portal.tscn)
|
||||||
- HUD (Instanz von hud.tscn)
|
- HUD (Instanz von hud.tscn)
|
||||||
|
|
||||||
- player.tscn — Spieler
|
- player.tscn — Spieler
|
||||||
@@ -41,6 +43,19 @@
|
|||||||
- ClassIcon (Label, Klassen-Symbol: T=Tank, D=Schaden, H=Heiler)
|
- ClassIcon (Label, Klassen-Symbol: T=Tank, D=Schaden, H=Heiler)
|
||||||
- Ability 1-5
|
- Ability 1-5
|
||||||
|
|
||||||
|
- portal.tscn — Portal (Gegner-Spawner, anvisierbar, Gruppe "targetable")
|
||||||
|
- StaticBody3D (portal.gd)
|
||||||
|
- Kollision (CylinderShape3D)
|
||||||
|
- Mesh (CylinderMesh, Platzhalter)
|
||||||
|
- Health (Node, health.gd)
|
||||||
|
- HitArea (Area3D, Trefferbereich)
|
||||||
|
- CollisionShape3D
|
||||||
|
- PortalSpawner (Node, portal_spawner.gd)
|
||||||
|
- Spawnt 3 Gegner bei 85%, 70%, 55%, 40%, 25%, 10% Leben (einmalig)
|
||||||
|
- Portal = Spawnpunkt der Gegner
|
||||||
|
- 10m Aggro-Radius um das Portal
|
||||||
|
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
||||||
|
|
||||||
- enemy.tscn — Gegner
|
- enemy.tscn — Gegner
|
||||||
- CharacterBody3D (enemy.gd)
|
- CharacterBody3D (enemy.gd)
|
||||||
- Kollision (CapsuleShape3D)
|
- Kollision (CapsuleShape3D)
|
||||||
@@ -55,7 +70,7 @@
|
|||||||
- EnemyMovement (Node, enemy_movement.gd)
|
- EnemyMovement (Node, enemy_movement.gd)
|
||||||
- EnemyCombat (Node, enemy_combat.gd)
|
- EnemyCombat (Node, enemy_combat.gd)
|
||||||
- EnemyAggro (Node, enemy_aggro.gd)
|
- EnemyAggro (Node, enemy_aggro.gd)
|
||||||
- Healthbar (Sprite3D + SubViewport, über dem Gegner, enemy_healthbar.gd)
|
- Healthbar (Sprite3D + SubViewport, über dem Gegner, healthbar.gd)
|
||||||
- SubViewport
|
- SubViewport
|
||||||
- Border (ColorRect, gelb, sichtbar bei Anvisierung)
|
- Border (ColorRect, gelb, sichtbar bei Anvisierung)
|
||||||
- HealthBar (ProgressBar, grün)
|
- HealthBar (ProgressBar, grün)
|
||||||
@@ -71,9 +86,11 @@
|
|||||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||||
- 4 Ult: 4x Single + 2x AOE um Ziel, 30s CD, GCD
|
- 4 Ult: 4x Single + 2x AOE um Ziel, 30s CD, GCD
|
||||||
- 5 Passive: 50% mehr Schaden (permanent aktiv, kein CD)
|
- 5 Passive: 50% mehr Schaden (permanent aktiv, kein CD)
|
||||||
- targeting.gd — Klick/TAB anvisieren, Kampfmodus bei Gegner-Angriff, Auto-Targeting auf nächsten Gegner
|
- targeting.gd — Klick/TAB anvisieren (Gruppe "targetable"), Kampfmodus bei Gegner-Angriff, Auto-Targeting auf nächsten Gegner
|
||||||
- event_bus.gd — Autoload-Singleton, globale Signals
|
- event_bus.gd — Autoload-Singleton, globale Signals
|
||||||
- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m
|
- portal.gd — Portal-Kern, Gruppe "targetable", kein Kampf/Aggro/State
|
||||||
|
- portal_spawner.gd — Spawnt Gegner bei Lebensschwellen, setzt Portal als Spawnpunkt
|
||||||
|
- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m, Gruppe "enemies" + "targetable"
|
||||||
- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt
|
- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt
|
||||||
- enemy_combat.gd — Angriff über Event (damage_requested)
|
- enemy_combat.gd — Angriff über Event (damage_requested)
|
||||||
- enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro
|
- enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro
|
||||||
@@ -81,13 +98,15 @@
|
|||||||
- Heilung = Aggro (0.5x)
|
- Heilung = Aggro (0.5x)
|
||||||
- Tank = Aggro-Multiplikator (2x)
|
- Tank = Aggro-Multiplikator (2x)
|
||||||
- Aggro verfällt -1/s
|
- Aggro verfällt -1/s
|
||||||
|
- Außerhalb 10m Portal-Radius: Aggro verfällt 1% * 2 je s (1%, 2%, 4%, 8%, ...)
|
||||||
|
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert 10% Leben/s bis 100%, dann 1%/s
|
||||||
- Bei Spieler-Tod → Aggro auf 0
|
- Bei Spieler-Tod → Aggro auf 0
|
||||||
- health.gd — Leben, 1/s Regeneration, Tod bei 0 (wiederverwendbar)
|
- health.gd — Leben, 1/s Regeneration, Tod bei 0 (wiederverwendbar)
|
||||||
- shield.gd — Schild, regeneriert nach 3s ohne Schaden, in 5s voll (wiederverwendbar)
|
- shield.gd — Schild, regeneriert nach 3s ohne Schaden, in 5s voll (wiederverwendbar)
|
||||||
- player_class.gd — Klassenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet
|
- player_class.gd — Klassenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet
|
||||||
- respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll
|
- respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll
|
||||||
- hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer
|
- hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer
|
||||||
- enemy_healthbar.gd — Liest Health/Shield vom Gegner, aktualisiert Balken über dem Gegner, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist
|
- healthbar.gd — Liest Health/Shield (optional) vom Parent, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist (nur bei Gegnern)
|
||||||
|
|
||||||
## Abilities (Resources)
|
## Abilities (Resources)
|
||||||
- ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute()
|
- ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute()
|
||||||
@@ -113,3 +132,4 @@
|
|||||||
- respawn_tick(timer) — Respawn-Countdown Update
|
- respawn_tick(timer) — Respawn-Countdown Update
|
||||||
- enemy_engaged(enemy, target) — Gegner hat Spieler anvisiert
|
- enemy_engaged(enemy, target) — Gegner hat Spieler anvisiert
|
||||||
- cooldown_tick(cooldowns, max_cooldowns, gcd_timer) — Cooldown-Update für HUD
|
- cooldown_tick(cooldowns, max_cooldowns, gcd_timer) — Cooldown-Update für HUD
|
||||||
|
- portal_spawn(portal, enemies) — Portal hat Gegner gespawnt
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy.gd" id="1"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy.gd" id="1"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/shield.gd" id="3"]
|
[ext_resource type="Script" path="res://scripts/components/shield.gd" id="3"]
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_healthbar.gd" id="4"]
|
[ext_resource type="Script" path="res://scripts/components/healthbar.gd" id="4"]
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_movement.gd" id="5"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy_movement.gd" id="5"]
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_combat.gd" id="6"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy_combat.gd" id="6"]
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"]
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
[ext_resource type="Script" uid="uid://cohjyjge1kqxb" path="res://scripts/player/camera.gd" id="2"]
|
[ext_resource type="Script" uid="uid://cohjyjge1kqxb" path="res://scripts/player/camera.gd" id="2"]
|
||||||
[ext_resource type="Script" uid="uid://fg87dh8fbc8" path="res://scripts/player/movement.gd" id="3"]
|
[ext_resource type="Script" uid="uid://fg87dh8fbc8" path="res://scripts/player/movement.gd" id="3"]
|
||||||
[ext_resource type="Script" uid="uid://d15til6fsxw5b" path="res://scripts/player/combat.gd" id="4"]
|
[ext_resource type="Script" uid="uid://d15til6fsxw5b" path="res://scripts/player/combat.gd" id="4"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/health.gd" id="5"]
|
[ext_resource type="Script" uid="uid://b053b4fkkeaod" path="res://scripts/components/health.gd" id="5"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/shield.gd" id="6"]
|
[ext_resource type="Script" uid="uid://bpfw71oprcvou" path="res://scripts/components/shield.gd" id="6"]
|
||||||
[ext_resource type="Script" path="res://scripts/player/targeting.gd" id="8"]
|
[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scripts/player/targeting.gd" id="8"]
|
||||||
[ext_resource type="Script" path="res://scripts/player/respawn.gd" id="9"]
|
[ext_resource type="Script" uid="uid://dw3dtax5bx0of" path="res://scripts/player/respawn.gd" id="9"]
|
||||||
[ext_resource type="Script" path="res://scripts/player/player_class.gd" id="10"]
|
[ext_resource type="Script" uid="uid://rus4umqvvqq4" path="res://scripts/player/player_class.gd" id="10"]
|
||||||
[ext_resource type="Resource" path="res://resources/ability_sets/tank_set.tres" id="11"]
|
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://resources/ability_sets/tank_set.tres" id="11"]
|
||||||
[ext_resource type="Resource" path="res://resources/ability_sets/damage_set.tres" id="12"]
|
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://resources/ability_sets/damage_set.tres" id="12"]
|
||||||
[ext_resource type="Resource" path="res://resources/ability_sets/healer_set.tres" id="13"]
|
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://resources/ability_sets/healer_set.tres" id="13"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||||
radius = 0.3
|
radius = 0.3
|
||||||
@@ -43,19 +43,19 @@ script = ExtResource("3")
|
|||||||
[node name="Combat" type="Node" parent="." unique_id=1754235583]
|
[node name="Combat" type="Node" parent="." unique_id=1754235583]
|
||||||
script = ExtResource("4")
|
script = ExtResource("4")
|
||||||
|
|
||||||
[node name="Targeting" type="Node" parent="."]
|
[node name="Targeting" type="Node" parent="." unique_id=592540710]
|
||||||
script = ExtResource("8")
|
script = ExtResource("8")
|
||||||
|
|
||||||
[node name="Health" type="Node" parent="."]
|
[node name="Health" type="Node" parent="." unique_id=1872357630]
|
||||||
script = ExtResource("5")
|
script = ExtResource("5")
|
||||||
|
|
||||||
[node name="Shield" type="Node" parent="."]
|
[node name="Shield" type="Node" parent="." unique_id=716948065]
|
||||||
script = ExtResource("6")
|
script = ExtResource("6")
|
||||||
|
|
||||||
[node name="Respawn" type="Node" parent="."]
|
[node name="Respawn" type="Node" parent="." unique_id=1562314386]
|
||||||
script = ExtResource("9")
|
script = ExtResource("9")
|
||||||
|
|
||||||
[node name="PlayerClass" type="Node" parent="."]
|
[node name="PlayerClass" type="Node" parent="." unique_id=134158295]
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
tank_set = ExtResource("11")
|
tank_set = ExtResource("11")
|
||||||
damage_set = ExtResource("12")
|
damage_set = ExtResource("12")
|
||||||
|
|||||||
78
scenes/portal/portal.tscn
Normal file
78
scenes/portal/portal.tscn
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/portal/portal.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/components/healthbar.gd" id="3"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/portal/portal_spawner.gd" id="4"]
|
||||||
|
|
||||||
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_1"]
|
||||||
|
radius = 1.0
|
||||||
|
height = 2.0
|
||||||
|
|
||||||
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_2"]
|
||||||
|
radius = 1.0
|
||||||
|
height = 2.0
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||||
|
albedo_color = Color(0.5, 0.1, 0.8, 1)
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_1"]
|
||||||
|
top_radius = 1.0
|
||||||
|
bottom_radius = 1.0
|
||||||
|
height = 2.0
|
||||||
|
material = SubResource("StandardMaterial3D_1")
|
||||||
|
|
||||||
|
[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)
|
||||||
|
|
||||||
|
[node name="Portal" type="StaticBody3D"]
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
|
shape = SubResource("CylinderShape3D_1")
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||||
|
mesh = SubResource("CylinderMesh_1")
|
||||||
|
|
||||||
|
[node name="Health" type="Node" parent="."]
|
||||||
|
script = ExtResource("2")
|
||||||
|
max_health = 500.0
|
||||||
|
|
||||||
|
[node name="HitArea" type="Area3D" parent="."]
|
||||||
|
collision_layer = 4
|
||||||
|
collision_mask = 0
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"]
|
||||||
|
shape = SubResource("CylinderShape3D_2")
|
||||||
|
|
||||||
|
[node name="PortalSpawner" type="Node" parent="."]
|
||||||
|
script = ExtResource("4")
|
||||||
|
|
||||||
|
[node name="Healthbar" type="Sprite3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
||||||
|
billboard = 1
|
||||||
|
pixel_size = 0.01
|
||||||
|
script = ExtResource("3")
|
||||||
|
|
||||||
|
[node name="SubViewport" type="SubViewport" parent="Healthbar"]
|
||||||
|
transparent_bg = true
|
||||||
|
size = Vector2i(104, 15)
|
||||||
|
|
||||||
|
[node name="Border" type="ColorRect" parent="Healthbar/SubViewport"]
|
||||||
|
offset_right = 104.0
|
||||||
|
offset_bottom = 15.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 = 13.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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
|
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" path="res://scenes/enemy/enemy.tscn" id="enemy"]
|
|
||||||
[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"]
|
[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/portal/portal.tscn" id="portal"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"]
|
[ext_resource type="PackedScene" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"]
|
||||||
|
|
||||||
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||||
@@ -51,14 +51,5 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7, 1, -7)
|
|||||||
|
|
||||||
[node name="HUD" parent="." unique_id=24362518 instance=ExtResource("hud")]
|
[node name="HUD" parent="." unique_id=24362518 instance=ExtResource("hud")]
|
||||||
|
|
||||||
[node name="Enemy1" parent="." unique_id=1152011787 instance=ExtResource("enemy")]
|
[node name="Portal" parent="." instance=ExtResource("portal")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.75, 5)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5)
|
||||||
|
|
||||||
[node name="Enemy2" parent="." unique_id=877649363 instance=ExtResource("enemy")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7, 0.75, 5)
|
|
||||||
|
|
||||||
[node name="Enemy3" parent="." unique_id=1831736748 instance=ExtResource("enemy")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.75, 7)
|
|
||||||
|
|
||||||
[node name="Enemy4" parent="." unique_id=1818643429 instance=ExtResource("enemy")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7, 0.75, 7)
|
|
||||||
|
|||||||
@@ -2,19 +2,24 @@ extends Sprite3D
|
|||||||
|
|
||||||
@onready var viewport: SubViewport = $SubViewport
|
@onready var viewport: SubViewport = $SubViewport
|
||||||
@onready var health_bar: ProgressBar = $SubViewport/HealthBar
|
@onready var health_bar: ProgressBar = $SubViewport/HealthBar
|
||||||
@onready var shield_bar: ProgressBar = $SubViewport/ShieldBar
|
|
||||||
@onready var border: ColorRect = $SubViewport/Border
|
@onready var border: ColorRect = $SubViewport/Border
|
||||||
@onready var health: Node = get_parent().get_node("Health")
|
@onready var health: Node = get_parent().get_node("Health")
|
||||||
@onready var shield: Node = get_parent().get_node("Shield")
|
@onready var parent_node: Node = get_parent()
|
||||||
@onready var enemy: CharacterBody3D = get_parent()
|
|
||||||
|
|
||||||
|
var shield: Node = null
|
||||||
|
var shield_bar: ProgressBar = null
|
||||||
var style_normal: StyleBoxFlat
|
var style_normal: StyleBoxFlat
|
||||||
var style_aggro: StyleBoxFlat
|
var style_aggro: StyleBoxFlat
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
texture = viewport.get_texture()
|
texture = viewport.get_texture()
|
||||||
health_bar.max_value = health.max_health
|
health_bar.max_value = health.max_health
|
||||||
|
shield = get_parent().get_node_or_null("Shield")
|
||||||
|
shield_bar = $SubViewport.get_node_or_null("ShieldBar")
|
||||||
|
if shield and shield_bar:
|
||||||
shield_bar.max_value = shield.max_shield
|
shield_bar.max_value = shield.max_shield
|
||||||
|
elif shield_bar:
|
||||||
|
shield_bar.visible = false
|
||||||
border.visible = false
|
border.visible = false
|
||||||
style_normal = health_bar.get_theme_stylebox("fill").duplicate()
|
style_normal = health_bar.get_theme_stylebox("fill").duplicate()
|
||||||
style_aggro = style_normal.duplicate()
|
style_aggro = style_normal.duplicate()
|
||||||
@@ -23,9 +28,10 @@ func _ready() -> void:
|
|||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
health_bar.value = health.current_health
|
health_bar.value = health.current_health
|
||||||
|
if shield and shield_bar:
|
||||||
shield_bar.value = shield.current_shield
|
shield_bar.value = shield.current_shield
|
||||||
var player: Node = get_tree().get_first_node_in_group("player")
|
var player: Node = get_tree().get_first_node_in_group("player")
|
||||||
if player and enemy.target == player:
|
if player and "target" in parent_node and parent_node.target == player:
|
||||||
health_bar.add_theme_stylebox_override("fill", style_aggro)
|
health_bar.add_theme_stylebox_override("fill", style_aggro)
|
||||||
else:
|
else:
|
||||||
health_bar.add_theme_stylebox_override("fill", style_normal)
|
health_bar.add_theme_stylebox_override("fill", style_normal)
|
||||||
1
scripts/components/healthbar.gd.uid
Normal file
1
scripts/components/healthbar.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://d1w7vm7t3k3ts
|
||||||
@@ -5,6 +5,7 @@ enum State { IDLE, CHASE, ATTACK, RETURN }
|
|||||||
var state: int = State.IDLE
|
var state: int = State.IDLE
|
||||||
var target: Node3D = null
|
var target: Node3D = null
|
||||||
var spawn_position: Vector3
|
var spawn_position: Vector3
|
||||||
|
var portal: Node3D = null
|
||||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||||
|
|
||||||
@onready var health: Node = $Health
|
@onready var health: Node = $Health
|
||||||
@@ -12,6 +13,7 @@ var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
spawn_position = global_position
|
spawn_position = global_position
|
||||||
add_to_group("enemies")
|
add_to_group("enemies")
|
||||||
|
add_to_group("targetable")
|
||||||
EventBus.entity_died.connect(_on_entity_died)
|
EventBus.entity_died.connect(_on_entity_died)
|
||||||
|
|
||||||
func _on_entity_died(entity: Node) -> void:
|
func _on_entity_died(entity: Node) -> void:
|
||||||
@@ -39,7 +41,7 @@ func _engage(new_target: Node3D) -> void:
|
|||||||
func _alert_nearby() -> void:
|
func _alert_nearby() -> void:
|
||||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||||
for enemy in enemies:
|
for enemy in enemies:
|
||||||
if enemy != self and is_instance_valid(enemy):
|
if enemy != self and is_instance_valid(enemy) and "state" in enemy:
|
||||||
if enemy.state == enemy.State.IDLE:
|
if enemy.state == enemy.State.IDLE:
|
||||||
var dist: float = global_position.distance_to(enemy.global_position)
|
var dist: float = global_position.distance_to(enemy.global_position)
|
||||||
if dist <= 3.0:
|
if dist <= 3.0:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const AGGRO_DECAY := 1.0
|
const AGGRO_DECAY := 1.0
|
||||||
|
const PORTAL_RADIUS := 10.0
|
||||||
var aggro_table: Dictionary = {}
|
var aggro_table: Dictionary = {}
|
||||||
|
var seconds_outside := 0.0
|
||||||
|
|
||||||
@onready var enemy: CharacterBody3D = get_parent()
|
@onready var enemy: CharacterBody3D = get_parent()
|
||||||
|
|
||||||
@@ -10,15 +12,32 @@ func _ready() -> void:
|
|||||||
EventBus.entity_died.connect(_on_entity_died)
|
EventBus.entity_died.connect(_on_entity_died)
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
var outside_portal := false
|
||||||
|
if enemy.portal and is_instance_valid(enemy.portal):
|
||||||
|
var dist_to_portal: float = enemy.global_position.distance_to(enemy.portal.global_position)
|
||||||
|
if dist_to_portal > PORTAL_RADIUS:
|
||||||
|
outside_portal = true
|
||||||
|
seconds_outside += delta
|
||||||
|
else:
|
||||||
|
seconds_outside = 0.0
|
||||||
|
|
||||||
for player in aggro_table.keys():
|
for player in aggro_table.keys():
|
||||||
aggro_table[player] -= AGGRO_DECAY * delta
|
var decay: float = AGGRO_DECAY * delta
|
||||||
|
if outside_portal:
|
||||||
|
var bonus_decay: float = aggro_table[player] * 0.01 * pow(2, seconds_outside) * delta
|
||||||
|
decay += bonus_decay
|
||||||
|
aggro_table[player] -= decay
|
||||||
if aggro_table[player] <= 0:
|
if aggro_table[player] <= 0:
|
||||||
aggro_table.erase(player)
|
aggro_table.erase(player)
|
||||||
|
|
||||||
var top_target: Node = _get_top_target()
|
var top_target: Node = _get_top_target()
|
||||||
if top_target and top_target != enemy.target:
|
if top_target and top_target != enemy.target:
|
||||||
enemy.target = top_target
|
enemy.target = top_target
|
||||||
if enemy.state == enemy.State.IDLE or enemy.state == enemy.State.RETURN:
|
if enemy.state == enemy.State.IDLE or enemy.state == enemy.State.RETURN:
|
||||||
enemy.state = enemy.State.CHASE
|
enemy.state = enemy.State.CHASE
|
||||||
|
elif not top_target and enemy.state != enemy.State.IDLE and enemy.state != enemy.State.RETURN:
|
||||||
|
enemy.target = null
|
||||||
|
enemy.state = enemy.State.RETURN
|
||||||
|
|
||||||
func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
||||||
if target != enemy:
|
if target != enemy:
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
uid://dwqx03nfypa7u
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const SPEED := 3.0
|
const SPEED := 3.0
|
||||||
const LEASH_RANGE := 15.0
|
|
||||||
const ATTACK_RANGE := 2.0
|
const ATTACK_RANGE := 2.0
|
||||||
|
const REGEN_FAST := 0.10
|
||||||
|
const REGEN_SLOW := 0.01
|
||||||
|
|
||||||
@onready var enemy: CharacterBody3D = get_parent()
|
@onready var enemy: CharacterBody3D = get_parent()
|
||||||
@onready var nav_agent: NavigationAgent3D = get_parent().get_node("NavigationAgent3D")
|
@onready var nav_agent: NavigationAgent3D = get_parent().get_node("NavigationAgent3D")
|
||||||
|
|
||||||
func _physics_process(_delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
match enemy.state:
|
match enemy.state:
|
||||||
enemy.State.IDLE:
|
enemy.State.IDLE:
|
||||||
enemy.velocity.x = 0
|
enemy.velocity.x = 0
|
||||||
@@ -15,17 +16,12 @@ func _physics_process(_delta: float) -> void:
|
|||||||
enemy.State.CHASE:
|
enemy.State.CHASE:
|
||||||
_chase()
|
_chase()
|
||||||
enemy.State.RETURN:
|
enemy.State.RETURN:
|
||||||
_return_to_spawn()
|
_return_to_spawn(delta)
|
||||||
|
|
||||||
func _chase() -> void:
|
func _chase() -> void:
|
||||||
if not is_instance_valid(enemy.target):
|
if not is_instance_valid(enemy.target):
|
||||||
enemy.state = enemy.State.RETURN
|
enemy.state = enemy.State.RETURN
|
||||||
return
|
return
|
||||||
var dist_to_spawn := enemy.global_position.distance_to(enemy.spawn_position)
|
|
||||||
if dist_to_spawn > LEASH_RANGE:
|
|
||||||
enemy.state = enemy.State.RETURN
|
|
||||||
enemy.target = null
|
|
||||||
return
|
|
||||||
var dist_to_target := enemy.global_position.distance_to(enemy.target.global_position)
|
var dist_to_target := enemy.global_position.distance_to(enemy.target.global_position)
|
||||||
if dist_to_target <= ATTACK_RANGE:
|
if dist_to_target <= ATTACK_RANGE:
|
||||||
enemy.state = enemy.State.ATTACK
|
enemy.state = enemy.State.ATTACK
|
||||||
@@ -37,7 +33,7 @@ func _chase() -> void:
|
|||||||
enemy.velocity.x = direction.x * SPEED
|
enemy.velocity.x = direction.x * SPEED
|
||||||
enemy.velocity.z = direction.z * SPEED
|
enemy.velocity.z = direction.z * SPEED
|
||||||
|
|
||||||
func _return_to_spawn() -> void:
|
func _return_to_spawn(delta: float) -> void:
|
||||||
var dist := enemy.global_position.distance_to(enemy.spawn_position)
|
var dist := enemy.global_position.distance_to(enemy.spawn_position)
|
||||||
if dist < 1.0:
|
if dist < 1.0:
|
||||||
enemy.state = enemy.State.IDLE
|
enemy.state = enemy.State.IDLE
|
||||||
@@ -50,3 +46,13 @@ func _return_to_spawn() -> void:
|
|||||||
direction.y = 0
|
direction.y = 0
|
||||||
enemy.velocity.x = direction.x * SPEED
|
enemy.velocity.x = direction.x * SPEED
|
||||||
enemy.velocity.z = direction.z * SPEED
|
enemy.velocity.z = direction.z * SPEED
|
||||||
|
_regenerate(delta)
|
||||||
|
|
||||||
|
func _regenerate(delta: float) -> void:
|
||||||
|
var health: Node = enemy.get_node("Health")
|
||||||
|
if health.current_health < health.max_health:
|
||||||
|
var rate: float = REGEN_FAST if health.current_health < health.max_health else REGEN_SLOW
|
||||||
|
if health.current_health >= health.max_health * 0.99:
|
||||||
|
rate = REGEN_SLOW
|
||||||
|
health.current_health = min(health.current_health + health.max_health * rate * delta, health.max_health)
|
||||||
|
EventBus.health_changed.emit(enemy, health.current_health, health.max_health)
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ signal shield_changed(entity, current, max_val)
|
|||||||
signal respawn_tick(timer)
|
signal respawn_tick(timer)
|
||||||
signal enemy_engaged(enemy, target)
|
signal enemy_engaged(enemy, target)
|
||||||
signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
||||||
|
signal portal_spawn(portal, enemies)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const GCD_TIME := 1.0
|
const GCD_TIME := 0.5
|
||||||
|
|
||||||
@onready var player: CharacterBody3D = get_parent()
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
@onready var targeting: Node = get_parent().get_node("Targeting")
|
@onready var targeting: Node = get_parent().get_node("Targeting")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
extends CanvasLayer
|
extends CanvasLayer
|
||||||
|
|
||||||
const GCD_TIME := 1.0
|
const GCD_TIME := 0.5
|
||||||
|
|
||||||
@onready var health_bar: ProgressBar = $HealthBar
|
@onready var health_bar: ProgressBar = $HealthBar
|
||||||
@onready var shield_bar: ProgressBar = $ShieldBar
|
@onready var shield_bar: ProgressBar = $ShieldBar
|
||||||
|
|||||||
@@ -49,16 +49,16 @@ func _try_target_under_mouse(mouse_pos: Vector2) -> void:
|
|||||||
set_target(null)
|
set_target(null)
|
||||||
|
|
||||||
func _cycle_target() -> void:
|
func _cycle_target() -> void:
|
||||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
var targets := get_tree().get_nodes_in_group("targetable")
|
||||||
if enemies.is_empty():
|
if targets.is_empty():
|
||||||
set_target(null)
|
set_target(null)
|
||||||
return
|
return
|
||||||
if current_target == null or current_target not in enemies:
|
if current_target == null or current_target not in targets:
|
||||||
set_target(enemies[0])
|
set_target(targets[0])
|
||||||
return
|
return
|
||||||
var idx := enemies.find(current_target)
|
var idx := targets.find(current_target)
|
||||||
var next_idx := (idx + 1) % enemies.size()
|
var next_idx := (idx + 1) % targets.size()
|
||||||
set_target(enemies[next_idx])
|
set_target(targets[next_idx])
|
||||||
|
|
||||||
func set_target(target: Node3D) -> void:
|
func set_target(target: Node3D) -> void:
|
||||||
current_target = target
|
current_target = target
|
||||||
|
|||||||
4
scripts/portal/portal.gd
Normal file
4
scripts/portal/portal.gd
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
extends StaticBody3D
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("targetable")
|
||||||
1
scripts/portal/portal.gd.uid
Normal file
1
scripts/portal/portal.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://byjxj4mq84gki
|
||||||
30
scripts/portal/portal_spawner.gd
Normal file
30
scripts/portal/portal_spawner.gd
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const SPAWN_COUNT := 3
|
||||||
|
var thresholds := [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||||
|
var triggered: Array[bool] = [false, false, false, false, false, false]
|
||||||
|
var enemy_scene: PackedScene = preload("res://scenes/enemy/enemy.tscn")
|
||||||
|
|
||||||
|
@onready var portal: StaticBody3D = get_parent()
|
||||||
|
@onready var health: Node = get_parent().get_node("Health")
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if health.current_health <= 0:
|
||||||
|
return
|
||||||
|
var ratio: float = health.current_health / health.max_health
|
||||||
|
for i in range(thresholds.size()):
|
||||||
|
if not triggered[i] and ratio <= thresholds[i]:
|
||||||
|
triggered[i] = true
|
||||||
|
_spawn_enemies()
|
||||||
|
|
||||||
|
func _spawn_enemies() -> void:
|
||||||
|
var spawned: Array = []
|
||||||
|
for j in range(SPAWN_COUNT):
|
||||||
|
var enemy: CharacterBody3D = enemy_scene.instantiate()
|
||||||
|
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
||||||
|
portal.get_parent().add_child(enemy)
|
||||||
|
enemy.global_position = portal.global_position + offset
|
||||||
|
enemy.spawn_position = portal.global_position
|
||||||
|
enemy.portal = portal
|
||||||
|
spawned.append(enemy)
|
||||||
|
EventBus.portal_spawn.emit(portal, spawned)
|
||||||
1
scripts/portal/portal_spawner.gd.uid
Normal file
1
scripts/portal/portal_spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://begrg74oh76pu
|
||||||
Reference in New Issue
Block a user