godot-rpg-stats

Expert blueprint for RPG stat systems (attributes, leveling, modifiers, damage formulas) using Resource-based stats, stackable modifiers, and derived stat calculations. Use when implementing character progression OR equipment/buff systems. Keywords stats, attributes, leveling, modifiers, CharacterStats, derived stats, damage calculation, XP.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "godot-rpg-stats" with this command: npx skills add thedivergentai/gd-agentic-skills/thedivergentai-gd-agentic-skills-godot-rpg-stats

RPG Stats

Resource-based stats, modifier stacks, and derived calculations define flexible character progression.

Available Scripts

stat_resource.gd

Robust Resource-based stat system with caching, dirty flags, and modifier stacks.

modifier_stack_stats.gd

Expert stat system with additive/multiplicative modifier stacks and priority ordering.

NEVER Do in RPG Stats

  • NEVER use int for percentagesvar critical_chance: int = 50 for 50%? Integer division = truncation errors. Use float (0.0-1.0 OR 0.0-100.0).
  • NEVER modify stats without signals — UI showing health bar but stats.current_health -= 10 doesn't update? MUST emit signals on stat changes.
  • NEVER use additive-only modifiers — Buff adds +10 strength on level 1 (10 base) = 100% increase. Same buff on level 50 (100 base) = 10% increase. Use multiplicative OR hybrid.
  • NEVER skip modifier IDsadd_modifier("strength", 5) without ID? Can't remove specific buffs later. MUST use unique IDs (e.g., "sword_buff", "potion_123").
  • NEVER use exponential XP formulas without capxp_to_next = level * 1000? Level 100 = 100k XP, level 1000 = 1M. Use sqrt/log OR flat scaling.
  • NEVER forget to clamp derived statsmax_health = vitality * 10? Negative vitality from debuff = negative health = crash. Use maxi(value, 1).

# stats.gd
class_name Stats
extends Resource

signal stat_changed(stat_name: String, old_value: float, new_value: float)
signal level_up(new_level: int)

@export var level: int = 1
@export var experience: int = 0
@export var experience_to_next_level: int = 100

# Base stats
@export var strength: int = 10
@export var dexterity: int = 10
@export var intelligence: int = 10
@export var vitality: int = 10

# Derived stats (calculated from base)
var max_health: int:
    get: return vitality * 10
var attack_power: int:
    get: return strength * 2
var defense: int:
    get: return strength + (vitality / 2)
var magic_power: int:
    get: return intelligence * 3
var critical_chance: float:
    get: return dexterity * 0.01

# Modifiers
var modifiers: Dictionary = {}

func add_experience(amount: int) -> void:
    experience += amount
    
    while experience >= experience_to_next_level:
        level_up_character()

func level_up_character() -> void:
    level += 1
    experience -= experience_to_next_level
    experience_to_next_level = int(experience_to_next_level * 1.5)
    
    # Increase base stats
    strength += 2
    dexterity += 2
    intelligence += 2
    vitality += 2
    
    level_up.emit(level)

func get_stat(stat_name: String) -> float:
    var base_value: float = get(stat_name)
    var modifier_bonus := get_modifier_total(stat_name)
    return base_value + modifier_bonus

func add_modifier(stat_name: String, modifier_id: String, value: float) -> void:
    if not modifiers.has(stat_name):
        modifiers[stat_name] = {}
    
    modifiers[stat_name][modifier_id] = value

func remove_modifier(stat_name: String, modifier_id: String) -> void:
    if modifiers.has(stat_name):
        modifiers[stat_name].erase(modifier_id)

func get_modifier_total(stat_name: String) -> float:
    if not modifiers.has(stat_name):
        return 0.0
    
    var total := 0.0
    for value in modifiers[stat_name].values():
        total += value
    return total

Equipment Stats

# equipment_item.gd
extends Item
class_name EquipmentItem

@export var stat_bonuses: Dictionary = {
    "strength": 5,
    "dexterity": 3
}

func on_equip(stats: Stats) -> void:
    for stat_name in stat_bonuses:
        stats.add_modifier(stat_name, "equipment_" + id, stat_bonuses[stat_name])

func on_unequip(stats: Stats) -> void:
    for stat_name in stat_bonuses:
        stats.remove_modifier(stat_name, "equipment_" + id)

Status Effects

# status_effect.gd
class_name StatusEffect
extends Resource

@export var effect_id: String
@export var duration: float
@export var stat_modifiers: Dictionary = {}

func apply(stats: Stats) -> void:
    for stat_name in stat_modifiers:
        stats.add_modifier(stat_name, "status_" + effect_id, stat_modifiers[stat_name])

func remove(stats: Stats) -> void:
    for stat_name in stat_modifiers:
        stats.remove_modifier(stat_name, "status_" + effect_id)

Damage Calculation

func calculate_damage(attacker_stats: Stats, defender_stats: Stats) -> float:
    var base_damage := float(attacker_stats.attack_power)
    var defense := float(defender_stats.defense)
    
    # Damage reduction formula
    var damage := base_damage * (100.0 / (100.0 + defense))
    
    # Critical hit
    if randf() < attacker_stats.critical_chance:
        damage *= 2.0
    
    return maxf(damage, 1.0)  # Minimum 1 damage

Skill Requirements

# skill.gd
class_name Skill
extends Resource

@export var required_level: int = 1
@export var required_stats: Dictionary = {
    "strength": 15,
    "intelligence": 10
}

func can_use(stats: Stats) -> bool:
    if stats.level < required_level:
        return false
    
    for stat_name in required_stats:
        if stats.get_stat(stat_name) < required_stats[stat_name]:
            return false
    
    return true

Best Practices

  1. Derived Stats - Calculate from base stats
  2. Modifiers - Temporary/permanent bonuses
  3. Formula Balance - Avoid exponential power creep

Reference

  • Related: godot-combat-system, godot-inventory-system

Related

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

godot-master

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-shaders-basics

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-ui-theming

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-particles

No summary provided by upstream source.

Repository SourceNeeds Review