diff --git a/level/scenes/Player_Lilguy.tscn b/level/scenes/Player_Lilguy.tscn index 1d8a8df..5277a36 100644 --- a/level/scenes/Player_Lilguy.tscn +++ b/level/scenes/Player_Lilguy.tscn @@ -4,7 +4,7 @@ [ext_resource type="PackedScene" uid="uid://b22ou40sbkavj" path="res://assets/characters/player/LilguyRigged.glb" id="2_lilguy"] [ext_resource type="Script" uid="uid://cf7jky1bcs560" path="res://level/scripts/lilguy_body.gd" id="3_body"] [ext_resource type="Script" uid="uid://bj7yrijm7bppq" path="res://level/scripts/spring_arm_offset.gd" id="4_spring"] -[ext_resource type="Script" path="res://level/scripts/hurt_box.gd" id="5_hurtbox"] +[ext_resource type="Script" uid="uid://bj3uepduxvgju" path="res://level/scripts/hurt_box.gd" id="5_hurtbox"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_yxyay"] radius = 0.35796 @@ -110,7 +110,7 @@ bone_idx = 10 transform = Transform3D(-17.74905, -295.46814, -48.82108, 21.019196, -50.01525, 295.05362, -298.73593, 14.035805, 23.660797, 0.005859375, 0.39337158, 0.06616211) [node name="CollisionShape3D" type="CollisionShape3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.066, 0.828, 0.01) +transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, -0.066, 1.647685, 0.01) shape = SubResource("CapsuleShape3D_yxyay") [node name="HurtBox" type="Area3D" parent="." node_paths=PackedStringArray("owner_entity")] @@ -120,7 +120,7 @@ script = ExtResource("5_hurtbox") owner_entity = NodePath("..") [node name="HurtBoxShape" type="CollisionShape3D" parent="HurtBox"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.066, 0.828, 0.01) +transform = Transform3D(1.9228287, 0, 0, 0, 1.4454772, 0, 0, 0, 1.4906956, -0.066, 2.0836046, 0.01) shape = SubResource("CapsuleShape3D_hurtbox") [node name="SpringArmOffset" type="Node3D" parent="." node_paths=PackedStringArray("_spring_arm")] diff --git a/level/scenes/weapons/world_weapon_sword.tscn b/level/scenes/weapons/world_weapon_sword.tscn index ac15ed0..b56f548 100644 --- a/level/scenes/weapons/world_weapon_sword.tscn +++ b/level/scenes/weapons/world_weapon_sword.tscn @@ -1,6 +1,6 @@ -[gd_scene load_steps=4 format=3] +[gd_scene load_steps=4 format=3 uid="uid://byxqw8bg5da2c"] -[ext_resource type="Script" path="res://level/scripts/world_weapon.gd" id="1"] +[ext_resource type="Script" uid="uid://ccnnd0y4jqiot" path="res://level/scripts/world_weapon.gd" id="1"] [ext_resource type="Resource" path="res://level/resources/weapon_sword.tres" id="2"] [sub_resource type="BoxShape3D" id="1"] diff --git a/level/scripts/base_weapon.gd b/level/scripts/base_weapon.gd index d9eda18..ff253d3 100644 --- a/level/scripts/base_weapon.gd +++ b/level/scripts/base_weapon.gd @@ -90,6 +90,12 @@ func _on_hitbox_hit(target: Node, damage_amount: float, knockback_amount: float, if not target or not owner_character: return + # Flash the target's hurtbox red for visual feedback + if target is Node: + var hurtbox = target.find_child("HurtBox", true, false) + if hurtbox and hurtbox.has_method("flash_hit"): + hurtbox.flash_hit() + hit_connected.emit(target) # Route damage through server @@ -145,7 +151,7 @@ func perform_attack() -> bool: attack_performed.emit() return true -## Activate the hitbox for attack detection using frame data timing +## Activate the hitbox for attack detection - active for entire animation func _activate_hitbox(): if not _hitbox: return @@ -153,21 +159,14 @@ func _activate_hitbox(): # Update owner reference in case it changed _hitbox.owner_entity = owner_character - # STARTUP PHASE - Wait before activating hitbox (wind-up) - var startup = weapon_data.startup_time if weapon_data else 0.15 - if startup > 0: - await get_tree().create_timer(startup).timeout - - if not _hitbox or not is_instance_valid(_hitbox): - return - - # ACTIVE PHASE - Hitbox is on, can deal damage + # Activate hitbox immediately for entire animation duration _hitbox.activate() - var active = weapon_data.active_time if weapon_data else 0.2 - await get_tree().create_timer(active).timeout + # Keep active for the full attack cooldown + var duration = weapon_data.attack_cooldown if weapon_data else 0.5 + await get_tree().create_timer(duration).timeout - # RECOVERY PHASE - Hitbox off (recovery time is remaining cooldown) + # Deactivate when attack is complete if _hitbox and is_instance_valid(_hitbox): _hitbox.deactivate() diff --git a/level/scripts/hit_box.gd b/level/scripts/hit_box.gd index b10e415..6e56593 100644 --- a/level/scripts/hit_box.gd +++ b/level/scripts/hit_box.gd @@ -20,14 +20,50 @@ var is_active: bool = false var _hits_this_attack: Array[Node] = [] ## Shape for queries (extracted from child CollisionShape3D) var _query_shape: Shape3D = null +## Debug mesh for visualization +var _debug_mesh: MeshInstance3D = null +var _debug_material: StandardMaterial3D = null func _ready(): # Find the collision shape for queries for child in get_children(): if child is CollisionShape3D and child.shape: _query_shape = child.shape + _create_debug_visualization(child) break +func _create_debug_visualization(collision_shape: CollisionShape3D): + # Create a semi-transparent red mesh to visualize the hitbox + _debug_mesh = MeshInstance3D.new() + _debug_material = StandardMaterial3D.new() + _debug_material.albedo_color = Color(1.0, 0.0, 0.0, 0.4) # Red, semi-transparent + _debug_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + _debug_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + _debug_material.cull_mode = BaseMaterial3D.CULL_DISABLED # Visible from both sides + + # Create mesh matching the collision shape + var mesh: Mesh = null + if collision_shape.shape is BoxShape3D: + var box_mesh = BoxMesh.new() + box_mesh.size = collision_shape.shape.size + mesh = box_mesh + elif collision_shape.shape is SphereShape3D: + var sphere_mesh = SphereMesh.new() + sphere_mesh.radius = collision_shape.shape.radius + sphere_mesh.height = collision_shape.shape.radius * 2 + mesh = sphere_mesh + elif collision_shape.shape is CapsuleShape3D: + var capsule_mesh = CapsuleMesh.new() + capsule_mesh.radius = collision_shape.shape.radius + capsule_mesh.height = collision_shape.shape.height + mesh = capsule_mesh + + if mesh: + _debug_mesh.mesh = mesh + _debug_mesh.material_override = _debug_material + # Don't set transform - it inherits from parent CollisionShape3D + collision_shape.add_child(_debug_mesh) + func _physics_process(_delta): if not is_active: return @@ -83,11 +119,17 @@ func _process_hit(hurtbox: HurtBox): func activate(): is_active = true _hits_this_attack.clear() + # Change to yellow when active + if _debug_material: + _debug_material.albedo_color = Color(1.0, 1.0, 0.0, 0.5) # Yellow, semi-transparent ## Deactivate hitbox (call when attack ends) func deactivate(): is_active = false _hits_this_attack.clear() + # Change back to red when inactive + if _debug_material: + _debug_material.albedo_color = Color(1.0, 0.0, 0.0, 0.4) # Red, semi-transparent ## Set damage stats (usually from weapon data) func set_stats(new_damage: float, new_knockback: float): diff --git a/level/scripts/hurt_box.gd b/level/scripts/hurt_box.gd index e7dfe37..e2f5c19 100644 --- a/level/scripts/hurt_box.gd +++ b/level/scripts/hurt_box.gd @@ -7,6 +7,11 @@ class_name HurtBox ## The entity that owns this hurtbox (should be a BaseUnit or similar) @export var owner_entity: Node = null +## Debug mesh for visualization +var _debug_mesh: MeshInstance3D = null +var _debug_material: StandardMaterial3D = null +var _hit_flash_timer: float = 0.0 +const HIT_FLASH_DURATION: float = 0.3 # seconds func _ready(): # Auto-find owner if not set (traverse up to find BaseUnit) @@ -25,3 +30,57 @@ func _ready(): # Ensure we can be detected but don't detect others monitorable = true monitoring = false + + # Add debug visualization + _create_debug_visualization() + +func _process(delta): + # Handle hit flash timer + if _hit_flash_timer > 0.0: + _hit_flash_timer -= delta + if _hit_flash_timer <= 0.0: + # Flash finished, return to green + if _debug_material: + _debug_material.albedo_color = Color(0.0, 1.0, 0.0, 0.3) # Green + +func _create_debug_visualization(): + # Find the collision shape to visualize + for child in get_children(): + if child is CollisionShape3D and child.shape: + # Create a semi-transparent green mesh to visualize the hurtbox + _debug_mesh = MeshInstance3D.new() + _debug_material = StandardMaterial3D.new() + _debug_material.albedo_color = Color(0.0, 1.0, 0.0, 0.3) # Green, semi-transparent + _debug_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + _debug_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + _debug_material.cull_mode = BaseMaterial3D.CULL_DISABLED # Visible from both sides + + # Create mesh matching the collision shape + var mesh: Mesh = null + if child.shape is BoxShape3D: + var box_mesh = BoxMesh.new() + box_mesh.size = child.shape.size + mesh = box_mesh + elif child.shape is SphereShape3D: + var sphere_mesh = SphereMesh.new() + sphere_mesh.radius = child.shape.radius + sphere_mesh.height = child.shape.radius * 2 + mesh = sphere_mesh + elif child.shape is CapsuleShape3D: + var capsule_mesh = CapsuleMesh.new() + capsule_mesh.radius = child.shape.radius + capsule_mesh.height = child.shape.height + mesh = capsule_mesh + + if mesh: + _debug_mesh.mesh = mesh + _debug_mesh.material_override = _debug_material + # Don't set transform - it inherits from parent CollisionShape3D + child.add_child(_debug_mesh) + break + +## Call this when the hurtbox is hit to flash red +func flash_hit(): + _hit_flash_timer = HIT_FLASH_DURATION + if _debug_material: + _debug_material.albedo_color = Color(1.0, 0.0, 0.0, 0.5) # Red, semi-transparent diff --git a/level/scripts/lilguy_body.gd b/level/scripts/lilguy_body.gd index 66978d9..5d3ca5f 100644 --- a/level/scripts/lilguy_body.gd +++ b/level/scripts/lilguy_body.gd @@ -62,6 +62,6 @@ func play_attack(anim_name: String = "Attack_OneHand") -> void: animation_player.animation_set_next(anim_name, "") else: # Fallback to default if animation doesn't exist - push_warning("Animation '%s' not found, using Attack_OneHand" % anim_name) + #push_warning("Animation '%s' not found, using Attack_OneHand" % anim_name) animation_player.play("Attack_OneHand", -1, 1.0) animation_player.animation_set_next("Attack_OneHand", "")