godot-combat-system

安装量: 54
排名: #13790

安装

npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-combat-system
Combat System
Expert guidance for building flexible, component-based combat systems.
NEVER Do
NEVER use direct damage references (
target.health -= 10
)
— Bypasses armor, resistance, events. Use DamageData + HealthComponent pattern.
NEVER forget invincibility frames
— Without i-frames, multi-hit attacks deal damage every frame. Add 0.5-1s invincibility after hit.
NEVER keep hitboxes active permanently
— Enable/disable hitboxes with animation tracks. Permanent hitboxes cause unintended damage.
NEVER use groups for hitbox filtering
— Use collision layers. Groups don't respect physics layers and cause friendly fire.
NEVER emit damage_received without DamageData
— Raw int/float damage loses context (source, type, knockback). Always use DamageData class.
Available Scripts
MANDATORY
Read the appropriate script before implementing the corresponding pattern. hitbox_hurtbox.gd Component-based hitbox with hit-stop and knockback. Uses Engine.time_scale with ignore_time_scale timer for proper hit-stop freeze frame. Damage System

damage_data.gd

class_name DamageData extends RefCounted var amount : float var source : Node var damage_type : String = "physical" var knockback : Vector2 = Vector2 . ZERO var is_critical : bool = false func _init ( dmg : float , src : Node = null ) -> void : amount = dmg source = src Hurtbox/Hitbox Pattern

hurtbox.gd

extends Area2D class_name Hurtbox signal damage_received ( data : DamageData ) @ export var health_component : Node func _ready ( ) -> void : area_entered . connect ( _on_area_entered ) func _on_area_entered ( area : Area2D ) -> void : if area is Hitbox : var damage := area . get_damage ( ) damage_received . emit ( damage ) if health_component : health_component . take_damage ( damage )

hitbox.gd

extends Area2D class_name Hitbox @ export var damage : float = 10.0 @ export var damage_type : String = "physical" @ export var knockback_force : float = 100.0 @ export var owner_node : Node func get_damage ( ) -> DamageData : var data := DamageData . new ( damage , owner_node ) data . damage_type = damage_type

Calculate knockback direction

if owner_node : var direction := ( global_position - owner_node . global_position ) . normalized ( ) data . knockback = direction * knockback_force return data Health Component

health_component.gd

extends Node class_name HealthComponent signal health_changed ( old_health : float , new_health : float ) signal died signal healed ( amount : float ) @ export var max_health : float = 100.0 @ export var current_health : float = 100.0 @ export var invincible : bool = false func take_damage ( data : DamageData ) -> void : if invincible : return var old_health := current_health current_health -= data . amount current_health = clampf ( current_health , 0 , max_health ) health_changed . emit ( old_health , current_health ) if current_health <= 0 : died . emit ( ) func heal ( amount : float ) -> void : var old_health := current_health current_health += amount current_health = minf ( current_health , max_health ) healed . emit ( amount ) health_changed . emit ( old_health , current_health ) func is_dead ( ) -> bool : return current_health <= 0 Combat State Machine

combat_state.gd

extends Node class_name CombatState enum State { IDLE , ATTACKING , BLOCKING , DODGING , STUNNED } var current_state : State = State . IDLE var can_act : bool = true func enter_attack_state ( ) -> bool : if not can_act : return false current_state = State . ATTACKING can_act = false return true func enter_block_state ( ) -> void : current_state = State . BLOCKING func enter_dodge_state ( ) -> bool : if not can_act : return false current_state = State . DODGING can_act = false return true func exit_state ( ) -> void : current_state = State . IDLE can_act = true Combo System

combo_system.gd

extends Node class_name ComboSystem signal combo_executed ( combo_name : String ) @ export var combo_window : float = 0.5 var combo_buffer : Array [ String ] = [ ] var last_input_time : float = 0.0 func register_input ( action : String ) -> void : var current_time := Time . get_ticks_msec ( ) / 1000.0 if current_time - last_input_time

combo_window : combo_buffer . clear ( ) combo_buffer . append ( action ) last_input_time = current_time check_combos ( ) func check_combos ( ) -> void :

Light → Light → Heavy = Special Attack

if combo_buffer . size ( )

= 3 : var last_three := combo_buffer . slice ( - 3 ) if last_three == [ "light" , "light" , "heavy" ] : execute_combo ( "special_attack" ) combo_buffer . clear ( ) func execute_combo ( combo_name : String ) -> void : combo_executed . emit ( combo_name ) Ability System

ability.gd

class_name Ability extends Resource @ export var ability_name : String @ export var cooldown : float = 1.0 @ export var damage : float = 25.0 @ export var range : float = 100.0 @ export var animation : String var is_on_cooldown : bool = false func can_use ( ) -> bool : return not is_on_cooldown func use ( caster : Node ) -> void : if not can_use ( ) : return is_on_cooldown = true

Execute ability logic

_execute ( caster )

Start cooldown

await caster . get_tree ( ) . create_timer ( cooldown ) . timeout is_on_cooldown = false func _execute ( caster : Node ) -> void :

Override in derived abilities

pass Damage Popups

damage_popup.gd

extends Label func show_damage ( amount : float , is_crit : bool = false ) -> void : text = str ( int ( amount ) ) if is_crit : modulate = Color . RED scale = Vector2 ( 1.5 , 1.5 ) var tween := create_tween ( ) tween . set_parallel ( true ) tween . tween_property ( self , "position:y" , position . y - 50 , 1.0 ) tween . tween_property ( self , "modulate:a" , 0.0 , 1.0 ) tween . finished . connect ( queue_free ) Critical Hits func calculate_damage ( base_damage : float , crit_chance : float = 0.1 ) -> DamageData : var data := DamageData . new ( base_damage ) if randf ( ) < crit_chance : data . is_critical = true data . amount *= 2.0 return data Best Practices Separate Concerns - Health ≠ Combat ≠ Movement Use Signals - Decouple systems Area2D for Hitboxes - Built-in collision detection Invincibility Frames - Prevent spam damage Reference Related: godot-2d-physics , godot-animation-player , godot-characterbody-2d Related Master Skill: godot-master

返回排行榜