Initial commit: DungeonCrawler Grundgerüst
- Third-Person Spieler mit WASD-Bewegung und Kamerasteuerung (RMB + Mausrad-Zoom) - HP-System mit Healthbar und Aktionsleiste (Slots 1-9) - Autoattack-System: Linksklick markiert Ziel, Rechtsklick markiert + greift an - Waffensystem-Basis: Schaden basiert auf ausgerüsteter Waffe (unbewaffnet = 1) - Gegner-KI: läuft auf Spieler zu, greift bei Reichweite an, zeigt HP-Label bei Markierung - Ressourcen-Klassen: Attack und Weapon Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
5addad0b8c
25 changed files with 803 additions and 0 deletions
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/
|
||||
9
basic_attack.tres
Normal file
9
basic_attack.tres
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[gd_resource type="Resource" script_class="Attack" format=3 uid="uid://d02bao1dwygan"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cpa1p4k6xtiga" path="res://resources/attack.gd" id="1_4028k"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_4028k")
|
||||
name = "Autoattack"
|
||||
cooldown = 1.5
|
||||
metadata/_custom_type_script = "uid://cpa1p4k6xtiga"
|
||||
32
camera_pivot.gd
Normal file
32
camera_pivot.gd
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# CameraPivot.gd
|
||||
# Steuert die Third-Person-Kamera: Maussteuerung (RMB), Zoom per Mausrad
|
||||
extends Node3D
|
||||
|
||||
@export var sensitivity = 0.3 # Mausempfindlichkeit
|
||||
@export var min_pitch = -40.0 # Maximale Neigung nach unten
|
||||
@export var max_pitch = 20.0 # Maximale Neigung nach oben
|
||||
@export var min_zoom = 5.0 # Minimale Kameraentfernung
|
||||
@export var max_zoom = 20.0 # Maximale Kameraentfernung
|
||||
@export var zoom_speed = 1.0 # Zoom-Geschwindigkeit pro Mausrad-Schritt
|
||||
|
||||
var pitch = 0.0
|
||||
|
||||
@onready var camera = $Camera3D
|
||||
|
||||
func _ready():
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
func _input(event):
|
||||
# RMB gehalten: Kamera drehen
|
||||
if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
get_parent().rotate_y(deg_to_rad(-event.relative.x * sensitivity))
|
||||
pitch -= event.relative.y * sensitivity
|
||||
pitch = clamp(pitch, min_pitch, max_pitch)
|
||||
rotation_degrees.x = pitch
|
||||
|
||||
# Mausrad: Zoom
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
camera.position.z = clamp(camera.position.z + zoom_speed, min_zoom, max_zoom)
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
camera.position.z = clamp(camera.position.z - zoom_speed, min_zoom, max_zoom)
|
||||
1
camera_pivot.gd.uid
Normal file
1
camera_pivot.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bwtwon54po4w3
|
||||
79
enemy.gd
Normal file
79
enemy.gd
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Enemy.gd
|
||||
# Steuert den Gegner: KI-Bewegung zum Spieler, Angriff, HP, Zielanzeige
|
||||
extends CharacterBody3D
|
||||
|
||||
const SPEED = 3.0
|
||||
const GRAVITY = 9.8
|
||||
const ATTACK_DAMAGE = 5
|
||||
const ATTACK_RANGE = 1.5
|
||||
const ATTACK_COOLDOWN = 2.0
|
||||
|
||||
var max_hp = 50
|
||||
var current_hp = 50
|
||||
var target = null # Ziel des Gegners (normalerweise der Spieler)
|
||||
var can_attack = true
|
||||
|
||||
@onready var health_label = $HealthLabel
|
||||
|
||||
func _ready():
|
||||
health_label.visible = false
|
||||
_update_label()
|
||||
|
||||
# HP-Label Text aktualisieren
|
||||
func _update_label():
|
||||
health_label.text = str(current_hp) + " / " + str(max_hp)
|
||||
|
||||
# HP-Label anzeigen (wenn Gegner markiert wird)
|
||||
func show_health():
|
||||
health_label.visible = true
|
||||
|
||||
# HP-Label verstecken (wenn Markierung aufgehoben wird)
|
||||
func hide_health():
|
||||
health_label.visible = false
|
||||
|
||||
# Schaden nehmen und Label aktualisieren
|
||||
func take_damage(amount):
|
||||
current_hp -= amount
|
||||
_update_label()
|
||||
if current_hp <= 0:
|
||||
die()
|
||||
|
||||
# Gegner aus der Szene entfernen
|
||||
func die():
|
||||
print("Gegner besiegt!")
|
||||
queue_free()
|
||||
|
||||
func _physics_process(delta):
|
||||
if not is_on_floor():
|
||||
velocity.y -= GRAVITY * delta
|
||||
|
||||
if target == null:
|
||||
move_and_slide()
|
||||
return
|
||||
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
|
||||
if distance <= ATTACK_RANGE:
|
||||
# In Reichweite: angreifen
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
if can_attack:
|
||||
_attack()
|
||||
else:
|
||||
# Direkt auf Ziel zubewegen
|
||||
var direction = (target.global_position - global_position)
|
||||
direction.y = 0
|
||||
direction = direction.normalized()
|
||||
velocity.x = direction.x * SPEED
|
||||
velocity.z = direction.z * SPEED
|
||||
look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z))
|
||||
|
||||
move_and_slide()
|
||||
|
||||
# Angriff mit Cooldown
|
||||
func _attack():
|
||||
can_attack = false
|
||||
target.take_damage(ATTACK_DAMAGE)
|
||||
print("Gegner greift an!")
|
||||
await get_tree().create_timer(ATTACK_COOLDOWN).timeout
|
||||
can_attack = true
|
||||
1
enemy.gd.uid
Normal file
1
enemy.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bg5qs3pcfp7p7
|
||||
34
enemy.tscn
Normal file
34
enemy.tscn
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[gd_scene format=3 uid="uid://cvojaeanxugfj"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://tosl2au4emxt" path="res://assets/kenney_blocky-characters_20/Models/GLB format/character-d.glb" id="1_7k104"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4gyqm"]
|
||||
radius = 0.6
|
||||
height = 3.0
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7k104"]
|
||||
radius = 0.6
|
||||
height = 3.0
|
||||
|
||||
[node name="Enemy" type="CharacterBody3D" unique_id=332011146]
|
||||
|
||||
[node name="character-d2" parent="." unique_id=846574684 instance=ExtResource("1_7k104")]
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1323028920]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0)
|
||||
shape = SubResource("CapsuleShape3D_4gyqm")
|
||||
|
||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=457987844]
|
||||
|
||||
[node name="Area3D" type="Area3D" parent="." unique_id=1689838821]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D" unique_id=116643275]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0)
|
||||
shape = SubResource("CapsuleShape3D_7k104")
|
||||
|
||||
[node name="HealthLabel" type="Label3D" parent="." unique_id=1251847350]
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 3.5, 0)
|
||||
visible = false
|
||||
text = "50 / 50"
|
||||
outline_size = 8
|
||||
33
hud.gd
Normal file
33
hud.gd
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# HUD.gd
|
||||
# Verwaltet die Spieler-UI: HP-Leiste, Aktionsleiste (Slots 1-9)
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var health_bar = $Control/HealthBar
|
||||
@onready var health_label = $Control/HealthBar/HealthLabel
|
||||
@onready var action_slots = [
|
||||
$Control/ActionBar/A1,
|
||||
$Control/ActionBar/A2,
|
||||
$Control/ActionBar/A3,
|
||||
$Control/ActionBar/A4,
|
||||
$Control/ActionBar/A5,
|
||||
$Control/ActionBar/A6,
|
||||
$Control/ActionBar/A7,
|
||||
$Control/ActionBar/A8,
|
||||
$Control/ActionBar/A9
|
||||
]
|
||||
|
||||
var active_slot = 0
|
||||
|
||||
# HP-Leiste und Text aktualisieren
|
||||
func update_health(current_hp, max_hp):
|
||||
health_bar.max_value = max_hp
|
||||
health_bar.value = current_hp
|
||||
health_label.text = str(current_hp) + " / " + str(max_hp)
|
||||
|
||||
# Aktions-Slot kurz golden hervorheben (0.1s)
|
||||
func set_active_slot(index):
|
||||
action_slots[active_slot].self_modulate = Color(1, 1, 1)
|
||||
active_slot = index
|
||||
action_slots[active_slot].self_modulate = Color(1, 0.8, 0)
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
action_slots[active_slot].self_modulate = Color(1, 1, 1)
|
||||
1
hud.gd.uid
Normal file
1
hud.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c1vae5t5fabmf
|
||||
161
hud.tscn
Normal file
161
hud.tscn
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
[gd_scene format=3 uid="uid://bej3excyoxrdh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c1vae5t5fabmf" path="res://hud.gd" id="1_37p78"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_37p78"]
|
||||
bg_color = Color(0.82191056, 2.623126e-06, 7.70092e-07, 1)
|
||||
|
||||
[node name="HUD" type="CanvasLayer" unique_id=1901284390]
|
||||
script = ExtResource("1_37p78")
|
||||
|
||||
[node name="Control" type="Control" parent="." unique_id=1898217661]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="HealthBar" type="ProgressBar" parent="Control" unique_id=1012082084]
|
||||
layout_mode = 0
|
||||
offset_left = 20.0
|
||||
offset_top = 20.0
|
||||
offset_right = 220.0
|
||||
offset_bottom = 47.0
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_37p78")
|
||||
value = 100.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="HealthLabel" type="Label" parent="Control/HealthBar" unique_id=347950412]
|
||||
layout_mode = 0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 27.0
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="ActionBar" type="HBoxContainer" parent="Control" unique_id=1015761408]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 14.875001
|
||||
anchor_top = 15.125001
|
||||
anchor_right = 14.875001
|
||||
anchor_bottom = 15.125001
|
||||
offset_left = -225.00006
|
||||
offset_top = -25.000061
|
||||
offset_right = 224.99994
|
||||
offset_bottom = 24.999939
|
||||
|
||||
[node name="A1" type="Panel" parent="Control/ActionBar" unique_id=712323959]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A1" unique_id=2101503869]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "1"
|
||||
|
||||
[node name="A2" type="Panel" parent="Control/ActionBar" unique_id=1606850166]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A2" unique_id=908151683]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "2"
|
||||
|
||||
[node name="A3" type="Panel" parent="Control/ActionBar" unique_id=1199740879]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A3" unique_id=2139034524]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "3"
|
||||
|
||||
[node name="A4" type="Panel" parent="Control/ActionBar" unique_id=268936083]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A4" unique_id=1504208330]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "4"
|
||||
|
||||
[node name="A5" type="Panel" parent="Control/ActionBar" unique_id=1768061775]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A5" unique_id=1852586315]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "5"
|
||||
|
||||
[node name="A6" type="Panel" parent="Control/ActionBar" unique_id=514136908]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A6" unique_id=129205342]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "6"
|
||||
|
||||
[node name="A7" type="Panel" parent="Control/ActionBar" unique_id=930419077]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A7" unique_id=1594316102]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "7"
|
||||
|
||||
[node name="A8" type="Panel" parent="Control/ActionBar" unique_id=1826123904]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A8" unique_id=682267067]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "8"
|
||||
|
||||
[node name="A9" type="Panel" parent="Control/ActionBar" unique_id=1708070042]
|
||||
custom_minimum_size = Vector2(50, 50)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Control/ActionBar/A9" unique_id=989106090]
|
||||
custom_minimum_size = Vector2(46, 50)
|
||||
layout_mode = 0
|
||||
offset_left = 2.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 50.0
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "9"
|
||||
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://cghqrs3ts1t2y"
|
||||
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
|
||||
168
player.gd
Normal file
168
player.gd
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Player.gd
|
||||
# Steuert den Spielercharakter: Bewegung, Kamera, HP, Angriff, Zielauswahl
|
||||
extends CharacterBody3D
|
||||
|
||||
const SPEED = 5.0
|
||||
const JUMP_VELOCITY = 4.5
|
||||
const GRAVITY = 9.8
|
||||
|
||||
var max_hp = 100
|
||||
var current_hp = 100
|
||||
var can_attack = true
|
||||
var target = null # Aktuell markierter Gegner
|
||||
var equipped_weapon = null # Ausgerüstete Waffe (null = unbewaffnet, Schaden = 1)
|
||||
|
||||
@onready var camera_pivot = $CameraPivot
|
||||
@onready var camera = $CameraPivot/Camera3D
|
||||
@onready var hud = $HUD
|
||||
|
||||
func _ready():
|
||||
hud.update_health(current_hp, max_hp)
|
||||
hud.set_active_slot(0)
|
||||
|
||||
# Schaden am Spieler abziehen und HP-Leiste aktualisieren
|
||||
func take_damage(amount):
|
||||
current_hp = clamp(current_hp - amount, 0, max_hp)
|
||||
hud.update_health(current_hp, max_hp)
|
||||
if current_hp <= 0:
|
||||
die()
|
||||
|
||||
# HP heilen und HP-Leiste aktualisieren
|
||||
func heal(amount):
|
||||
current_hp = clamp(current_hp + amount, 0, max_hp)
|
||||
hud.update_health(current_hp, max_hp)
|
||||
|
||||
func die():
|
||||
print("Spieler gestorben!")
|
||||
|
||||
# Schaden basierend auf ausgerüsteter Waffe (unbewaffnet = 1)
|
||||
func get_attack_damage() -> int:
|
||||
if equipped_weapon == null:
|
||||
return 1
|
||||
return randi_range(equipped_weapon.min_damage, equipped_weapon.max_damage)
|
||||
|
||||
# Reichweite basierend auf ausgerüsteter Waffe (unbewaffnet = 1.5)
|
||||
func get_attack_range() -> float:
|
||||
if equipped_weapon == null:
|
||||
return 1.5
|
||||
return equipped_weapon.range
|
||||
|
||||
# Angriffsgeschwindigkeit basierend auf ausgerüsteter Waffe (unbewaffnet = 1.5s)
|
||||
func get_attack_cooldown() -> float:
|
||||
if equipped_weapon == null:
|
||||
return 1.5
|
||||
return equipped_weapon.attack_speed
|
||||
|
||||
# Ziel markieren — start_attack=true startet sofort die Autoattack
|
||||
func set_target(new_target, start_attack: bool = false):
|
||||
if target != null and is_instance_valid(target):
|
||||
target.hide_health()
|
||||
target = new_target
|
||||
target.show_health()
|
||||
print("Ziel markiert: ", target.name)
|
||||
if start_attack and can_attack:
|
||||
autoattack()
|
||||
|
||||
# Autoattack: greift wiederholt an solange Ziel gültig ist
|
||||
func autoattack():
|
||||
if target == null or not is_instance_valid(target):
|
||||
target = null
|
||||
return
|
||||
if not can_attack:
|
||||
return
|
||||
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
if distance <= get_attack_range():
|
||||
can_attack = false
|
||||
var dmg = get_attack_damage()
|
||||
target.take_damage(dmg)
|
||||
print("Autoattack: ", dmg, " Schaden")
|
||||
await get_tree().create_timer(get_attack_cooldown()).timeout
|
||||
can_attack = true
|
||||
autoattack()
|
||||
else:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
autoattack()
|
||||
|
||||
# Raycast von der Kamera auf Mausposition — trifft Gegner mit take_damage()
|
||||
func _try_select_target(start_attack: bool = false):
|
||||
var space_state = get_world_3d().direct_space_state
|
||||
var viewport = get_viewport()
|
||||
var mouse_pos = viewport.get_mouse_position()
|
||||
var ray_origin = camera.project_ray_origin(mouse_pos)
|
||||
var ray_end = ray_origin + camera.project_ray_normal(mouse_pos) * 100.0
|
||||
var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
|
||||
query.exclude = [self]
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result and result.collider.has_method("take_damage"):
|
||||
set_target(result.collider, start_attack)
|
||||
|
||||
func _physics_process(delta):
|
||||
# Schwerkraft
|
||||
if not is_on_floor():
|
||||
velocity.y -= GRAVITY * delta
|
||||
|
||||
# Springen
|
||||
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
|
||||
velocity.y = JUMP_VELOCITY
|
||||
|
||||
# Linksklick: nur markieren
|
||||
if Input.is_action_just_pressed("select_target"):
|
||||
_try_select_target(false)
|
||||
|
||||
# Rechtsklick: markieren + angreifen
|
||||
if Input.is_action_just_pressed("ui_right_mouse"):
|
||||
_try_select_target(true)
|
||||
|
||||
# Aktionsleiste 1-9
|
||||
if Input.is_action_just_pressed("action_1"):
|
||||
hud.set_active_slot(0)
|
||||
if target != null:
|
||||
autoattack()
|
||||
if Input.is_action_just_pressed("action_2"):
|
||||
hud.set_active_slot(1)
|
||||
if Input.is_action_just_pressed("action_3"):
|
||||
hud.set_active_slot(2)
|
||||
if Input.is_action_just_pressed("action_4"):
|
||||
hud.set_active_slot(3)
|
||||
if Input.is_action_just_pressed("action_5"):
|
||||
hud.set_active_slot(4)
|
||||
if Input.is_action_just_pressed("action_6"):
|
||||
hud.set_active_slot(5)
|
||||
if Input.is_action_just_pressed("action_7"):
|
||||
hud.set_active_slot(6)
|
||||
if Input.is_action_just_pressed("action_8"):
|
||||
hud.set_active_slot(7)
|
||||
if Input.is_action_just_pressed("action_9"):
|
||||
hud.set_active_slot(8)
|
||||
|
||||
# TEST: T drücken = 10 Schaden
|
||||
if Input.is_action_just_pressed("test_damage"):
|
||||
take_damage(10)
|
||||
|
||||
# Eingabe
|
||||
var input_dir = Vector2.ZERO
|
||||
if Input.is_action_pressed("move_forward"):
|
||||
input_dir.y -= 1
|
||||
if Input.is_action_pressed("move_back"):
|
||||
input_dir.y += 1
|
||||
if Input.is_action_pressed("move_left"):
|
||||
input_dir.x -= 1
|
||||
if Input.is_action_pressed("move_right"):
|
||||
input_dir.x += 1
|
||||
|
||||
# Bewegung relativ zur Kamera
|
||||
var world_yaw = rotation.y + camera_pivot.rotation.y
|
||||
var forward = Vector3(-sin(world_yaw), 0, -cos(world_yaw)).normalized()
|
||||
var right = Vector3(cos(world_yaw), 0, -sin(world_yaw)).normalized()
|
||||
var direction = (forward * -input_dir.y + right * input_dir.x)
|
||||
|
||||
velocity.x = direction.x * SPEED
|
||||
velocity.z = direction.z * SPEED
|
||||
|
||||
# RMB gehalten: Spieler schaut in Kamerarichtung
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
rotation.y = world_yaw
|
||||
camera_pivot.rotation.y = 0
|
||||
|
||||
move_and_slide()
|
||||
1
player.gd.uid
Normal file
1
player.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b6n25e5fh82ra
|
||||
29
player.tscn
Normal file
29
player.tscn
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[gd_scene format=3 uid="uid://dniyuebl8yhtv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b6n25e5fh82ra" path="res://player.gd" id="1_4flbx"]
|
||||
[ext_resource type="PackedScene" uid="uid://01rrtitc6yh1" path="res://assets/kenney_blocky-characters_20/Models/GLB format/character-b.glb" id="2_hqtel"]
|
||||
[ext_resource type="Script" uid="uid://bwtwon54po4w3" path="res://camera_pivot.gd" id="2_onrkg"]
|
||||
[ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="4_hqtel"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"]
|
||||
radius = 0.6
|
||||
height = 3.0
|
||||
|
||||
[node name="CharacterBody3D" type="CharacterBody3D" unique_id=937297102]
|
||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0)
|
||||
script = ExtResource("1_4flbx")
|
||||
|
||||
[node name="character-b2" parent="." unique_id=926968795 instance=ExtResource("2_hqtel")]
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1359412306]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0)
|
||||
shape = SubResource("CapsuleShape3D_4flbx")
|
||||
|
||||
[node name="CameraPivot" type="Node3D" parent="." unique_id=638440275]
|
||||
script = ExtResource("2_onrkg")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1625345908]
|
||||
transform = Transform3D(2, 0, 0, 0, 1.8126155, 0.84523654, 0, -0.84523654, 1.8126155, 0, 5, 5)
|
||||
|
||||
[node name="HUD" parent="." unique_id=1901284390 instance=ExtResource("4_hqtel")]
|
||||
107
project.godot
Normal file
107
project.godot
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
; 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="DungeonCrawler"
|
||||
run/main_scene="uid://bp0g1glxo816h"
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[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)
|
||||
]
|
||||
}
|
||||
test_damage={
|
||||
"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":84,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_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)
|
||||
]
|
||||
}
|
||||
action_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)
|
||||
]
|
||||
}
|
||||
action_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)
|
||||
]
|
||||
}
|
||||
action_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)
|
||||
]
|
||||
}
|
||||
action_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)
|
||||
]
|
||||
}
|
||||
action_6={
|
||||
"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":54,"key_label":0,"unicode":54,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_7={
|
||||
"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":55,"key_label":0,"unicode":55,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_8={
|
||||
"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":56,"key_label":0,"unicode":56,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_9={
|
||||
"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":57,"key_label":0,"unicode":57,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
select_target={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(250, 17),"global_position":Vector2(259, 65),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
ui_right_mouse={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":2,"position":Vector2(411, 19),"global_position":Vector2(420, 67),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
3d/physics_engine="Jolt Physics"
|
||||
|
||||
[rendering]
|
||||
|
||||
rendering_device/driver.windows="d3d12"
|
||||
28
resources/attack.gd
Normal file
28
resources/attack.gd
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Attack.gd
|
||||
# Ressource für Angriffe — Schaden, Reichweite und Cooldown kommen von der ausgerüsteten Waffe
|
||||
extends Resource
|
||||
class_name Attack
|
||||
|
||||
enum DamageType { PHYSICAL, FIRE, ICE, LIGHTNING, POISON }
|
||||
|
||||
@export var name: String = "Unbenannt"
|
||||
@export var damage_type: DamageType = DamageType.PHYSICAL
|
||||
@export var icon: Texture2D = null
|
||||
|
||||
# Schaden berechnen — ohne Waffe = 1
|
||||
func get_damage(weapon: Weapon) -> int:
|
||||
if weapon == null or weapon.weapon_type == Weapon.WeaponType.UNARMED:
|
||||
return 1
|
||||
return randi_range(weapon.min_damage, weapon.max_damage)
|
||||
|
||||
# Reichweite — ohne Waffe = 1.5
|
||||
func get_range(weapon: Weapon) -> float:
|
||||
if weapon == null or weapon.weapon_type == Weapon.WeaponType.UNARMED:
|
||||
return 1.5
|
||||
return weapon.range
|
||||
|
||||
# Cooldown — ohne Waffe = 1.5s
|
||||
func get_cooldown(weapon: Weapon) -> float:
|
||||
if weapon == null or weapon.weapon_type == Weapon.WeaponType.UNARMED:
|
||||
return 1.5
|
||||
return weapon.attack_speed
|
||||
1
resources/attack.gd.uid
Normal file
1
resources/attack.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cpa1p4k6xtiga
|
||||
14
resources/weapon.gd
Normal file
14
resources/weapon.gd
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Weapon.gd
|
||||
# Ressource für Waffen — definiert Schadensbereich, Angriffsgeschwindigkeit und Reichweite
|
||||
extends Resource
|
||||
class_name Weapon
|
||||
|
||||
enum WeaponType { UNARMED, SWORD, AXE, MACE, DAGGER, STAFF, BOW }
|
||||
|
||||
@export var name: String = "Unbewaffnet"
|
||||
@export var weapon_type: WeaponType = WeaponType.UNARMED
|
||||
@export var min_damage: int = 1
|
||||
@export var max_damage: int = 1
|
||||
@export var attack_speed: float = 1.5 # Cooldown in Sekunden
|
||||
@export var range: float = 2.0
|
||||
@export var icon: Texture2D = null
|
||||
1
resources/weapon.gd.uid
Normal file
1
resources/weapon.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c0jenjw4stba2
|
||||
11
world.gd
Normal file
11
world.gd
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# World.gd
|
||||
# Initialisiert die Spielwelt: weist dem Gegner den Spieler als Ziel zu
|
||||
extends Node3D
|
||||
|
||||
func _ready():
|
||||
var player = get_node("Player")
|
||||
var enemy = get_node("Enemy")
|
||||
if enemy and player:
|
||||
enemy.target = player
|
||||
else:
|
||||
print("Fehler: Player oder Enemy nicht gefunden!")
|
||||
1
world.gd.uid
Normal file
1
world.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cx56h588mfsk0
|
||||
38
world.tscn
Normal file
38
world.tscn
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
[gd_scene format=3 uid="uid://bp0g1glxo816h"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://dniyuebl8yhtv" path="res://player.tscn" id="1_f3sb7"]
|
||||
[ext_resource type="Script" uid="uid://cx56h588mfsk0" path="res://world.gd" id="1_tlwt5"]
|
||||
[ext_resource type="PackedScene" uid="uid://cvojaeanxugfj" path="res://enemy.tscn" id="2_fj7yv"]
|
||||
[ext_resource type="Script" uid="uid://bg5qs3pcfp7p7" path="res://enemy.gd" id="4_aqk2v"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_fj7yv"]
|
||||
size = Vector3(200, 0.5, 200)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_tlwt5"]
|
||||
size = Vector3(200, 0.5, 200)
|
||||
|
||||
[sub_resource type="NavigationMesh" id="NavigationMesh_fj7yv"]
|
||||
|
||||
[node name="World" type="Node3D" unique_id=2007838514]
|
||||
script = ExtResource("1_tlwt5")
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=2101916269]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=1873339390]
|
||||
shape = SubResource("BoxShape3D_fj7yv")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D" unique_id=1214783061]
|
||||
mesh = SubResource("BoxMesh_tlwt5")
|
||||
|
||||
[node name="Player" parent="." unique_id=937297102 instance=ExtResource("1_f3sb7")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1394887598]
|
||||
transform = Transform3D(-45, 0, 0, 0, -45, 0, 0, 0, -45, 0, 0, 0)
|
||||
|
||||
[node name="Enemy" parent="." unique_id=332011146 instance=ExtResource("2_fj7yv")]
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0.3, -7.038)
|
||||
script = ExtResource("4_aqk2v")
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=827244005]
|
||||
navigation_mesh = SubResource("NavigationMesh_fj7yv")
|
||||
Loading…
Add table
Reference in a new issue