From 80a65fa555ba0a902b6e23408e43c83d2cfa92a7 Mon Sep 17 00:00:00 2001 From: Marek Le Date: Sun, 29 Mar 2026 22:58:23 +0200 Subject: [PATCH] update --- plan.md | 30 +++++-- scenes/enemy/enemy.tscn | 2 +- scenes/player/player.tscn | 26 +++---- scenes/portal/portal.tscn | 78 +++++++++++++++++++ scenes/world.tscn | 15 +--- .../healthbar.gd} | 18 +++-- scripts/components/healthbar.gd.uid | 1 + scripts/enemy/enemy.gd | 4 +- scripts/enemy/enemy_aggro.gd | 21 ++++- scripts/enemy/enemy_healthbar.gd.uid | 1 - scripts/enemy/enemy_movement.gd | 24 +++--- scripts/event_bus.gd | 1 + scripts/player/combat.gd | 2 +- scripts/player/hud.gd | 2 +- scripts/player/targeting.gd | 14 ++-- scripts/portal/portal.gd | 4 + scripts/portal/portal.gd.uid | 1 + scripts/portal/portal_spawner.gd | 30 +++++++ scripts/portal/portal_spawner.gd.uid | 1 + 19 files changed, 217 insertions(+), 58 deletions(-) create mode 100644 scenes/portal/portal.tscn rename scripts/{enemy/enemy_healthbar.gd => components/healthbar.gd} (68%) create mode 100644 scripts/components/healthbar.gd.uid delete mode 100644 scripts/enemy/enemy_healthbar.gd.uid create mode 100644 scripts/portal/portal.gd create mode 100644 scripts/portal/portal.gd.uid create mode 100644 scripts/portal/portal_spawner.gd create mode 100644 scripts/portal/portal_spawner.gd.uid diff --git a/plan.md b/plan.md index 0a6017b..68089e8 100644 --- a/plan.md +++ b/plan.md @@ -2,7 +2,8 @@ ## Szenenbaum - Welt - Player - - Gegner + - Portal + - Gegner - HUD ## Architektur @@ -16,6 +17,7 @@ - Kollision (StaticBody3D, WorldBoundaryShape3D) - Licht (DirectionalLight3D, 45°, Schatten) - Spieler (Instanz von player.tscn) + - Portal (Instanz von portal.tscn) - HUD (Instanz von hud.tscn) - player.tscn — Spieler @@ -41,6 +43,19 @@ - ClassIcon (Label, Klassen-Symbol: T=Tank, D=Schaden, H=Heiler) - 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 - CharacterBody3D (enemy.gd) - Kollision (CapsuleShape3D) @@ -55,7 +70,7 @@ - EnemyMovement (Node, enemy_movement.gd) - EnemyCombat (Node, enemy_combat.gd) - EnemyAggro (Node, enemy_aggro.gd) - - Healthbar (Sprite3D + SubViewport, über dem Gegner, enemy_healthbar.gd) + - Healthbar (Sprite3D + SubViewport, über dem Gegner, healthbar.gd) - SubViewport - Border (ColorRect, gelb, sichtbar bei Anvisierung) - HealthBar (ProgressBar, grün) @@ -71,9 +86,11 @@ - 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD - 4 Ult: 4x Single + 2x AOE um Ziel, 30s CD, GCD - 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 -- 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_combat.gd — Angriff über Event (damage_requested) - enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro @@ -81,13 +98,15 @@ - Heilung = Aggro (0.5x) - Tank = Aggro-Multiplikator (2x) - 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 - health.gd — Leben, 1/s Regeneration, Tod bei 0 (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 - respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll - 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) - ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute() @@ -113,3 +132,4 @@ - respawn_tick(timer) — Respawn-Countdown Update - enemy_engaged(enemy, target) — Gegner hat Spieler anvisiert - cooldown_tick(cooldowns, max_cooldowns, gcd_timer) — Cooldown-Update für HUD +- portal_spawn(portal, enemies) — Portal hat Gegner gespawnt diff --git a/scenes/enemy/enemy.tscn b/scenes/enemy/enemy.tscn index 1150365..763a532 100644 --- a/scenes/enemy/enemy.tscn +++ b/scenes/enemy/enemy.tscn @@ -3,7 +3,7 @@ [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/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_combat.gd" id="6"] [ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"] diff --git a/scenes/player/player.tscn b/scenes/player/player.tscn index 8c0d7d2..92c55b6 100644 --- a/scenes/player/player.tscn +++ b/scenes/player/player.tscn @@ -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://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" path="res://scripts/components/health.gd" id="5"] -[ext_resource type="Script" path="res://scripts/components/shield.gd" id="6"] -[ext_resource type="Script" path="res://scripts/player/targeting.gd" id="8"] -[ext_resource type="Script" path="res://scripts/player/respawn.gd" id="9"] -[ext_resource type="Script" 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" 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="Script" uid="uid://b053b4fkkeaod" path="res://scripts/components/health.gd" id="5"] +[ext_resource type="Script" uid="uid://bpfw71oprcvou" path="res://scripts/components/shield.gd" id="6"] +[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scripts/player/targeting.gd" id="8"] +[ext_resource type="Script" uid="uid://dw3dtax5bx0of" path="res://scripts/player/respawn.gd" id="9"] +[ext_resource type="Script" uid="uid://rus4umqvvqq4" path="res://scripts/player/player_class.gd" id="10"] +[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://resources/ability_sets/tank_set.tres" id="11"] +[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://resources/ability_sets/damage_set.tres" id="12"] +[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://resources/ability_sets/healer_set.tres" id="13"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] radius = 0.3 @@ -43,19 +43,19 @@ script = ExtResource("3") [node name="Combat" type="Node" parent="." unique_id=1754235583] script = ExtResource("4") -[node name="Targeting" type="Node" parent="."] +[node name="Targeting" type="Node" parent="." unique_id=592540710] script = ExtResource("8") -[node name="Health" type="Node" parent="."] +[node name="Health" type="Node" parent="." unique_id=1872357630] script = ExtResource("5") -[node name="Shield" type="Node" parent="."] +[node name="Shield" type="Node" parent="." unique_id=716948065] script = ExtResource("6") -[node name="Respawn" type="Node" parent="."] +[node name="Respawn" type="Node" parent="." unique_id=1562314386] script = ExtResource("9") -[node name="PlayerClass" type="Node" parent="."] +[node name="PlayerClass" type="Node" parent="." unique_id=134158295] script = ExtResource("10") tank_set = ExtResource("11") damage_set = ExtResource("12") diff --git a/scenes/portal/portal.tscn b/scenes/portal/portal.tscn new file mode 100644 index 0000000..04dbff9 --- /dev/null +++ b/scenes/portal/portal.tscn @@ -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 diff --git a/scenes/world.tscn b/scenes/world.tscn index 8bb599a..c3b8d8d 100644 --- a/scenes/world.tscn +++ b/scenes/world.tscn @@ -1,7 +1,7 @@ [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/portal/portal.tscn" id="portal"] [ext_resource type="PackedScene" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"] [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="Enemy1" parent="." unique_id=1152011787 instance=ExtResource("enemy")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.75, 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) +[node name="Portal" parent="." instance=ExtResource("portal")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5) diff --git a/scripts/enemy/enemy_healthbar.gd b/scripts/components/healthbar.gd similarity index 68% rename from scripts/enemy/enemy_healthbar.gd rename to scripts/components/healthbar.gd index 0638e53..323c508 100644 --- a/scripts/enemy/enemy_healthbar.gd +++ b/scripts/components/healthbar.gd @@ -2,19 +2,24 @@ extends Sprite3D @onready var viewport: SubViewport = $SubViewport @onready var health_bar: ProgressBar = $SubViewport/HealthBar -@onready var shield_bar: ProgressBar = $SubViewport/ShieldBar @onready var border: ColorRect = $SubViewport/Border @onready var health: Node = get_parent().get_node("Health") -@onready var shield: Node = get_parent().get_node("Shield") -@onready var enemy: CharacterBody3D = get_parent() +@onready var parent_node: Node = get_parent() +var shield: Node = null +var shield_bar: ProgressBar = null var style_normal: StyleBoxFlat var style_aggro: StyleBoxFlat func _ready() -> void: texture = viewport.get_texture() health_bar.max_value = health.max_health - shield_bar.max_value = shield.max_shield + 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 + elif shield_bar: + shield_bar.visible = false border.visible = false style_normal = health_bar.get_theme_stylebox("fill").duplicate() style_aggro = style_normal.duplicate() @@ -23,9 +28,10 @@ func _ready() -> void: func _process(_delta: float) -> void: health_bar.value = health.current_health - shield_bar.value = shield.current_shield + if shield and shield_bar: + shield_bar.value = shield.current_shield 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) else: health_bar.add_theme_stylebox_override("fill", style_normal) diff --git a/scripts/components/healthbar.gd.uid b/scripts/components/healthbar.gd.uid new file mode 100644 index 0000000..572c7af --- /dev/null +++ b/scripts/components/healthbar.gd.uid @@ -0,0 +1 @@ +uid://d1w7vm7t3k3ts diff --git a/scripts/enemy/enemy.gd b/scripts/enemy/enemy.gd index 2e9e29a..11520a9 100644 --- a/scripts/enemy/enemy.gd +++ b/scripts/enemy/enemy.gd @@ -5,6 +5,7 @@ enum State { IDLE, CHASE, ATTACK, RETURN } 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") @onready var health: Node = $Health @@ -12,6 +13,7 @@ var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") func _ready() -> void: spawn_position = global_position add_to_group("enemies") + add_to_group("targetable") EventBus.entity_died.connect(_on_entity_died) func _on_entity_died(entity: Node) -> void: @@ -39,7 +41,7 @@ func _engage(new_target: Node3D) -> void: func _alert_nearby() -> void: var enemies := get_tree().get_nodes_in_group("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: var dist: float = global_position.distance_to(enemy.global_position) if dist <= 3.0: diff --git a/scripts/enemy/enemy_aggro.gd b/scripts/enemy/enemy_aggro.gd index 99d5a23..4b18132 100644 --- a/scripts/enemy/enemy_aggro.gd +++ b/scripts/enemy/enemy_aggro.gd @@ -1,7 +1,9 @@ extends Node const AGGRO_DECAY := 1.0 +const PORTAL_RADIUS := 10.0 var aggro_table: Dictionary = {} +var seconds_outside := 0.0 @onready var enemy: CharacterBody3D = get_parent() @@ -10,15 +12,32 @@ func _ready() -> void: EventBus.entity_died.connect(_on_entity_died) 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(): - 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: aggro_table.erase(player) + var top_target: Node = _get_top_target() if top_target and top_target != enemy.target: enemy.target = top_target if enemy.state == enemy.State.IDLE or enemy.state == enemy.State.RETURN: 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: if target != enemy: diff --git a/scripts/enemy/enemy_healthbar.gd.uid b/scripts/enemy/enemy_healthbar.gd.uid deleted file mode 100644 index 4524b26..0000000 --- a/scripts/enemy/enemy_healthbar.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dwqx03nfypa7u diff --git a/scripts/enemy/enemy_movement.gd b/scripts/enemy/enemy_movement.gd index 23dad80..fb46ed3 100644 --- a/scripts/enemy/enemy_movement.gd +++ b/scripts/enemy/enemy_movement.gd @@ -1,13 +1,14 @@ extends Node const SPEED := 3.0 -const LEASH_RANGE := 15.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: +func _physics_process(delta: float) -> void: match enemy.state: enemy.State.IDLE: enemy.velocity.x = 0 @@ -15,17 +16,12 @@ func _physics_process(_delta: float) -> void: enemy.State.CHASE: _chase() enemy.State.RETURN: - _return_to_spawn() + _return_to_spawn(delta) func _chase() -> void: if not is_instance_valid(enemy.target): enemy.state = enemy.State.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) if dist_to_target <= ATTACK_RANGE: enemy.state = enemy.State.ATTACK @@ -37,7 +33,7 @@ func _chase() -> void: enemy.velocity.x = direction.x * 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) if dist < 1.0: enemy.state = enemy.State.IDLE @@ -50,3 +46,13 @@ func _return_to_spawn() -> void: direction.y = 0 enemy.velocity.x = direction.x * 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) diff --git a/scripts/event_bus.gd b/scripts/event_bus.gd index a2ed436..a742dde 100644 --- a/scripts/event_bus.gd +++ b/scripts/event_bus.gd @@ -14,3 +14,4 @@ signal shield_changed(entity, current, max_val) signal respawn_tick(timer) signal enemy_engaged(enemy, target) signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer) +signal portal_spawn(portal, enemies) diff --git a/scripts/player/combat.gd b/scripts/player/combat.gd index ef43bbe..e9e6002 100644 --- a/scripts/player/combat.gd +++ b/scripts/player/combat.gd @@ -1,6 +1,6 @@ extends Node -const GCD_TIME := 1.0 +const GCD_TIME := 0.5 @onready var player: CharacterBody3D = get_parent() @onready var targeting: Node = get_parent().get_node("Targeting") diff --git a/scripts/player/hud.gd b/scripts/player/hud.gd index cdba447..b646579 100644 --- a/scripts/player/hud.gd +++ b/scripts/player/hud.gd @@ -1,6 +1,6 @@ extends CanvasLayer -const GCD_TIME := 1.0 +const GCD_TIME := 0.5 @onready var health_bar: ProgressBar = $HealthBar @onready var shield_bar: ProgressBar = $ShieldBar diff --git a/scripts/player/targeting.gd b/scripts/player/targeting.gd index 8c8736b..034aa69 100644 --- a/scripts/player/targeting.gd +++ b/scripts/player/targeting.gd @@ -49,16 +49,16 @@ func _try_target_under_mouse(mouse_pos: Vector2) -> void: set_target(null) func _cycle_target() -> void: - var enemies := get_tree().get_nodes_in_group("enemies") - if enemies.is_empty(): + var targets := get_tree().get_nodes_in_group("targetable") + if targets.is_empty(): set_target(null) return - if current_target == null or current_target not in enemies: - set_target(enemies[0]) + if current_target == null or current_target not in targets: + set_target(targets[0]) return - var idx := enemies.find(current_target) - var next_idx := (idx + 1) % enemies.size() - set_target(enemies[next_idx]) + var idx := targets.find(current_target) + var next_idx := (idx + 1) % targets.size() + set_target(targets[next_idx]) func set_target(target: Node3D) -> void: current_target = target diff --git a/scripts/portal/portal.gd b/scripts/portal/portal.gd new file mode 100644 index 0000000..8813e19 --- /dev/null +++ b/scripts/portal/portal.gd @@ -0,0 +1,4 @@ +extends StaticBody3D + +func _ready() -> void: + add_to_group("targetable") diff --git a/scripts/portal/portal.gd.uid b/scripts/portal/portal.gd.uid new file mode 100644 index 0000000..06e64cd --- /dev/null +++ b/scripts/portal/portal.gd.uid @@ -0,0 +1 @@ +uid://byjxj4mq84gki diff --git a/scripts/portal/portal_spawner.gd b/scripts/portal/portal_spawner.gd new file mode 100644 index 0000000..3200847 --- /dev/null +++ b/scripts/portal/portal_spawner.gd @@ -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) diff --git a/scripts/portal/portal_spawner.gd.uid b/scripts/portal/portal_spawner.gd.uid new file mode 100644 index 0000000..635cafd --- /dev/null +++ b/scripts/portal/portal_spawner.gd.uid @@ -0,0 +1 @@ +uid://begrg74oh76pu