Waffen-Modell an Spielerhand via BoneAttachment3D

Schwert (medieval_sword.glb) wird zur Laufzeit an mixamorig_RightHand
gehängt. Modell erscheint/verschwindet beim Ausrüsten/Ablegen.
Equipment-Ressource um model_scene Property erweitert.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andre 2026-03-17 01:25:19 +01:00
parent e4efb239f2
commit 9ed18e034c
13 changed files with 241 additions and 2 deletions

View file

@ -190,6 +190,13 @@ Resource-Klasse für alle Ausrüstungsgegenstände.
- **iron_helm.tres** — Eisenhelm (Kopf)
- **wooden_shield.tres** — Holzschild (Nebenhand)
### Waffen-Modell am Charakter (BoneAttachment3D)
- Schwert-Modell (`medieval_sword.glb`) wird zur Laufzeit an die rechte Hand (`mixamorig_RightHand`) des Spielers gehängt
- Verwendet `BoneAttachment3D` am Skeleton3D des Spielermodells
- Modell wird beim Ausrüsten einer Waffe (`equip_item`) angezeigt und beim Ablegen entfernt
- Position/Rotation/Scale werden im Code gesetzt: `Vector3(0.5, 0.2, 0)`, `Vector3(90, 180, 0)`, `Vector3(0.8, 0.8, 0.8)`
- Funktionen: `_setup_weapon_attachment()`, `_attach_weapon_model()`, `_remove_weapon_model()`
### Character Panel (character_panel.gd, C-Taste)
Zeigt zwei Spalten:
- **Stats-Spalte:** Stärke, Beweglichkeit, Intelligenz, Ausdauer, Rüstung, HP

Binary file not shown.

View file

@ -0,0 +1,42 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://8klqbvdg43b3"
path="res://.godot/imported/medieval_sword.glb-6bd5ad7fbf20317ed64b8bc381ee05fb.scn"
[deps]
source_file="res://assets/Weapons/medieval_sword.glb"
dest_files=["res://.godot/imported/medieval_sword.glb-6bd5ad7fbf20317ed64b8bc381ee05fb.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

View file

@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cr8svhu2algti"
path.s3tc="res://.godot/imported/medieval_sword_0.jpg-94723da2994d60d5619ffcd032251761.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "e8adc51a863b1e90561d52d69ad93bfc"
}
[deps]
source_file="res://assets/Weapons/medieval_sword_0.jpg"
dest_files=["res://.godot/imported/medieval_sword_0.jpg-94723da2994d60d5619ffcd032251761.s3tc.ctex"]
[params]
compress/mode=2
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=true
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=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

View file

@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://51b8qklemnky"
path.s3tc="res://.godot/imported/medieval_sword_1.png-a07317cea2a7cf66dc265905e26d7546.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "694b37b7841572959ed4f217aef8380f"
}
[deps]
source_file="res://assets/Weapons/medieval_sword_1.png"
dest_files=["res://.godot/imported/medieval_sword_1.png-a07317cea2a7cf66dc265905e26d7546.s3tc.ctex"]
[params]
compress/mode=2
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=true
mipmaps/limit=-1
roughness/mode=8
roughness/src_normal="res://assets/Weapons/medieval_sword_2.jpg"
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=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

View file

@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dwb4r1domnogc"
path.s3tc="res://.godot/imported/medieval_sword_2.jpg-5b274ce8d251c67b5d49b4af74f182a1.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "c9583464c97ca6ecfbb32b19e1fa9180"
}
[deps]
source_file="res://assets/Weapons/medieval_sword_2.jpg"
dest_files=["res://.godot/imported/medieval_sword_2.jpg-5b274ce8d251c67b5d49b4af74f182a1.s3tc.ctex"]
[params]
compress/mode=2
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=1
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=1
roughness/src_normal="res://assets/Weapons/medieval_sword_2.jpg"
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=0

View file

@ -38,6 +38,9 @@ enum Rarity {
@export var attack_speed: float = 1.5 # Sekunden zwischen Angriffen
@export var weapon_range: float = 3.0
# 3D-Modell (GLB/GLTF) wird an der Hand des Spielers angezeigt
@export var model_scene: PackedScene
# Icon für UI
@export var icon: Texture2D

View file

@ -1,7 +1,8 @@
[gd_resource type="Resource" script_class="Equipment" format=3 uid="uid://vprfv2phlcbc"]
[gd_resource type="Resource" script_class="Equipment" load_steps=4 format=3 uid="uid://vprfv2phlcbc"]
[ext_resource type="Script" uid="uid://re0xiie1udfq" path="res://equipment.gd" id="1_equipment"]
[ext_resource type="Texture2D" uid="uid://bi7sh66txlj1c" path="res://icons/iron_sword_icon.svg" id="2_icon"]
[ext_resource type="PackedScene" uid="uid://8klqbvdg43b3" path="res://assets/Weapons/medieval_sword.glb" id="3_model"]
[resource]
script = ExtResource("1_equipment")
@ -9,4 +10,5 @@ item_name = "Eisenschwert"
strength = 2
min_damage = 3
max_damage = 6
model_scene = ExtResource("3_model")
icon = ExtResource("2_icon")

View file

@ -1,7 +1,8 @@
[gd_resource type="Resource" script_class="Equipment" load_steps=3 format=3]
[gd_resource type="Resource" script_class="Equipment" load_steps=4 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
[ext_resource type="Texture2D" path="res://icons/steel_sword_icon.svg" id="2_icon"]
[ext_resource type="PackedScene" uid="uid://8klqbvdg43b3" path="res://assets/Weapons/medieval_sword.glb" id="3_model"]
[resource]
script = ExtResource("1_equipment")
@ -17,4 +18,5 @@ min_damage = 5
max_damage = 9
attack_speed = 1.4
weapon_range = 3.0
model_scene = ExtResource("3_model")
icon = ExtResource("2_icon")

View file

@ -141,6 +141,9 @@ var target = null # Aktuell markierter Gegner
# ═══════════════════════════════════════════════════════════════
var anim_player: AnimationPlayer = null
var skeleton: Skeleton3D = null
var weapon_attachment: BoneAttachment3D = null
var current_weapon_model: Node3D = null
var is_attacking: bool = false
var is_dead: bool = false
var is_walking: bool = false
@ -346,6 +349,9 @@ func equip_item(item: Equipment) -> Equipment:
hud.update_resource(current_resource, max_resource, get_resource_name())
if character_panel and character_panel.panel_visible:
character_panel.update_stats(self)
# Waffen-Modell aktualisieren
if slot == Equipment.Slot.WEAPON and weapon_attachment != null:
_attach_weapon_model(item)
return old_item
func get_equipped_weapon() -> Equipment:
@ -752,6 +758,51 @@ func _setup_animations():
_play_anim("idle")
# Waffen-Attachment einrichten
_setup_weapon_attachment()
func _setup_weapon_attachment():
if model == null:
print("Player Weapon: Kein Model!")
return
skeleton = model.find_child("Skeleton3D", true, false) as Skeleton3D
if skeleton == null:
push_warning("Player: Kein Skeleton3D gefunden!")
return
# Alle Bones auflisten zum Debuggen
print("Player Bones: ", skeleton.get_bone_count())
for i in range(skeleton.get_bone_count()):
if "Hand" in skeleton.get_bone_name(i) or "hand" in skeleton.get_bone_name(i):
print(" Bone ", i, ": ", skeleton.get_bone_name(i))
var bone_idx = skeleton.find_bone("mixamorig_RightHand")
if bone_idx == -1:
push_warning("Player: Bone 'mixamorig_RightHand' nicht gefunden!")
return
print("Player Weapon: RightHand Bone gefunden (idx=", bone_idx, ")")
weapon_attachment = BoneAttachment3D.new()
weapon_attachment.bone_name = "mixamorig_RightHand"
skeleton.add_child(weapon_attachment)
func _attach_weapon_model(weapon: Equipment):
_remove_weapon_model()
if weapon == null or weapon.model_scene == null:
print("Player Weapon: Kein Model an Waffe! model_scene=", weapon.model_scene if weapon else "null")
return
if weapon_attachment == null:
print("Player Weapon: Kein weapon_attachment!")
return
current_weapon_model = weapon.model_scene.instantiate()
current_weapon_model.rotation_degrees = Vector3(90, 180, 0)
current_weapon_model.position = Vector3(0.5, 0.2, 0)
current_weapon_model.scale = Vector3(0.8, 0.8, 0.8)
weapon_attachment.add_child(current_weapon_model)
print("Player Weapon: Schwert angehängt! Scale=", current_weapon_model.scale)
func _remove_weapon_model():
if current_weapon_model != null and is_instance_valid(current_weapon_model):
current_weapon_model.queue_free()
current_weapon_model = null
func _load_anim_from_fbx(fbx_path: String, anim_name: String, loop: bool = false):
var scene = load(fbx_path)
if scene == null: