last init
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 995 B |
43
icon.svg.import
Normal file
43
icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://xpc0cepkwm7e"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
107
plan.md
Normal file
107
plan.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Projektstruktur
|
||||
## Szenenbaum
|
||||
- Welt
|
||||
- Player
|
||||
- Gegner
|
||||
- HUD
|
||||
|
||||
## Architektur
|
||||
- Zwischen Szenen: Kommunikation über EventBus (Szenen kennen sich nicht)
|
||||
- Innerhalb einer Szene: Modulare Skripte, Zugriff auf Geschwister-Nodes erlaubt
|
||||
|
||||
## Szenen
|
||||
- world.tscn — Hauptszene
|
||||
- NavigationRegion3D (Wegfindung für Gegner)
|
||||
- Boden (MeshInstance3D, 20x20m PlaneMesh, Gras-Noise-Textur)
|
||||
- Kollision (StaticBody3D, WorldBoundaryShape3D)
|
||||
- Licht (DirectionalLight3D, 45°, Schatten)
|
||||
- Spieler (Instanz von player.tscn)
|
||||
- HUD (Instanz von hud.tscn)
|
||||
|
||||
- player.tscn — Spieler
|
||||
- CharacterBody3D (player.gd)
|
||||
- Kollision (CapsuleShape3D, 1.8m x 0.3m)
|
||||
- Mesh (CapsuleMesh)
|
||||
- CameraPivot (Node3D, Kopfhöhe, camera.gd)
|
||||
- Camera3D (hinter/über Spieler)
|
||||
- Movement (Node, movement.gd)
|
||||
- Combat (Node, combat.gd)
|
||||
- PlayerClass (Node, player_class.gd)
|
||||
- Targeting (Node, targeting.gd)
|
||||
- Health (Node, health.gd)
|
||||
- Shield (Node, shield.gd)
|
||||
- Respawn (Node, respawn.gd)
|
||||
|
||||
- hud.tscn — HUD (eigene Szene, kommuniziert nur über Events)
|
||||
- CanvasLayer (hud.gd)
|
||||
- HealthBar (ProgressBar, links oben)
|
||||
- ShieldBar (ProgressBar, links oben, unter HealthBar)
|
||||
- RespawnTimer (Label, mitte, Countdown bei Tod)
|
||||
- AbilityBar (HBoxContainer, mitte unten)
|
||||
- ClassIcon (Label, Klassen-Symbol: T=Tank, D=Schaden, H=Heiler)
|
||||
- Ability 1-5
|
||||
|
||||
- enemy.tscn — Gegner
|
||||
- CharacterBody3D (enemy.gd)
|
||||
- Kollision (CapsuleShape3D)
|
||||
- Mesh (SphereMesh, Platzhalter)
|
||||
- Health (Node, health.gd)
|
||||
- Shield (Node, shield.gd)
|
||||
- HitArea (Area3D, Trefferbereich des Gegners)
|
||||
- CollisionShape3D (CapsuleShape3D)
|
||||
- DetectionArea (Area3D, Erkennungsradius)
|
||||
- CollisionShape3D (SphereShape3D)
|
||||
- NavigationAgent3D (Wegfindung)
|
||||
- EnemyMovement (Node, enemy_movement.gd)
|
||||
- EnemyCombat (Node, enemy_combat.gd)
|
||||
- Healthbar (Sprite3D + SubViewport, über dem Gegner, enemy_healthbar.gd)
|
||||
- SubViewport
|
||||
- Border (ColorRect, gelb, sichtbar bei Anvisierung)
|
||||
- HealthBar (ProgressBar, grün)
|
||||
- ShieldBar (ProgressBar, blau)
|
||||
|
||||
## Skripte
|
||||
- player.gd — Kern, verbindet Komponenten
|
||||
- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung anpassen
|
||||
- movement.gd — Bewegung (WASD relativ zur Kamera), Springen, Schwerkraft
|
||||
- combat.gd — Führt Abilities aus, Fähigkeiten 1-5
|
||||
- 1 Single: Anvisiertes Ziel angreifen (10 Schaden, Distanzprüfung)
|
||||
- 2 AOE: Alle Gegner im Bereich (5 Schaden, halber Single-Schaden)
|
||||
- 3 Utility: Schild-Regeneration sofort zurücksetzen
|
||||
- 4 Ult: 4x Schaden an anvisiertem Ziel + 2x AOE-Schaden um das Ziel herum
|
||||
- 5 Passive: 50% mehr Schaden (permanent aktiv)
|
||||
- targeting.gd — Klick/TAB anvisieren, 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)
|
||||
- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt
|
||||
- enemy_combat.gd — Angriff über Event (damage_requested)
|
||||
- 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
|
||||
|
||||
## Abilities (Resources)
|
||||
- ability.gd (Resource) — name, type, damage, range, execute()
|
||||
- ability_set.gd (Resource) — Set von 5 Abilities pro Klasse
|
||||
- ability_modifier.gd (Resource) — Verändert Ability (Element, Beruf, Prestige)
|
||||
- Typen: Single, AOE, Utility, Ult, Passive
|
||||
- Jede Klasse hat ein eigenes AbilitySet
|
||||
- Beim Klassenwechsel wird das AbilitySet getauscht
|
||||
- Elemente und Modifikatoren verändern Abilities nachträglich
|
||||
|
||||
## Events
|
||||
- attack_executed(attacker, position, direction, damage) — Angriff wurde ausgeführt
|
||||
- damage_dealt(attacker, target, damage) — Schaden wurde verteilt
|
||||
- damage_requested(attacker, target, amount) — Schaden zwischen Szenen anfordern
|
||||
- entity_died(entity) — Entity ist gestorben
|
||||
- shield_broken(entity) — Schild ist auf 0 gefallen
|
||||
- shield_regenerated(entity) — Schild ist wieder voll
|
||||
- target_changed(player, target) — Spieler hat neues Ziel anvisiert
|
||||
- player_respawned(player) — Spieler ist respawnt
|
||||
- class_changed(player, class_name) — Spieler hat Klasse gewechselt
|
||||
- health_changed(entity, current, max) — Leben hat sich verändert
|
||||
- shield_changed(entity, current, max) — Schild hat sich verändert
|
||||
- respawn_tick(timer) — Respawn-Countdown Update
|
||||
- enemy_engaged(enemy, target) — Gegner hat Spieler anvisiert
|
||||
96
project.godot
Normal file
96
project.godot
Normal file
@@ -0,0 +1,96 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="mmo"
|
||||
run/main_scene="res://scenes/world.tscn"
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
EventBus="*res://scripts/event_bus.gd"
|
||||
|
||||
[input]
|
||||
|
||||
move_forward={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_back={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_1={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_2={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_3={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_4={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_5={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
target_next={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
class_tank={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
class_damage={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
class_healer={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
3d/physics_engine="Jolt Physics"
|
||||
|
||||
[rendering]
|
||||
|
||||
rendering_device/driver.windows="d3d12"
|
||||
11
resources/abilities/aoe_attack.tres
Normal file
11
resources/abilities/aoe_attack.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "AOE"
|
||||
type = 1
|
||||
damage = 5.0
|
||||
ability_range = 5.0
|
||||
icon = "2"
|
||||
11
resources/abilities/passive_damage_boost.tres
Normal file
11
resources/abilities/passive_damage_boost.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Damage Boost"
|
||||
type = 4
|
||||
damage = 50.0
|
||||
ability_range = 0.0
|
||||
icon = "5"
|
||||
11
resources/abilities/single_attack.tres
Normal file
11
resources/abilities/single_attack.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Single"
|
||||
type = 0
|
||||
damage = 10.0
|
||||
ability_range = 3.0
|
||||
icon = "1"
|
||||
11
resources/abilities/ult_burst.tres
Normal file
11
resources/abilities/ult_burst.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Burst"
|
||||
type = 3
|
||||
damage = 10.0
|
||||
ability_range = 5.0
|
||||
icon = "4"
|
||||
11
resources/abilities/utility_shield_reset.tres
Normal file
11
resources/abilities/utility_shield_reset.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Shield Reset"
|
||||
type = 2
|
||||
damage = 0.0
|
||||
ability_range = 0.0
|
||||
icon = "3"
|
||||
13
resources/ability_sets/damage_set.tres
Normal file
13
resources/ability_sets/damage_set.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://beodknb6i1pm4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scripts/abilities/ability_set.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1_ability"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/single_attack.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/aoe_attack.tres" id="3"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/utility_shield_reset.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/ult_burst.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/passive_damage_boost.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
13
resources/ability_sets/healer_set.tres
Normal file
13
resources/ability_sets/healer_set.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://kcwuhnqy34mj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scripts/abilities/ability_set.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1_ability"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/single_attack.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/aoe_attack.tres" id="3"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/utility_shield_reset.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/ult_burst.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/passive_damage_boost.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
13
resources/ability_sets/tank_set.tres
Normal file
13
resources/ability_sets/tank_set.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://cgxtn7dfs40bh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scripts/abilities/ability_set.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1_ability"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/single_attack.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/aoe_attack.tres" id="3"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/utility_shield_reset.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/ult_burst.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/passive_damage_boost.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
117
scenes/enemy/enemy.tscn
Normal file
117
scenes/enemy/enemy.tscn
Normal file
@@ -0,0 +1,117 @@
|
||||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[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/enemy/enemy_movement.gd" id="5"]
|
||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_combat.gd" id="6"]
|
||||
|
||||
[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")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("SphereMesh_1")
|
||||
|
||||
[node name="Health" type="Node" parent="."]
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Shield" type="Node" parent="."]
|
||||
script = ExtResource("3")
|
||||
|
||||
[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="EnemyCombat" type="Node" parent="."]
|
||||
script = ExtResource("6")
|
||||
|
||||
[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"]
|
||||
181
scenes/hud/hud.tscn
Normal file
181
scenes/hud/hud.tscn
Normal file
@@ -0,0 +1,181 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/player/hud.gd" id="1"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_active"]
|
||||
bg_color = Color(0.2, 0.2, 0.2, 0.8)
|
||||
border_width_bottom = 2
|
||||
border_width_left = 2
|
||||
border_width_right = 2
|
||||
border_width_top = 2
|
||||
border_color = Color(0.8, 0.8, 0.8, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_round"]
|
||||
bg_color = Color(0.2, 0.2, 0.2, 0.8)
|
||||
border_width_bottom = 2
|
||||
border_width_left = 2
|
||||
border_width_right = 2
|
||||
border_width_top = 2
|
||||
border_color = Color(0.8, 0.8, 0.8, 1)
|
||||
corner_radius_top_left = 22
|
||||
corner_radius_top_right = 22
|
||||
corner_radius_bottom_right = 22
|
||||
corner_radius_bottom_left = 22
|
||||
|
||||
[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)
|
||||
|
||||
[node name="HUD" type="CanvasLayer"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="HealthBar" type="ProgressBar" parent="."]
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 30.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="."]
|
||||
offset_left = 10.0
|
||||
offset_top = 35.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 55.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
|
||||
|
||||
[node name="RespawnTimer" type="Label" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -50.0
|
||||
offset_top = -30.0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 30.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_font_sizes/font_size = 48
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="AbilityBar" type="HBoxContainer" parent="."]
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -130.0
|
||||
offset_top = -60.0
|
||||
offset_right = 130.0
|
||||
offset_bottom = -10.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_constants/separation = 5
|
||||
|
||||
[node name="ClassIcon" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round")
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/ClassIcon"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "D"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability1" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability1"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "1"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability2" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability2"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "2"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability3" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability3"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "3"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability4" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active")
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability4"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "4"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Ability5" type="Panel" parent="AbilityBar"]
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round")
|
||||
|
||||
[node name="Label" type="Label" parent="AbilityBar/Ability5"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "P"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
62
scenes/player/player.tscn
Normal file
62
scenes/player/player.tscn
Normal file
@@ -0,0 +1,62 @@
|
||||
[gd_scene format=3 uid="uid://cdnkbt1f0db7e"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bfpt2p7uucfyb" path="res://scripts/player/player.gd" id="1"]
|
||||
[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"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
radius = 0.3
|
||||
height = 1.8
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_1"]
|
||||
radius = 0.3
|
||||
height = 1.8
|
||||
|
||||
[node name="Player" type="CharacterBody3D" unique_id=1350215040]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=33887999]
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="." unique_id=1346931672]
|
||||
mesh = SubResource("CapsuleMesh_1")
|
||||
|
||||
[node name="CameraPivot" type="Node3D" parent="." unique_id=1292689540]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1225820651]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.966, 0.259, 0, -0.259, 0.966, 0, 2, 5)
|
||||
|
||||
[node name="Movement" type="Node" parent="." unique_id=654979387]
|
||||
script = ExtResource("3")
|
||||
|
||||
[node name="Combat" type="Node" parent="." unique_id=1754235583]
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="Targeting" type="Node" parent="."]
|
||||
script = ExtResource("8")
|
||||
|
||||
[node name="Health" type="Node" parent="."]
|
||||
script = ExtResource("5")
|
||||
|
||||
[node name="Shield" type="Node" parent="."]
|
||||
script = ExtResource("6")
|
||||
|
||||
[node name="Respawn" type="Node" parent="."]
|
||||
script = ExtResource("9")
|
||||
|
||||
[node name="PlayerClass" type="Node" parent="."]
|
||||
script = ExtResource("10")
|
||||
tank_set = ExtResource("11")
|
||||
damage_set = ExtResource("12")
|
||||
healer_set = ExtResource("13")
|
||||
64
scenes/world.tscn
Normal file
64
scenes/world.tscn
Normal file
@@ -0,0 +1,64 @@
|
||||
[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" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"]
|
||||
|
||||
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||
vertices = PackedVector3Array(-9.5, 0.5, -9.5, -9.5, 0.5, 9.5, 9.5, 0.5, 9.5, 9.5, 0.5, -9.5)
|
||||
polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_1"]
|
||||
colors = PackedColorArray(0.15, 0.35, 0.05, 1, 0.3, 0.55, 0.1, 1)
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_1"]
|
||||
frequency = 0.05
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_1"]
|
||||
noise = SubResource("FastNoiseLite_1")
|
||||
color_ramp = SubResource("Gradient_1")
|
||||
seamless = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_1")
|
||||
uv1_scale = Vector3(3, 3, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_1"]
|
||||
material = SubResource("StandardMaterial3D_1")
|
||||
size = Vector2(20, 20)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1"]
|
||||
|
||||
[node name="World" type="Node3D" unique_id=1834775183]
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=561649622]
|
||||
navigation_mesh = SubResource("NavigationMesh_1")
|
||||
|
||||
[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D" unique_id=1129907783]
|
||||
mesh = SubResource("PlaneMesh_1")
|
||||
|
||||
[node name="BodenCollision" type="StaticBody3D" parent="." unique_id=391155617]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision" unique_id=809444866]
|
||||
shape = SubResource("WorldBoundaryShape3D_1")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1132129079]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 10, 0)
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="Player" parent="." unique_id=190642073 instance=ExtResource("player")]
|
||||
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)
|
||||
66
scripts/abilities/ability.gd
Normal file
66
scripts/abilities/ability.gd
Normal file
@@ -0,0 +1,66 @@
|
||||
extends Resource
|
||||
class_name Ability
|
||||
|
||||
enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
||||
|
||||
@export var ability_name: String = ""
|
||||
@export var type: Type = Type.SINGLE
|
||||
@export var damage: float = 0.0
|
||||
@export var ability_range: float = 3.0
|
||||
@export var icon: String = ""
|
||||
|
||||
func execute(player: Node, targeting: Node) -> void:
|
||||
var dmg: float = _get_modified_damage(player, damage)
|
||||
match type:
|
||||
Type.SINGLE:
|
||||
_execute_single(player, targeting, dmg)
|
||||
Type.AOE:
|
||||
_execute_aoe(player, dmg)
|
||||
Type.UTILITY:
|
||||
_execute_utility(player)
|
||||
Type.ULT:
|
||||
_execute_ult(player, targeting, dmg)
|
||||
|
||||
func _get_modified_damage(player: Node, base: float) -> float:
|
||||
var combat: Node = player.get_node("Combat")
|
||||
return combat.apply_passive(base)
|
||||
|
||||
func _execute_single(player: Node, targeting: Node, dmg: float) -> void:
|
||||
var target: Node3D = targeting.current_target
|
||||
if not target or not is_instance_valid(target):
|
||||
return
|
||||
var dist: float = player.global_position.distance_to(target.global_position)
|
||||
if dist > ability_range:
|
||||
return
|
||||
EventBus.damage_requested.emit(player, target, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
|
||||
func _execute_aoe(player: Node, dmg: float) -> void:
|
||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
var dist: float = player.global_position.distance_to(enemy.global_position)
|
||||
if dist <= ability_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
|
||||
func _execute_utility(player: Node) -> void:
|
||||
var shield: Node = player.get_node_or_null("Shield")
|
||||
if shield:
|
||||
shield.current_shield = shield.max_shield
|
||||
EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield)
|
||||
|
||||
func _execute_ult(player: Node, targeting: Node, dmg: float) -> void:
|
||||
var target: Node3D = targeting.current_target
|
||||
if not target or not is_instance_valid(target):
|
||||
return
|
||||
var dist: float = player.global_position.distance_to(target.global_position)
|
||||
if dist > ability_range:
|
||||
return
|
||||
EventBus.damage_requested.emit(player, target, dmg * 4.0)
|
||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
if enemy != target:
|
||||
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
||||
if enemy_dist <= ability_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 4.0)
|
||||
1
scripts/abilities/ability.gd.uid
Normal file
1
scripts/abilities/ability.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c03xbbf3yhfl3
|
||||
4
scripts/abilities/ability_set.gd
Normal file
4
scripts/abilities/ability_set.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
extends Resource
|
||||
class_name AbilitySet
|
||||
|
||||
@export var abilities: Array[Ability] = []
|
||||
1
scripts/abilities/ability_set.gd.uid
Normal file
1
scripts/abilities/ability_set.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://voedgs25cwrb
|
||||
33
scripts/components/health.gd
Normal file
33
scripts/components/health.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
extends Node
|
||||
|
||||
@export var max_health := 100.0
|
||||
const HEALTH_REGEN := 1.0
|
||||
var current_health: float
|
||||
|
||||
func _ready() -> void:
|
||||
current_health = max_health
|
||||
EventBus.damage_requested.connect(_on_damage_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if current_health > 0 and current_health < max_health:
|
||||
current_health = min(current_health + HEALTH_REGEN * delta, max_health)
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
|
||||
func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void:
|
||||
if target != get_parent():
|
||||
return
|
||||
var remaining: float = amount
|
||||
var shield: Node = get_parent().get_node_or_null("Shield")
|
||||
if shield:
|
||||
remaining = shield.absorb(remaining)
|
||||
EventBus.damage_dealt.emit(attacker, get_parent(), amount)
|
||||
if remaining > 0:
|
||||
take_damage(remaining, attacker)
|
||||
|
||||
func take_damage(amount: float, attacker: Node) -> void:
|
||||
current_health -= amount
|
||||
if current_health <= 0:
|
||||
current_health = 0
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
if current_health <= 0:
|
||||
EventBus.entity_died.emit(get_parent())
|
||||
1
scripts/components/health.gd.uid
Normal file
1
scripts/components/health.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b053b4fkkeaod
|
||||
32
scripts/components/shield.gd
Normal file
32
scripts/components/shield.gd
Normal file
@@ -0,0 +1,32 @@
|
||||
extends Node
|
||||
|
||||
@export var max_shield := 50.0
|
||||
const REGEN_DELAY := 3.0
|
||||
const REGEN_TIME := 5.0
|
||||
var current_shield: float
|
||||
var regen_timer := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
current_shield = max_shield
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if current_shield < max_shield:
|
||||
regen_timer += delta
|
||||
if regen_timer >= REGEN_DELAY:
|
||||
current_shield += (max_shield / REGEN_TIME) * delta
|
||||
if current_shield >= max_shield:
|
||||
current_shield = max_shield
|
||||
EventBus.shield_regenerated.emit(get_parent())
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
|
||||
func absorb(amount: float) -> float:
|
||||
if current_shield <= 0:
|
||||
return amount
|
||||
regen_timer = 0.0
|
||||
var absorbed: float = min(amount, current_shield)
|
||||
current_shield -= absorbed
|
||||
print("%s Schild: %s/%s (-%s)" % [get_parent().name, current_shield, max_shield, absorbed])
|
||||
if current_shield <= 0:
|
||||
EventBus.shield_broken.emit(get_parent())
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
return amount - absorbed
|
||||
1
scripts/components/shield.gd.uid
Normal file
1
scripts/components/shield.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bpfw71oprcvou
|
||||
38
scripts/enemy/enemy.gd
Normal file
38
scripts/enemy/enemy.gd
Normal file
@@ -0,0 +1,38 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
enum State { IDLE, CHASE, ATTACK, RETURN }
|
||||
|
||||
var state: int = State.IDLE
|
||||
var target: Node3D = null
|
||||
var spawn_position: Vector3
|
||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
@onready var health: Node = $Health
|
||||
|
||||
func _ready() -> void:
|
||||
spawn_position = global_position
|
||||
add_to_group("enemies")
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == self:
|
||||
queue_free()
|
||||
elif entity == target:
|
||||
target = null
|
||||
state = State.RETURN
|
||||
|
||||
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":
|
||||
target = body
|
||||
state = State.CHASE
|
||||
EventBus.enemy_engaged.emit(self, body)
|
||||
|
||||
func _on_detection_area_body_exited(body: Node3D) -> void:
|
||||
if body == target and state == State.CHASE:
|
||||
state = State.RETURN
|
||||
target = null
|
||||
1
scripts/enemy/enemy.gd.uid
Normal file
1
scripts/enemy/enemy.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bwi75jx0agktd
|
||||
26
scripts/enemy/enemy_combat.gd
Normal file
26
scripts/enemy/enemy_combat.gd
Normal file
@@ -0,0 +1,26 @@
|
||||
extends Node
|
||||
|
||||
const ATTACK_RANGE := 2.0
|
||||
const ATTACK_COOLDOWN := 1.5
|
||||
const ATTACK_DAMAGE := 5.0
|
||||
|
||||
var attack_timer := 0.0
|
||||
|
||||
@onready var enemy: CharacterBody3D = get_parent()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
attack_timer -= delta
|
||||
if enemy.state != enemy.State.ATTACK:
|
||||
return
|
||||
if not is_instance_valid(enemy.target):
|
||||
enemy.state = enemy.State.RETURN
|
||||
return
|
||||
var dist := enemy.global_position.distance_to(enemy.target.global_position)
|
||||
if dist > ATTACK_RANGE:
|
||||
enemy.state = enemy.State.CHASE
|
||||
return
|
||||
if attack_timer <= 0:
|
||||
attack_timer = ATTACK_COOLDOWN
|
||||
EventBus.damage_requested.emit(enemy, enemy.target, ATTACK_DAMAGE)
|
||||
enemy.velocity.x = 0
|
||||
enemy.velocity.z = 0
|
||||
1
scripts/enemy/enemy_combat.gd.uid
Normal file
1
scripts/enemy/enemy_combat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ct4u62xalrjyo
|
||||
22
scripts/enemy/enemy_healthbar.gd
Normal file
22
scripts/enemy/enemy_healthbar.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
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")
|
||||
|
||||
func _ready() -> void:
|
||||
texture = viewport.get_texture()
|
||||
health_bar.max_value = health.max_health
|
||||
shield_bar.max_value = shield.max_shield
|
||||
border.visible = false
|
||||
EventBus.target_changed.connect(_on_target_changed)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
health_bar.value = health.current_health
|
||||
shield_bar.value = shield.current_shield
|
||||
|
||||
func _on_target_changed(_player: Node, target: Node) -> void:
|
||||
border.visible = (target == get_parent())
|
||||
1
scripts/enemy/enemy_healthbar.gd.uid
Normal file
1
scripts/enemy/enemy_healthbar.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dwqx03nfypa7u
|
||||
52
scripts/enemy/enemy_movement.gd
Normal file
52
scripts/enemy/enemy_movement.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
extends Node
|
||||
|
||||
const SPEED := 3.0
|
||||
const LEASH_RANGE := 15.0
|
||||
const ATTACK_RANGE := 2.0
|
||||
|
||||
@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()
|
||||
|
||||
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
|
||||
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() -> 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
|
||||
1
scripts/enemy/enemy_movement.gd.uid
Normal file
1
scripts/enemy/enemy_movement.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://tnx6rbnnngn
|
||||
15
scripts/event_bus.gd
Normal file
15
scripts/event_bus.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
extends Node
|
||||
|
||||
signal attack_executed(attacker, position, direction, damage)
|
||||
signal damage_dealt(attacker, target, damage)
|
||||
signal entity_died(entity)
|
||||
signal shield_broken(entity)
|
||||
signal shield_regenerated(entity)
|
||||
signal target_changed(player, target)
|
||||
signal player_respawned(player)
|
||||
signal class_changed(player, class_type)
|
||||
signal damage_requested(attacker, target, amount)
|
||||
signal health_changed(entity, current, max_val)
|
||||
signal shield_changed(entity, current, max_val)
|
||||
signal respawn_tick(timer)
|
||||
signal enemy_engaged(enemy, target)
|
||||
1
scripts/event_bus.gd.uid
Normal file
1
scripts/event_bus.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://g7a7xkg1pgb4
|
||||
30
scripts/player/camera.gd
Normal file
30
scripts/player/camera.gd
Normal file
@@ -0,0 +1,30 @@
|
||||
extends Node3D
|
||||
|
||||
const SENSITIVITY := 0.003
|
||||
const PITCH_MIN := -80.0
|
||||
const PITCH_MAX := 80.0
|
||||
|
||||
var pitch := -0.3
|
||||
var camera_yaw := 0.0
|
||||
var player_yaw := -2.356
|
||||
|
||||
func _ready() -> void:
|
||||
get_parent().rotation.y = player_yaw
|
||||
rotation = Vector3(pitch, camera_yaw, 0)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion:
|
||||
var lmb := Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||
var rmb := Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
|
||||
|
||||
if lmb or rmb:
|
||||
pitch -= event.relative.y * SENSITIVITY
|
||||
pitch = clamp(pitch, deg_to_rad(PITCH_MIN), deg_to_rad(PITCH_MAX))
|
||||
|
||||
if rmb:
|
||||
player_yaw -= event.relative.x * SENSITIVITY
|
||||
get_parent().rotation.y = player_yaw
|
||||
else:
|
||||
camera_yaw -= event.relative.x * SENSITIVITY
|
||||
|
||||
rotation = Vector3(pitch, camera_yaw, 0)
|
||||
1
scripts/player/camera.gd.uid
Normal file
1
scripts/player/camera.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cohjyjge1kqxb
|
||||
35
scripts/player/combat.gd
Normal file
35
scripts/player/combat.gd
Normal file
@@ -0,0 +1,35 @@
|
||||
extends Node
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
@onready var targeting: Node = get_parent().get_node("Targeting")
|
||||
@onready var player_class: Node = get_parent().get_node("PlayerClass")
|
||||
|
||||
var abilities: Array = []
|
||||
|
||||
func _ready() -> void:
|
||||
_load_abilities()
|
||||
EventBus.class_changed.connect(_on_class_changed)
|
||||
|
||||
func _load_abilities() -> void:
|
||||
var ability_set: AbilitySet = player_class.get_ability_set()
|
||||
if ability_set:
|
||||
abilities = ability_set.abilities
|
||||
else:
|
||||
abilities = []
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
for i in range(min(abilities.size(), 5)):
|
||||
if event.is_action_pressed("ability_%s" % (i + 1)) and abilities[i]:
|
||||
if abilities[i].type == Ability.Type.PASSIVE:
|
||||
return
|
||||
abilities[i].execute(player, targeting)
|
||||
return
|
||||
|
||||
func apply_passive(base_damage: float) -> float:
|
||||
for ability in abilities:
|
||||
if ability and ability.type == Ability.Type.PASSIVE:
|
||||
return base_damage * (1.0 + ability.damage / 100.0)
|
||||
return base_damage
|
||||
|
||||
func _on_class_changed(_player: Node, _class_type: int) -> void:
|
||||
_load_abilities()
|
||||
1
scripts/player/combat.gd.uid
Normal file
1
scripts/player/combat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d15til6fsxw5b
|
||||
43
scripts/player/hud.gd
Normal file
43
scripts/player/hud.gd
Normal file
@@ -0,0 +1,43 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var health_bar: ProgressBar = $HealthBar
|
||||
@onready var shield_bar: ProgressBar = $ShieldBar
|
||||
@onready var respawn_label: Label = $RespawnTimer
|
||||
@onready var class_icon: Label = $AbilityBar/ClassIcon/Label
|
||||
|
||||
var player_node: Node = null
|
||||
|
||||
func _ready() -> void:
|
||||
respawn_label.visible = false
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
EventBus.shield_changed.connect(_on_shield_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.player_respawned.connect(_on_player_respawned)
|
||||
EventBus.class_changed.connect(_on_class_changed)
|
||||
EventBus.respawn_tick.connect(_on_respawn_tick)
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
if entity.name == "Player":
|
||||
health_bar.max_value = max_val
|
||||
health_bar.value = current
|
||||
|
||||
func _on_shield_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
if entity.name == "Player":
|
||||
shield_bar.max_value = max_val
|
||||
shield_bar.value = current
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity.name == "Player":
|
||||
respawn_label.visible = true
|
||||
|
||||
func _on_player_respawned(_player: Node) -> void:
|
||||
respawn_label.visible = false
|
||||
|
||||
func _on_respawn_tick(timer: float) -> void:
|
||||
respawn_label.text = str(ceil(timer))
|
||||
|
||||
func _on_class_changed(_player: Node, class_type: int) -> void:
|
||||
match class_type:
|
||||
0: class_icon.text = "T"
|
||||
1: class_icon.text = "D"
|
||||
2: class_icon.text = "H"
|
||||
1
scripts/player/hud.gd.uid
Normal file
1
scripts/player/hud.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c4jhr8k4uwoy7
|
||||
35
scripts/player/movement.gd
Normal file
35
scripts/player/movement.gd
Normal file
@@ -0,0 +1,35 @@
|
||||
extends Node
|
||||
|
||||
const SPEED := 5.0
|
||||
const JUMP_VELOCITY := 4.5
|
||||
|
||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not player.is_on_floor():
|
||||
player.velocity.y -= gravity * delta
|
||||
|
||||
if Input.is_action_just_pressed("ui_accept") and player.is_on_floor():
|
||||
player.velocity.y = JUMP_VELOCITY
|
||||
|
||||
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||
var camera_pivot := player.get_node("CameraPivot") as Node3D
|
||||
var forward := -camera_pivot.global_transform.basis.z
|
||||
forward.y = 0
|
||||
forward = forward.normalized()
|
||||
var right := camera_pivot.global_transform.basis.x
|
||||
right.y = 0
|
||||
right = right.normalized()
|
||||
|
||||
var direction := (forward * -input_dir.y + right * input_dir.x).normalized()
|
||||
|
||||
if direction:
|
||||
player.velocity.x = direction.x * SPEED
|
||||
player.velocity.z = direction.z * SPEED
|
||||
else:
|
||||
player.velocity.x = move_toward(player.velocity.x, 0, SPEED)
|
||||
player.velocity.z = move_toward(player.velocity.z, 0, SPEED)
|
||||
|
||||
player.move_and_slide()
|
||||
1
scripts/player/movement.gd.uid
Normal file
1
scripts/player/movement.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://fg87dh8fbc8
|
||||
1
scripts/player/player.gd
Normal file
1
scripts/player/player.gd
Normal file
@@ -0,0 +1 @@
|
||||
extends CharacterBody3D
|
||||
1
scripts/player/player.gd.uid
Normal file
1
scripts/player/player.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bfpt2p7uucfyb
|
||||
37
scripts/player/player_class.gd
Normal file
37
scripts/player/player_class.gd
Normal file
@@ -0,0 +1,37 @@
|
||||
extends Node
|
||||
|
||||
enum PlayerClass { TANK, DAMAGE, HEALER }
|
||||
|
||||
var current_class: int = PlayerClass.DAMAGE
|
||||
|
||||
@export var tank_set: AbilitySet
|
||||
@export var damage_set: AbilitySet
|
||||
@export var healer_set: AbilitySet
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("class_tank"):
|
||||
set_class(PlayerClass.TANK)
|
||||
elif event.is_action_pressed("class_damage"):
|
||||
set_class(PlayerClass.DAMAGE)
|
||||
elif event.is_action_pressed("class_healer"):
|
||||
set_class(PlayerClass.HEALER)
|
||||
|
||||
func set_class(new_class: int) -> void:
|
||||
current_class = new_class
|
||||
EventBus.class_changed.emit(player, current_class)
|
||||
|
||||
func get_class_icon() -> String:
|
||||
match current_class:
|
||||
PlayerClass.TANK: return "T"
|
||||
PlayerClass.DAMAGE: return "D"
|
||||
PlayerClass.HEALER: return "H"
|
||||
return ""
|
||||
|
||||
func get_ability_set() -> AbilitySet:
|
||||
match current_class:
|
||||
PlayerClass.TANK: return tank_set
|
||||
PlayerClass.DAMAGE: return damage_set
|
||||
PlayerClass.HEALER: return healer_set
|
||||
return damage_set
|
||||
1
scripts/player/player_class.gd.uid
Normal file
1
scripts/player/player_class.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://rus4umqvvqq4
|
||||
46
scripts/player/respawn.gd
Normal file
46
scripts/player/respawn.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
extends Node
|
||||
|
||||
const RESPAWN_TIME := 3.0
|
||||
var respawn_timer := 0.0
|
||||
var is_dead := false
|
||||
var spawn_position: Vector3
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _ready() -> void:
|
||||
spawn_position = player.global_position
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if is_dead:
|
||||
respawn_timer -= delta
|
||||
EventBus.respawn_tick.emit(respawn_timer)
|
||||
if respawn_timer <= 0:
|
||||
_respawn()
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == player and not is_dead:
|
||||
is_dead = true
|
||||
respawn_timer = RESPAWN_TIME
|
||||
player.velocity = Vector3.ZERO
|
||||
player.get_node("Mesh").visible = false
|
||||
player.get_node("CollisionShape3D").disabled = true
|
||||
player.get_node("Movement").set_physics_process(false)
|
||||
player.get_node("Combat").set_process_unhandled_input(false)
|
||||
player.get_node("Targeting").set_process_unhandled_input(false)
|
||||
|
||||
func _respawn() -> void:
|
||||
is_dead = false
|
||||
player.global_position = spawn_position
|
||||
player.get_node("Mesh").visible = true
|
||||
player.get_node("CollisionShape3D").disabled = false
|
||||
player.get_node("Movement").set_physics_process(true)
|
||||
player.get_node("Combat").set_process_unhandled_input(true)
|
||||
player.get_node("Targeting").set_process_unhandled_input(true)
|
||||
var health_node: Node = player.get_node("Health")
|
||||
var shield_node: Node = player.get_node("Shield")
|
||||
health_node.current_health = health_node.max_health
|
||||
shield_node.current_shield = shield_node.max_shield
|
||||
EventBus.health_changed.emit(player, health_node.current_health, health_node.max_health)
|
||||
EventBus.shield_changed.emit(player, shield_node.current_shield, shield_node.max_shield)
|
||||
EventBus.player_respawned.emit(player)
|
||||
1
scripts/player/respawn.gd.uid
Normal file
1
scripts/player/respawn.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dw3dtax5bx0of
|
||||
113
scripts/player/targeting.gd
Normal file
113
scripts/player/targeting.gd
Normal file
@@ -0,0 +1,113 @@
|
||||
extends Node
|
||||
|
||||
const TARGET_RANGE := 20.0
|
||||
const COMBAT_TIMEOUT := 3.0
|
||||
|
||||
var current_target: Node3D = null
|
||||
var mouse_press_pos: Vector2 = Vector2.ZERO
|
||||
var in_combat := false
|
||||
var combat_timer := 0.0
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
@onready var camera: Camera3D = get_parent().get_node("CameraPivot/Camera3D")
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.damage_dealt.connect(_on_damage_dealt)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.enemy_engaged.connect(_on_enemy_engaged)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if in_combat:
|
||||
combat_timer -= delta
|
||||
if combat_timer <= 0:
|
||||
in_combat = false
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
mouse_press_pos = event.position
|
||||
else:
|
||||
var drag: float = event.position.distance_to(mouse_press_pos)
|
||||
if drag < 5.0:
|
||||
_try_target_under_mouse(event.position)
|
||||
if event.is_action_pressed("target_next"):
|
||||
_cycle_target()
|
||||
|
||||
func _try_target_under_mouse(mouse_pos: Vector2) -> void:
|
||||
var from := camera.project_ray_origin(mouse_pos)
|
||||
var to := from + camera.project_ray_normal(mouse_pos) * TARGET_RANGE
|
||||
var space := player.get_world_3d().direct_space_state
|
||||
var query := PhysicsRayQueryParameters3D.create(from, to)
|
||||
query.collision_mask = 4
|
||||
query.collide_with_areas = true
|
||||
query.collide_with_bodies = false
|
||||
var result := space.intersect_ray(query)
|
||||
if result:
|
||||
var hit_target := result.collider.get_parent() as Node3D
|
||||
set_target(hit_target)
|
||||
else:
|
||||
set_target(null)
|
||||
|
||||
func _cycle_target() -> void:
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
if enemies.is_empty():
|
||||
set_target(null)
|
||||
return
|
||||
if current_target == null or current_target not in enemies:
|
||||
set_target(enemies[0])
|
||||
return
|
||||
var idx := enemies.find(current_target)
|
||||
var next_idx := (idx + 1) % enemies.size()
|
||||
set_target(enemies[next_idx])
|
||||
|
||||
func set_target(target: Node3D) -> void:
|
||||
current_target = target
|
||||
EventBus.target_changed.emit(player, target)
|
||||
|
||||
func _on_enemy_engaged(_enemy: Node, target: Node) -> void:
|
||||
if target == player:
|
||||
combat_timer = COMBAT_TIMEOUT
|
||||
if not in_combat:
|
||||
in_combat = true
|
||||
if current_target == null:
|
||||
_target_nearest()
|
||||
|
||||
func _on_damage_dealt(_attacker: Node, target: Node, _amount: float) -> void:
|
||||
if target == player:
|
||||
combat_timer = COMBAT_TIMEOUT
|
||||
if not in_combat:
|
||||
in_combat = true
|
||||
if current_target == null:
|
||||
_target_nearest()
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == current_target:
|
||||
set_target(null)
|
||||
if in_combat:
|
||||
_target_nearest_except(entity)
|
||||
|
||||
func _target_nearest_except(exclude: Node = null) -> void:
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
var nearest: Node3D = null
|
||||
var nearest_dist: float = INF
|
||||
for enemy in enemies:
|
||||
if is_instance_valid(enemy) and enemy != exclude:
|
||||
var dist: float = player.global_position.distance_to(enemy.global_position)
|
||||
if dist < nearest_dist:
|
||||
nearest_dist = dist
|
||||
nearest = enemy
|
||||
if nearest:
|
||||
set_target(nearest)
|
||||
|
||||
func _target_nearest() -> void:
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
var nearest: Node3D = null
|
||||
var nearest_dist: float = INF
|
||||
for enemy in enemies:
|
||||
if is_instance_valid(enemy):
|
||||
var dist: float = player.global_position.distance_to(enemy.global_position)
|
||||
if dist < nearest_dist:
|
||||
nearest_dist = dist
|
||||
nearest = enemy
|
||||
if nearest:
|
||||
set_target(nearest)
|
||||
1
scripts/player/targeting.gd.uid
Normal file
1
scripts/player/targeting.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b05nkuryipwny
|
||||
Reference in New Issue
Block a user