Claude-skill-registry godot-modernize-gdscript

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/godot-modernize-gdscript" ~/.claude/skills/majiayu000-claude-skill-registry-godot-modernize-gdscript && rm -rf "$T"
manifest: skills/data/godot-modernize-gdscript/SKILL.md
source content

Godot GDScript 2.0 Modernizer

Converts GDScript 1.0 (Godot 3.x) patterns to GDScript 2.0 (Godot 4.x) syntax.

Core Conversions

This skill performs five key modernizations:

GDScript 1.0GDScript 2.0Pattern
yield(object, "signal")
await object.signal
Async operations
onready var
@onready var
Node references
export var
@export var
Inspector variables
var x setget set_x, get_x
Property syntaxGetters/setters
var health = 100
var health: int = 100
Static typing

UPON INVOCATION - START HERE

When this skill is invoked, IMMEDIATELY execute:

1. Verify Godot Project (5 seconds)

ls project.godot 2>/dev/null && echo "✓ Godot project detected" || echo "✗ Not a Godot project"

If NOT a Godot project:

  • Inform user this skill only works on Godot projects
  • STOP here

If IS a Godot project:

  • Proceed to step 2

2. Detect GDScript Version (10 seconds)

# Check for Godot 3.x patterns
echo "=== Detecting GDScript 1.0 Patterns ==="
echo "yield statements:"
grep -rn "yield(" --include="*.gd" . | wc -l

echo "onready declarations:"
grep -rn "^onready var" --include="*.gd" . | wc -l

echo "export declarations:"
grep -rn "^export var\|^export(int)\|^export(float)" --include="*.gd" . | wc -l

echo "setget properties:"
grep -rn "setget" --include="*.gd" . | wc -l

echo "untyped variables:"
grep -rn "^var [a-z]" --include="*.gd" . | grep -v ":" | wc -l

3. Present Findings

Show the user:

=== GDScript 2.0 Modernization Analysis ===

Project: [project name]
Current: GDScript 1.0 (Godot 3.x style)

Patterns to modernize:
- yield statements: X
- onready variables: X
- export variables: X
- setget properties: X
- untyped variables: X

Total: X modernizations needed

Modernization includes:
✓ yield → await conversion
✓ onready → @onready
✓ export → @export
✓ setget → property syntax
✓ Optional static typing
✓ Git commit per file
✓ Backup before changes

Would you like me to:
1. Modernize all files (recommended)
2. Show detailed breakdown first
3. Select specific conversions
4. Cancel

4. Wait for User Choice

  • If 1 (Proceed): Start Phase 2 immediately
  • If 2 (Details): Show file-by-file breakdown, then offer to proceed
  • If 3 (Selective): Ask which conversions to apply
  • If 4 (Cancel): Exit skill

Phase 1: Analysis & Inventory

1.1 Create File Inventory

# Find all .gd files
find . -name "*.gd" -type f | sort > /tmp/gdscript_files.txt
wc -l /tmp/gdscript_files.txt
echo "files found"

1.2 Analyze Each File

For each .gd file, detect patterns:

# Analyze patterns per file
for file in $(cat /tmp/gdscript_files.txt); do
    echo "=== $file ==="
    grep -c "yield(" "$file" 2>/dev/null || echo 0
    grep -c "^onready var" "$file" 2>/dev/null || echo 0
    grep -c "^export" "$file" 2>/dev/null || echo 0
    grep -c "setget" "$file" 2>/dev/null || echo 0
done

1.3 Create Modernization Plan

Modernization Plan:
===================

1. yield → await conversions (X files)
2. onready → @onready (X files)
3. export → @export (X files)
4. setget → properties (X files)
5. Static typing (X files)

Total files to modify: X
Estimated time: Auto (user doesn't wait)
Backup created: YES (git tag)
Rollback available: YES

Phase 2: Modernization Operations

Conversion A: yield → await

Detection:

grep -rn "yield(" --include="*.gd" .

Common Patterns:

Old PatternNew Pattern
yield(get_tree().create_timer(1.0), "timeout")
await get_tree().create_timer(1.0).timeout
yield(timer, "timeout")
await timer.timeout
yield(animation_player, "animation_finished")
await animation_player.animation_finished
yield(get_tree(), "idle_frame")
await get_tree().process_frame

Transformation Process:

  1. Extract yield statement

    # Before
    yield(get_tree().create_timer(2.0), "timeout")
    
  2. Convert to await

    # After
    await get_tree().create_timer(2.0).timeout
    
  3. Handle variable assignment

    # Before
    var result = yield(async_function(), "completed")
    
    # After
    var result = await async_function()
    

Implementation:

# Pseudo-code for replacement
def convert_yield_to_await(line):
    # Pattern: yield(object, "signal_name")
    # Convert to: await object.signal_name
    
    import re
    pattern = r'yield\(([^,]+),\s*"([^"]+)"\)'
    replacement = r'await \1.\2'
    return re.sub(pattern, replacement, line)

Conversion B: onready → @onready

Detection:

grep -rn "^onready var" --include="*.gd" .

Transformation:

# Before
onready var player = $Player
onready var health_bar = $UI/HealthBar

# After
@onready var player: Node = $Player
@onready var health_bar: ProgressBar = $UI/HealthBar

Process:

  1. Replace
    onready
    with
    @onready
  2. Add type hints where detectable
  3. Keep variable name and initialization

Type Inference (Optional):

# If $Player is CharacterBody2D in scene
@onready var player: CharacterBody2D = $Player

# If $Timer is Timer node
@onready var _timer: Timer = $Timer

Conversion C: export → @export

Detection:

grep -rn "^export" --include="*.gd" .

Transformation:

# Before
export var speed = 200
export(int) var max_health = 100
export(float) var jump_force = 500.0
export(String) var character_name = "Player"
export(NodePath) var target_path

# After
@export var speed: float = 200.0
@export var max_health: int = 100
@export var jump_force: float = 500.0
@export var character_name: String = "Player"
@export var target_path: NodePath

Export Types Mapping:

Old ExportNew ExportType
export(int)
@export var x: int
Integer
export(float)
@export var x: float
Float
export(String)
@export var x: String
String
export(bool)
@export var x: bool
Boolean
export(Color)
@export var x: Color
Color
export(Vector2)
@export var x: Vector2
Vector2
export(Vector3)
@export var x: Vector3
Vector3
export(NodePath)
@export var x: NodePath
NodePath
export var
@export var x: Type
Inferred

Conversion D: setget → Property Syntax

Detection:

grep -rn "setget" --include="*.gd" .

Transformation:

# Before (GDScript 1.0)
var health = 100 setget set_health, get_health

func set_health(value):
    health = clamp(value, 0, max_health)
    health_changed.emit(health)

func get_health():
    return health
# After (GDScript 2.0)
var health: int = 100:
    set(value):
        health = clamp(value, 0, max_health)
        health_changed.emit(health)
    get:
        return health

Process:

  1. Remove
    setget
    from variable declaration
  2. Add colon after type
  3. Inline setter and getter
  4. Use
    set(value):
    and
    get:
    syntax

Complex Example:

# Before
var score = 0 setget set_score

func set_score(value):
    score = max(0, value)
    update_ui()

# After
var score: int = 0:
    set(value):
        score = max(0, value)
        update_ui()

Conversion E: Static Typing (Optional)

Detection:

grep -rn "^var [a-z_]* = " --include="*.gd" . | grep -v ":"

Type Inference:

ValueInferred Type
= 100
int
= 3.14
float
= "text"
String
= true
bool
= Vector2(x, y)
Vector2
= $Node
Node type from scene

Transformation:

# Before
var health = 100
var speed = 200.5
var name = "Player"
var active = true
var position = Vector2.ZERO

# After
var health: int = 100
var speed: float = 200.5
var name: String = "Player"
var active: bool = true
var position: Vector2 = Vector2.ZERO

Phase 3: Execution & Safety

3.1 Create Git Baseline

# Create backup tag
git tag gdscript1-baseline-$(date +%Y%m%d-%H%M%S)

# Stage any current changes
git add .
git commit -m "Baseline: Pre-GDScript 2.0 modernization" || echo "No changes to commit"

3.2 Process Files Sequentially

For each .gd file:

# Process file
python3 << 'EOF'
import re

def modernize_gdscript(content):
    # Conversion 1: yield → await
    content = re.sub(
        r'yield\(([^,]+),\s*"([^"]+)"\)',
        r'await \1.\2',
        content
    )
    
    # Conversion 2: onready → @onready
    content = re.sub(
        r'^onready var',
        '@onready var',
        content,
        flags=re.MULTILINE
    )
    
    # Conversion 3: export → @export (simple)
    content = re.sub(
        r'^export var',
        '@export var',
        content,
        flags=re.MULTILINE
    )
    
    return content

# Read, process, write
with open("script.gd", "r") as f:
    content = f.read()

new_content = modernize_gdscript(content)

with open("script.gd", "w") as f:
    f.write(new_content)
EOF

3.3 Commit Per File

# After each file modification
git add "$file"
git commit -m "Modernize: $file to GDScript 2.0

- Converted yield to await
- Updated onready to @onready  
- Migrated export to @export
- Applied static typing"

3.4 Validation After Each File

# Validate syntax
godot --headless --quit-after 2 project.godot 2>&1 | grep -i "error" && {
    echo "Syntax error detected in $file"
    git reset --hard HEAD~1
    echo "Reverted changes to $file"
}

Phase 4: Verification & Testing

4.1 Syntax Validation

# Check all modified files for syntax errors
for file in $(git diff --name-only HEAD~10..HEAD | grep "\.gd$"); do
    echo "Checking $file..."
    # Basic syntax check
    gdscript_tool --check "$file" 2>/dev/null || echo "Manual review needed: $file"
done

4.2 Pattern Verification

# Verify no old patterns remain
echo "Checking for remaining GDScript 1.0 patterns..."

echo "yield statements remaining:"
grep -rn "yield(" --include="*.gd" . | wc -l

echo "onready remaining:"
grep -rn "^onready var" --include="*.gd" . | wc -l

echo "export remaining:"
grep -rn "^export var\|^export(" --include="*.gd" . | wc -l

4.3 Godot Project Test

# Open project in Godot to verify
godot --editor project.godot &
sleep 5

# Check for errors
# (User should visually verify no red errors in Output panel)

Examples

Example 1: Complete Script Modernization

Before (GDScript 1.0):

extends CharacterBody2D

export var speed = 200
export(int) var health = 100

onready var sprite = $Sprite
onready var animation = $AnimationPlayer

var damage = 10 setget set_damage

func set_damage(value):
    damage = clamp(value, 0, 100)

func _ready():
    yield(get_tree().create_timer(1.0), "timeout")
    start_game()

func take_damage(amount):
    health -= amount
    if health <= 0:
        yield(play_death_animation(), "completed")
        queue_free()

func play_death_animation():
    animation.play("death")
    yield(animation, "animation_finished")

After (GDScript 2.0):

extends CharacterBody2D

@export var speed: float = 200.0
@export var health: int = 100

@onready var sprite: Sprite2D = $Sprite
@onready var animation: AnimationPlayer = $AnimationPlayer

var damage: int = 10:
    set(value):
        damage = clamp(value, 0, 100)

func _ready():
    await get_tree().create_timer(1.0).timeout
    start_game()

func take_damage(amount: int) -> void:
    health -= amount
    if health <= 0:
        await play_death_animation()
        queue_free()

func play_death_animation() -> void:
    animation.play("death")
    await animation.animation_finished

Example 2: Signal Connection Modernization

Before:

func _ready():
    button.connect("pressed", self, "_on_button_pressed")
    timer.connect("timeout", self, "_on_timeout")

func _on_button_pressed():
    pass

func _on_timeout():
    pass

After:

func _ready():
    button.pressed.connect(_on_button_pressed)
    timer.timeout.connect(_on_timeout)

func _on_button_pressed() -> void:
    pass

func _on_timeout() -> void:
    pass

Success Criteria

Modernization complete when:

  • ✓ Zero
    yield(
    statements remain (converted to await)
  • ✓ Zero
    onready var
    remain (converted to @onready)
  • ✓ Zero
    export var
    remain (converted to @export)
  • ✓ Zero
    setget
    remain (converted to property syntax)
  • ✓ All scripts compile without errors
  • ✓ No functional changes (behavior identical)
  • ✓ Git history shows clear modernization commits

Common Issues & Solutions

Issue 1: yield with function calls

Problem:

var result = yield(load_data(), "completed")

Solution:

var result = await load_data()

Issue 2: Coroutines with state

Problem:

func process():
    yield(do_step_1(), "completed")
    yield(do_step_2(), "completed")

Solution:

func process() -> void:
    await do_step_1()
    await do_step_2()

Issue 3: setget with only getter or setter

Problem:

var score = 0 setget , get_score  # Only getter

Solution:

var score: int = 0:
    get:
        return score

Rollback Procedure

If modernization causes issues:

# Find baseline tag
git tag | grep "gdscript1-baseline"

# Reset to pre-modernization state
git reset --hard <baseline-tag>

# Or revert specific commits
git revert <modernization-commit-hash>

Integration with Other Skills

Use before:

  • godot-refactor
    - Modernize syntax first, then refactor architecture
  • godot-migrate-tilemap
    - Update code before TileMap migration

Use after:

  • Project conversion from Godot 3.x to 4.x
  • godot-organize-project
    - Organize files first

This skill automates the tedious parts of GDScript 1.0 → 2.0 migration while preserving exact functionality.