
Godot Genre Action Rpg
- 177 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-genre-action-rpg for development tasks
About
godot-genre-action-rpg: A skill for development. This provides functionality for development workflows.
- godot-genre-action-rpg
Godot Genre Action Rpg by the numbers
- 177 all-time installs (skills.sh)
- +11 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,211 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-action-rpgAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 177 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-genre-action-rpg for development tasks
Files
Genre: Action RPG
Expert blueprint for action RPGs emphasizing real-time combat, character builds, loot, and progression.
NEVER Do (Expert Anti-Patterns)
Combat & Progression
- NEVER use linear damage scaling for progression; strictly use an exponential curve (e.g.,
base * pow(1.15, level)) to maintain the power fantasy. - NEVER allow defense stats to stack linearly to 100%; strictly use a Diminishing Returns formula (e.g.,
armor / (armor + 100.0)) to prevent invincibility. - NEVER skip Hit Recovery (Stagger); strictly implement a brief stagger state (0.2s - 0.5s) on significant hits to prevent "floaty" combat.
- NEVER hide critical stats from the player; strictly provide a detailed character sheet for theory-crafting (Crit Chance, Resistance, etc.).
- NEVER make loot drops visually identical; strictly differentiate rarities with color-coded beams (purple/gold) and distinct sound cues.
- NEVER calculate hitboxes, knockbacks, or combat movement in
_process(); strictly use_physics_process()for deterministic results. - NEVER evaluate exact floating-point equality (==) for combat thresholds; strictly use
is_equal_approx(). - NEVER use the ! (NOT) operator in AnimationTree Advance Condition expressions; strictly use explicit boolean equality (
is_walking == false).
Technical & Architecture
- NEVER store character stats or massive inventories as Nodes; strictly use Resource-based data containers for lightweight memory overhead.
- NEVER forget to call
duplicate()on shared Resources; modifying one goblin's stats must not affect all other instances. - NEVER rigidly couple combat detection to specific classes; strictly use Duck-Typing (e.g.,
if body.has_method(&"take_damage")) for interaction. - NEVER rely on the UI SceneTree as the source of truth for inventory; strictly separate data logic from visualization.
- NEVER recalculate stats every frame; strictly trigger recalculation only on gear changes or level-ups.
- NEVER parse massive RPG save files synchronously; strictly offload heavy parsing to the
WorkerThreadPool. - NEVER synchronize complex Resource types over the network; strictly serialize changes into primitive Dictionaries or PackedByteArrays.
- NEVER manage character state by coupling child nodes to parent existence; strictly use signals for loose coupling ("Signal Up, Call Down").
- NEVER use standard Strings for high-frequency AI state identifiers; strictly use
StringNamefor optimized hash comparisons.
Performance & AI
- NEVER instantiate/destroy hundreds of objects (projectiles, damage text) per second; strictly use Object Pooling.
- NEVER delete active combat entities via
free(); strictly usequeue_free()for safe deferred disposal. - NEVER calculate complex loot drops or parse massive late-game inventories on the main thread; strictly offload heavy RNG rolls and array iterations to the WorkerThreadPool.
- NEVER use nested if/elif blocks for complex Boss AI; strictly use a modular StateMachine or pattern matching.
- NEVER iterate through the SceneTree for global state changes; strictly use Signal Groups (
call_group()). - NEVER move
OccluderInstance3Dnodes attached to dynamic characters; this causes CPU BVH rebuild stalls.
---
🛠 Expert Components (scripts/)
Original Expert Patterns
- damage_label_manager.gd - High-performance pooled system for floating damage numbers and critical hits.
- telegraphed_enemy.gd - Advanced AI component for Soul-like wind-ups, AOE indicators, and timed attacks.
Modular Components
- character_stats_resource.gd - Modular data container for base RPG attributes and scaling logic.
- entity_stat_duplicator.gd - Pattern for ensuring unique death/health state for instanced enemies.
- duck_typed_hitbox.gd - Safe combat interaction system for players, enemies, and props.
- combat_log_connector.gd - Signal-binding logic for decoupled combat event logging.
- aoe_physics_query.gd - Performance-optimized AoE detection using direct PhysicsServer queries.
- hierarchical_state_base.gd - Robust base for managing complex ARPG character behavior.
- animation_condition_sync.gd - Safe synchronization logic for AnimationTree Advance Conditions.
- threaded_inventory_loader.gd - WorkerThreadPool-driven background parsing for large inventories.
- typed_inventory_storage.gd - High-performance strongly-typed dictionary for item storage.
- high_speed_aggro_broadcaster.gd - Group-based broadcasting pattern for instant localized AI alerts.
---
Core Loop
Combat → Loot → Level Up → Build Power → Challenge Harder Content → Repeat
Skill Chain
godot-project-foundations, godot-characterbody-2d, godot-combat-system, godot-rpg-stats, godot-inventory-system, godot-ability-system, godot-quest-system, godot-economy-system, godot-save-load-systems
---
Combat System
Real-Time Combat with Stats
class_name CombatController
extends Node
signal damage_dealt(target: Node, amount: int, type: String)
signal enemy_killed(enemy: Node, xp_reward: int)
func calculate_damage(attacker: RPGStats, defender: RPGStats, base_damage: int) -> Dictionary:
# Physical damage formula
var attack_power := attacker.get_stat("strength") * 2 + base_damage
var defense := defender.get_stat("armor")
# Damage reduction formula (diminishing returns)
var reduction := defense / (defense + 100.0)
var final_damage := int(attack_power * (1.0 - reduction))
# Critical hit check
var crit_chance := attacker.get_stat("crit_chance") / 100.0
var is_crit := randf() < crit_chance
if is_crit:
final_damage = int(final_damage * attacker.get_stat("crit_damage") / 100.0)
return {
"damage": max(1, final_damage),
"is_crit": is_crit,
"damage_type": "physical"
}
func apply_damage(target: Node, damage_result: Dictionary) -> void:
if target.has_method("take_damage"):
target.take_damage(damage_result["damage"], damage_result["is_crit"])
damage_dealt.emit(target, damage_result["damage"], damage_result["damage_type"])Hitbox/Hurtbox Combat
class_name Hitbox
extends Area2D
@export var damage: int = 10
@export var knockback_force: float = 200.0
@export var attack_owner: Node
var has_hit: Array[Node] = [] # Prevent multi-hit per swing
func _ready() -> void:
monitoring = false # Enable only during attack frames
func enable() -> void:
has_hit.clear()
monitoring = true
func disable() -> void:
monitoring = false
func _on_area_entered(area: Area2D) -> void:
if area is Hurtbox:
var target := area.owner_entity
if target != attack_owner and target not in has_hit:
has_hit.append(target)
var result := CombatController.calculate_damage(
attack_owner.stats, target.stats, damage
)
CombatController.apply_damage(target, result)
apply_knockback(target)
func apply_knockback(target: Node) -> void:
var direction := (target.global_position - attack_owner.global_position).normalized()
if target.has_method("apply_knockback"):
target.apply_knockback(direction * knockback_force)---
RPG Stats System
Attribute-Based Stats
class_name RPGStats
extends Resource
signal stat_changed(stat_name: String, new_value: float)
signal level_up(new_level: int)
# Base attributes (increased on level up)
@export var strength: int = 10
@export var dexterity: int = 10
@export var intelligence: int = 10
@export var vitality: int = 10
# Derived stats (calculated from attributes)
var derived_stats: Dictionary = {}
# Modifiers from equipment, buffs, etc.
var flat_modifiers: Dictionary = {} # +50 health
var percent_modifiers: Dictionary = {} # +10% damage
var level: int = 1
var experience: int = 0
var skill_points: int = 0
func _init() -> void:
recalculate_stats()
func recalculate_stats() -> void:
derived_stats = {
# Health: Vitality-based
"max_health": vitality * 10 + 100,
"health_regen": vitality * 0.5,
# Mana: Intelligence-based
"max_mana": intelligence * 8 + 50,
"mana_regen": intelligence * 0.3,
# Physical: Strength + Dexterity
"physical_damage": strength * 2,
"armor": strength + vitality,
# Critical: Dexterity-based
"crit_chance": 5.0 + dexterity * 0.2,
"crit_damage": 150.0 + dexterity * 0.5,
# Speed: Dexterity-based
"attack_speed": 1.0 + dexterity * 0.01,
"move_speed": 100.0 + dexterity * 2
}
# Apply modifiers
for stat_name in derived_stats:
var base := derived_stats[stat_name]
var flat := flat_modifiers.get(stat_name, 0.0)
var percent := percent_modifiers.get(stat_name, 0.0)
derived_stats[stat_name] = (base + flat) * (1.0 + percent / 100.0)
func get_stat(stat_name: String) -> float:
if stat_name in derived_stats:
return derived_stats[stat_name]
return get(stat_name)
func add_experience(amount: int) -> void:
experience += amount
while experience >= get_xp_for_next_level():
experience -= get_xp_for_next_level()
level += 1
skill_points += 5
level_up.emit(level)
func get_xp_for_next_level() -> int:
# Exponential scaling
return int(100 * pow(1.5, level - 1))---
Loot System
Item Generation
class_name LootGenerator
extends Node
enum Rarity { COMMON, UNCOMMON, RARE, EPIC, LEGENDARY }
const RARITY_WEIGHTS := {
Rarity.COMMON: 60,
Rarity.UNCOMMON: 25,
Rarity.RARE: 10,
Rarity.EPIC: 4,
Rarity.LEGENDARY: 1
}
const RARITY_AFFIX_COUNT := {
Rarity.COMMON: 0,
Rarity.UNCOMMON: 1,
Rarity.RARE: 2,
Rarity.EPIC: 3,
Rarity.LEGENDARY: 4
}
@export var affix_pool: Array[ItemAffix]
@export var base_items: Array[ItemBase]
func generate_item(item_level: int, magic_find: float = 0.0) -> Item:
var rarity := roll_rarity(magic_find)
var base := base_items.pick_random()
var item := Item.new()
item.base = base
item.rarity = rarity
item.item_level = item_level
# Roll affixes based on rarity
var affix_count := RARITY_AFFIX_COUNT[rarity]
var available_affixes := affix_pool.duplicate()
for i in affix_count:
if available_affixes.is_empty():
break
var affix := available_affixes.pick_random()
available_affixes.erase(affix)
item.affixes.append(generate_affix_roll(affix, item_level))
return item
func roll_rarity(magic_find: float) -> Rarity:
var weights := RARITY_WEIGHTS.duplicate()
# Magic find increases rare+ drops
weights[Rarity.RARE] *= (1.0 + magic_find / 100.0)
weights[Rarity.EPIC] *= (1.0 + magic_find / 100.0)
weights[Rarity.LEGENDARY] *= (1.0 + magic_find / 100.0)
var total := 0.0
for w in weights.values():
total += w
var roll := randf() * total
for rarity in weights:
roll -= weights[rarity]
if roll <= 0:
return rarity
return Rarity.COMMON
func generate_affix_roll(affix: ItemAffix, item_level: int) -> Dictionary:
# Scale affix values with item level
var min_roll := affix.min_value * (1.0 + item_level * 0.1)
var max_roll := affix.max_value * (1.0 + item_level * 0.1)
return {
"affix": affix,
"value": randf_range(min_roll, max_roll)
}Equipment System
class_name Equipment
extends Node
signal equipment_changed(slot: String, item: Item)
enum Slot { HEAD, CHEST, HANDS, LEGS, FEET, WEAPON, OFFHAND, RING1, RING2, AMULET }
var equipped: Dictionary = {} # Slot -> Item
func equip(item: Item) -> Item:
var slot: Slot = item.base.slot
var previous: Item = equipped.get(slot)
# Unequip old item
if previous:
remove_item_stats(previous)
# Equip new item
equipped[slot] = item
apply_item_stats(item)
equipment_changed.emit(Slot.keys()[slot], item)
return previous # Return to inventory
func apply_item_stats(item: Item) -> void:
var stats := owner.stats as RPGStats
# Base stats
for stat_name in item.base.base_stats:
stats.flat_modifiers[stat_name] = stats.flat_modifiers.get(stat_name, 0) + item.base.base_stats[stat_name]
# Affix stats
for affix_data in item.affixes:
var affix := affix_data["affix"] as ItemAffix
var value := affix_data["value"]
if affix.is_percent:
stats.percent_modifiers[affix.stat] = stats.percent_modifiers.get(affix.stat, 0) + value
else:
stats.flat_modifiers[affix.stat] = stats.flat_modifiers.get(affix.stat, 0) + value
stats.recalculate_stats()---
Ability System
Skill Trees and Unlocks
class_name SkillTree
extends Resource
@export var skills: Array[Skill]
@export var connections: Dictionary # skill_id -> Array[prerequisite_ids]
func can_unlock(skill_id: String, unlocked_skills: Array[String]) -> bool:
if skill_id in unlocked_skills:
return false # Already unlocked
var prereqs: Array = connections.get(skill_id, [])
for prereq in prereqs:
if prereq not in unlocked_skills:
return false
return true
func unlock_skill(skill_id: String, player: Node) -> bool:
var skill := get_skill(skill_id)
if not skill or player.stats.skill_points < skill.cost:
return false
player.stats.skill_points -= skill.cost
player.unlocked_skills.append(skill_id)
player.ability_manager.add_ability(skill.ability)
return trueActive Abilities
class_name ActiveAbility
extends Resource
@export var name: String
@export var cooldown: float = 5.0
@export var mana_cost: int = 20
@export var damage_multiplier: float = 2.0
@export var aoe_radius: float = 0.0
@export var effect_scene: PackedScene
var current_cooldown: float = 0.0
func can_use(caster: Node) -> bool:
return current_cooldown <= 0 and caster.stats.current_mana >= mana_cost
func use(caster: Node, target_position: Vector2) -> void:
if not can_use(caster):
return
caster.stats.current_mana -= mana_cost
current_cooldown = cooldown
var effect := effect_scene.instantiate()
effect.global_position = target_position
effect.damage = int(caster.stats.get_stat("physical_damage") * damage_multiplier)
effect.caster = caster
caster.get_tree().current_scene.add_child(effect)
func update_cooldown(delta: float) -> void:
current_cooldown = max(0, current_cooldown - delta)---
Enemy Design
Scaling Difficulty
class_name EnemySpawner
extends Node
@export var base_enemy_scene: PackedScene
@export var area_level: int = 1
func spawn_enemy(position: Vector2) -> Node:
var enemy := base_enemy_scene.instantiate()
enemy.global_position = position
# Scale stats with area level
var stats := enemy.stats as RPGStats
var level_mult := 1.0 + (area_level - 1) * 0.15
stats.vitality = int(stats.vitality * level_mult)
stats.strength = int(stats.strength * level_mult)
stats.recalculate_stats()
# Scale rewards
enemy.xp_reward = int(enemy.xp_reward * level_mult)
enemy.loot_table.item_level = area_level
add_child(enemy)
return enemy---
Common Pitfalls
| Pitfall | Solution |
|---|---|
| Stats feel meaningless | Ensure each point noticeably affects gameplay |
| Loot feels same | Dramatic visual and mechanical differences between rarities |
| Combat too simple | Combo systems, positioning matters, enemy variety |
| Progression walls | Multiple viable paths, catch-up mechanics |
| Inventory management tedium | Auto-sort, quick-sell, stash tabs |
---
Architecture Overview
AutoLoads:
├── PlayerStats (godot-rpg-stats)
├── InventoryManager (godot-inventory-system)
├── QuestManager (godot-quest-system)
├── LootGenerator (godot-economy-system)
└── GameManager (godot-scene-management)
Player:
├── CharacterBody2D/3D
├── RPGStats
├── Equipment
├── AbilityManager
├── Hitbox/Hurtbox
└── InputHandler
Enemies:
├── AI Controller (state machine)
├── RPGStats (scaled)
├── HealthComponent
├── LootTable
└── Hitbox/Hurtbox---
Godot-Specific Tips
1. Resources for items: Use Resource for items - easily serializable for save/load 2. Object pooling: Pool damage numbers, projectiles, item pickups 3. Animation callbacks: Use AnimationPlayer method tracks to enable/disable hitboxes 4. Stat recalculation: Only recalculate on equip/level, not every frame
---
Example Games for Reference
- Diablo / Path of Exile - Loot-focused ARPG
- Elden Ring / Dark Souls - Combat-focused action RPG
- Hades - Roguelike ARPG hybrid
- Grim Dawn - Deep character builds
Advanced ARPG Meta-Systems
Expert implementation of high-end ARPG systems for endgame progression and visual polish.
1. Paragon/Ascension System (Resource-Based Progression)
For post-level-cap progression, utilize a custom Resource. This allows for encapsulated methods, signals for UI updates, and efficient serialization.
class_name ParagonStats extends Resource
@export var paragon_level: int = 0
@export var bonus_strength: float = 0.0
@export var bonus_vitality: float = 0.0
const XP_REQUIREMENT_BASE: int = 10000
var current_xp: int = 0
func add_paragon_experience(amount: int) -> void:
current_xp += amount
var leveled_up: bool = false
while current_xp >= _get_xp_requirement():
current_xp -= _get_xp_requirement()
paragon_level += 1
bonus_strength += 2.5
bonus_vitality += 1.5
leveled_up = true
if leveled_up:
emit_changed() # Notify UI and combat systems
func _get_xp_requirement() -> int:
return XP_REQUIREMENT_BASE * (paragon_level + 1)Architectural Tip: When assigning a paragon template to a character, always use duplicate(true) to avoid modifying the globally cached resource instance.
2. Shader-Based Loot Beams (Visual Cues)
To handle massive loot drops without performance degradation, use the RenderingServer to pass rarity parameters to a shared shader without duplicating materials.
loot_beam.gdshader
shader_type spatial;
render_mode unshaded, cull_disabled;
uniform vec3 rarity_color = vec3(1.0, 1.0, 1.0);
void fragment() {
ALBEDO = rarity_color;
EMISSION = rarity_color * 3.0; # High emission for glowing effect
}loot_drop_manager.gd
class_name LootDropManager extends Node
# Update visual rarity without material duplication overhead
func apply_rarity_visuals(mesh_instance: MeshInstance3D, color: Color) -> void:
if mesh_instance:
# Use low-level RenderingServer for high-performance per-instance parameters
RenderingServer.instance_geometry_set_shader_parameter(
mesh_instance.get_instance_id(),
"rarity_color",
color
)3. Stat-Snapshot System (Combat Logging)
Combat logs should capture isolated state snapshots in a Dictionary and be flushed to disk periodically to avoid I/O bottlenecks.
class_name CombatLogger extends Node
const LOG_FILE_PATH: String = "user://combat_log.json"
var _session_buffer: Array[Dictionary] = []
func record_event(source: String, target: String, damage: int, is_crit: bool) -> void:
var snapshot: Dictionary = {
"timestamp": Time.get_unix_time_from_system(),
"source": source,
"target": target,
"damage": damage,
"is_crit": is_crit
}
_session_buffer.append(snapshot)
# Periodic flush (e.g., every 100 entries) to prevent memory bloat
if _session_buffer.size() >= 100:
flush_to_disk()
func flush_to_disk() -> void:
if _session_buffer.is_empty(): return
var file := FileAccess.open(LOG_FILE_PATH, FileAccess.WRITE)
if file:
file.store_string(JSON.stringify(_session_buffer, "\t"))
file.close()
# Note: In a real scenario, you'd append to existing logs or rotate filesAnti-Pattern: NEVER write to res:// at runtime. Always use user:// for persistent logs, as res:// is typically read-only in exported builds.
Reference
- Master Skill: godot-master
# animation_condition_sync.gd
extends Node
class_name AnimationConditionSync
# AnimationTree Advance Condition Sync
# Synchronizes internal logic state with AnimationTree parameters safely.
@export var anim_tree: AnimationTree
func update_parameters(moving: bool, attacking: bool) -> void:
if not anim_tree: return
# Pattern: AnimationTree Advance Conditions are Booleans.
# NEVER use negation (!) in the editor expression; pass explicit bools.
anim_tree.set("parameters/conditions/is_moving", moving)
anim_tree.set("parameters/conditions/is_attacking", attacking)
# Important: Ensure the conditions in the state machine match exactly.
# aoe_group_broadcaster.gd
# Rapidly applying damage to groups of entities
extends Node
# EXPERT NOTE: call_group is an optimized engine-level broadcast
# that avoids Python-style loop overhead for thousands of entities.
func cast_fireball(pos: Vector2, radius: float, damage: int):
# Assuming enemies are added to the "enemies" group
get_tree().call_group("enemies", "take_aoe_damage", pos, radius, damage)
# The individual enemy script would implement:
# func take_aoe_damage(origin: Vector2, r: float, dmg: int):
# if global_position.distance_to(origin) < r:
# _take_damage(dmg)
# aoe_physics_query.gd
extends Node3D
class_name AOEPhysicsQuery
# High-Performance AoE Server Query
# Directly queries the C++ physics server for massive AoE spells, bypassing Node overhead.
func execute_explosion(radius: float, damage: float) -> void:
var space_state := get_world_3d().direct_space_state
# Configure the spatial query parameters.
var query := PhysicsShapeQueryParameters3D.new()
var sphere := SphereShape3D.new()
sphere.radius = radius
query.shape = sphere
query.transform = global_transform
# Intersects all physics bodies in the radius instantly [28].
var results: Array[Dictionary] = space_state.intersect_shape(query)
for hit in results:
var target = hit["collider"]
if target.has_method(&"take_damage"):
target.take_damage(damage)
# base_stat_resource.gd
# Defining character stats as data-driven Resources
class_name BaseStats extends Resource
# EXPERT NOTE: Custom resources allow designers to edit stats
# directly in the Inspector and save them as .tres files.
@export var max_health: int = 100
@export var attack_power: int = 10
@export var defense: int = 5
@export var speed: float = 300.0
# Mandatory parameterless constructor for Editor support
func _init(p_max_health = 100, p_atk = 10):
max_health = p_max_health
attack_power = p_atk
# character_stats_resource.gd
extends Resource
class_name CharacterStatsResource
# Modular Resource-Based Character Stats
# Lightweight Inspector-friendly data container for RPG attributes.
@export var max_health: int = 100
@export var base_attack_damage: float = 15.0
@export var elemental_resistances: Dictionary[StringName, float] = {
&"fire": 0.0,
&"ice": 0.0,
&"lightning": 0.0
}
# Methods allow for internal scaling logic without exposing math variables.
func get_scaled_damage(multiplier: float) -> float:
return base_attack_damage * multiplier
func get_mitigated_damage(incoming_damage: float, element: StringName) -> float:
var res = elemental_resistances.get(element, 0.0)
return incoming_damage * (1.0 - res)
# combat_event_bus.gd
# Centralized bus for decoupled global combat events
extends Node
# EXPERT NOTE: A global bus (Autoload) is great for events
# like "AnyEnemyDied" that multiple diverse systems observe.
signal player_spawned(player: Node2D)
signal enemy_defeated(experience: int)
signal boss_phase_changed(phase_id: int)
func notify_enemy_death(xp: int):
enemy_defeated.emit(xp)
# combat_log_connector.gd
extends Node
class_name CombatLogConnector
# Signal-Driven Combat Log (Dependency Injection)
# Connects combat events to UI/Logging systems without hardcoding references.
func connect_entity(entity: Node) -> void:
# Pattern: Bind metadata like entity name to the signal callback.
if entity.has_signal(&"health_changed"):
entity.health_changed.connect(_on_health_update.bind(entity.name))
func _on_health_update(new_val: int, entity_name: String) -> void:
# Centralized event handling (e.g., adding to a UI RichTextLabel).
print("[Combat] ", entity_name, " health updated to: ", new_val)
# cooldown_coroutine.gd
# Lightweight skill cooldowns without Node overhead
extends Node
# EXPERT NOTE: Timers as Nodes are heavy. Coroutines (await)
# are lightweight and ephemeral.
var can_cast: bool = true
func fire_skill():
if not can_cast: return
_execute_logic()
_start_cooldown(2.5)
func _start_cooldown(duration: float):
can_cast = false
await get_tree().create_timer(duration).timeout
can_cast = true
func _execute_logic(): pass
# skills/genre-action-rpg/scripts/damage_label_manager.gd
extends Node2D
## Damage Label Manager
## High-performance pooling system for floating damage numbers.
class_name DamageLabelManager
@export var label_scene: PackedScene
@export var pool_size: int = 50
@export var float_speed: float = 50.0
@export var fade_duration: float = 0.5
@export var crit_color: Color = Color.ORANGE
@export var normal_color: Color = Color.WHITE
var _pool: Array[Node2D] = []
var _active_labels: Array[Node2D] = []
func _ready() -> void:
if not label_scene: return
for i in pool_size:
var lbl = label_scene.instantiate()
lbl.visible = false
lbl.process_mode = Node.PROCESS_MODE_DISABLED
add_child(lbl)
_pool.append(lbl)
func spawn_label(pos: Vector2, amount: int, is_crit: bool = false) -> void:
var lbl = _get_label()
if not lbl: return # Pool exhausted
lbl.global_position = pos
lbl.visible = true
lbl.process_mode = Node.PROCESS_MODE_INHERIT
lbl.modulate = Color(1, 1, 1, 1) # Reset alpha
lbl.scale = Vector2.ONE
# Assumes scene has a Label node named "Value"
var val_node = lbl.get_node_or_null("Value")
if val_node:
val_node.text = str(amount)
val_node.modulate = crit_color if is_crit else normal_color
if is_crit:
lbl.scale = Vector2(1.5, 1.5)
# Animate
var tween = create_tween()
tween.set_parallel(true)
tween.tween_property(lbl, "position:y", lbl.position.y - 50, fade_duration).set_trans(Tween.TRANS_OUT).set_ease(Tween.EASE_OUT)
tween.tween_property(lbl, "modulate:a", 0.0, fade_duration).set_delay(fade_duration * 0.5)
tween.chain().tween_callback(_return_to_pool.bind(lbl))
func _get_label() -> Node2D:
if _pool.is_empty():
# Optional: Expand pool dynamically or steal oldest active
return null
return _pool.pop_back()
func _return_to_pool(lbl: Node2D) -> void:
lbl.visible = false
lbl.process_mode = Node.PROCESS_MODE_DISABLED
_pool.append(lbl)
## EXPERT USAGE:
## Add to Main Scene (CanvasLayer).
## Call `DamageLabelManager.spawn_label(enemy.global_position, 100)`
# deep_stat_duplicator.gd
# Preventing resource leakage across shared templates
extends Node
# EXPERT NOTE: Resources are shared by reference.
# You MUST duplicate() them to avoid global state pollution.
@export var template_stats: BaseStats
var current_stats: BaseStats
func _ready():
# EXPERT: Deep duplicate ensures this instance has its own health/buffs
current_stats = template_stats.duplicate()
func apply_poison():
# Only affects this specific instance
current_stats.max_health -= 5
# duck_typed_hitbox.gd
extends Area3D
class_name DuckTypedHitbox
# Duck-Typing Hitboxes for Loose Coupling
# Processes combat hits without requiring specific target class references.
@export var base_damage: float = 10.0
func _on_body_entered(body: Node3D) -> void:
# Pattern: Check for method existence to support destructibles, actors, and props.
if body.has_method(&"take_damage"):
# We can pass dictionaries for complex RPG data (element, knockback).
body.take_damage({
"amount": base_damage,
"source": get_parent(),
"element": &"physical"
})
# entity_stat_duplicator.gd
extends Node
class_name EntityStatDuplicator
# Deep Duplication for Unique Entity State
# Ensures instanced enemies/allies have unique stat blocks, not globally shared ones.
@export var base_stats: CharacterStatsResource
var current_stats: CharacterStatsResource
func _ready() -> void:
# Pattern: ALWAYS duplicate shared resources to prevent cross-entity state leaks.
if base_stats:
# Use DEEP_DUPLICATE_ALL to ensure internal dictionaries/sub-resources are unique.
current_stats = base_stats.duplicate(true) as CharacterStatsResource
print("Stats localized for: ", get_parent().name)
# health_component.gd
# Entity Composition pattern for modular RPG units
extends Node
class_name HealthComponent
signal health_changed(current: int, max: int)
signal died
@export var stats: BaseStats
var current_health: int
func _ready():
current_health = stats.max_health
func damage(amount: int):
current_health -= amount
health_changed.emit(current_health, stats.max_health)
if current_health <= 0:
died.emit()
# hierarchical_state_base.gd
extends Node
class_name HierarchicalStateBase
# Hierarchical State Machine Base
# Modular base for complex RPG AI and Player state logic.
@export var initial_state: Node
@onready var current_state: Node = initial_state
func _physics_process(delta: float) -> void:
# Pattern: Delegate the tick to the active specialized state script.
if current_state and current_state.has_method(&"physics_tick"):
current_state.physics_tick(delta)
func transition(target_path: NodePath, params: Dictionary = {}) -> void:
var next_state = get_node_or_null(target_path)
if not next_state: return
if current_state.has_method(&"on_exit"):
current_state.on_exit()
current_state = next_state
if current_state.has_method(&"on_enter"):
current_state.on_enter(params)
# high_speed_aggro_broadcaster.gd
extends Node
class_name HighSpeedAggroBroadcaster
# Group Broadcasting for Faction/Aggro
# Alerts a localized faction instantly without nested node iterations.
func alert_faction(faction_id: StringName, threat_pos: Vector3) -> void:
# Pattern: Use call_group_flags with DEFERRED to avoid re-entering state logic.
get_tree().call_group_flags(
SceneTree.GROUP_CALL_DEFERRED,
faction_id, # Target Group (e.g. &"guards")
&"on_threat_detected", # Target Method
threat_pos # Arguments
)
# hitbox_component.gd
# Safe type-casting for combat detections
extends Area2D
class_name HitboxComponent
# EXPERT NOTE: Using 'as' keyword prevents runtime crashes
# if a non-combat node enters the area.
func _on_area_entered(area: Area2D):
var health = area as HealthComponent
if health:
health.damage(10)
# leveling_table.gd
# Data-driven XP required curves
extends Resource
class_name LevelingTable
# EXPERT NOTE: Using a curve or a formula in a Resource
# allows balancing progression without touching code.
@export var xp_curve: Curve
func get_required_xp(level: int) -> int:
# Sample from the designer-defined balance curve
return int(xp_curve.sample(float(level) / 100.0) * 1000)
# signal_combat_decoupler.gd
# "Signal Up, Call Down" architecture for combat
extends Node
# EXPERT NOTE: Avoid coupling UI to combat.
# Combat nodes emit signals; UI nodes observe and react.
signal combat_logged(msg: String, type: String)
func _on_damage_dealt(target: String, amount: int):
# UI/Log decoupled via signal
combat_logged.emit("Hit %s for %d damage!" % [target, amount], "damage")
func _on_death():
combat_logged.emit("Enemy defeated!", "death")
# stat_reduction_solver.gd
# Calculating complex modifier chains using C++ performance loops
extends Node
# EXPERT NOTE: Using reduce() is faster than manual GDScript loops
# for calculating total damage from a list of multipliers.
func calculate_total_damage(base_dmg: float, modifiers: Array[float]) -> float:
# Optimized reduction in the engine's internal VM
return modifiers.reduce(func(accum, val): return accum * val, base_dmg)
func apply_defense(dmg: float, def_res: BaseStats) -> float:
return max(0, dmg - def_res.defense)
# skills/genre-action-rpg/scripts/telegraphed_enemy.gd
extends CharacterBody2D
## Telegraphed Enemy Expert Pattern
## Manages attack states with clear visual telegraphs (wind-up) before striking.
class_name TelegraphedEnemy
enum State { IDLE, CHASE, CHARGE, ATTACK, RECOVERY }
@export var attack_range: float = 60.0
@export var windup_time: float = 0.5
@export var recovery_time: float = 1.0
@export var damage: int = 20
@onready var telegraph_visual: Node2D = $TelegraphVisual # Area indicator
@onready var hitbox: Area2D = $Hitbox
var _state: State = State.IDLE
var _timer: float = 0.0
var target: Node2D
func _physics_process(delta: float) -> void:
match _state:
State.IDLE:
_find_target()
State.CHASE:
_chase_target(delta)
State.CHARGE:
_process_charge(delta)
State.ATTACK:
_execute_attack()
State.RECOVERY:
_process_recovery(delta)
func _find_target() -> void:
# Simple logic: assume player is in group "player"
var tree = get_tree()
if tree.has_group("player"):
target = tree.get_nodes_in_group("player")[0]
_state = State.CHASE
func _chase_target(delta: float) -> void:
if not target:
_state = State.IDLE
return
var dist = global_position.distance_to(target.global_position)
if dist <= attack_range:
_start_attack()
else:
velocity = global_position.direction_to(target.global_position) * 100.0
move_and_slide()
func _start_attack() -> void:
_state = State.CHARGE
_timer = windup_time
velocity = Vector2.ZERO
# Show telegraph
if telegraph_visual:
telegraph_visual.visible = true
telegraph_visual.modulate.a = 0.5
# Tween transparency to imply buildup
var tween = create_tween()
tween.tween_property(telegraph_visual, "modulate:a", 1.0, windup_time)
func _process_charge(delta: float) -> void:
_timer -= delta
if _timer <= 0:
_state = State.ATTACK
func _execute_attack() -> void:
# Activate hitbox
if hitbox:
hitbox.monitoring = true
# Check overlapping bodies immediately or wait for next physics frame
# For simplicity, we assume Area2D monitors
if telegraph_visual:
telegraph_visual.visible = false
_state = State.RECOVERY
_timer = recovery_time
# Deactivate hitbox after 0.1s
await get_tree().create_timer(0.1).timeout
if hitbox: hitbox.monitoring = false
func _process_recovery(delta: float) -> void:
_timer -= delta
if _timer <= 0:
_state = State.CHASE
## EXPERT USAGE:
## Assign a Sprite or Polygon2D as 'TelegraphVisual'.
## Enemy freezes during Charge, giving player time to dodge.
# threaded_inventory_loader.gd
extends Node
class_name ThreadedInventoryLoader
# Threaded Inventory Parser
# Offloads parsing thousands of persistent items to worker threads to maintain 60FPS.
var _item_pool: Array[Dictionary] = []
func process_inventory(raw_json_items: Array[Dictionary]) -> void:
_item_pool = raw_json_items
# Pattern: Use WorkerThreadPool to distribute the dataset across all cores.
var task_id := WorkerThreadPool.add_group_task(_parse_single_item, _item_pool.size())
# Optional: Wait if completion is required before closing the loading screen.
WorkerThreadPool.wait_for_group_task_completion(task_id)
func _parse_single_item(index: int) -> void:
var item_data = _item_pool[index]
# Handle heavy logic: Resource loading, Icon generation, or dynamic stats.
pass
# typed_inventory_storage.gd
extends Node
class_name TypedInventoryStorage
# Strongly Typed Inventory Dictionaries
# Enforces data safety and performance for thousands of persistent RPG items.
# Pattern: Use [StringName, Resource] for high-speed lookup and type safety.
var equipment_slots: Dictionary[StringName, Resource] = {
&"head": null,
&"chest": null,
&"weapon": null
}
func equip_resource(slot: StringName, item: Resource) -> void:
if equipment_slots.has(slot):
equipment_slots[slot] = item
_on_item_equipped(slot, item)
func _on_item_equipped(slot: StringName, item: Resource) -> void:
# Trigger visual updates or stat recalculations.
pass