[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7BTYDY6HDWZPXJS949FNWRP":3,"skill-same:01M7BTYDY6HDWZPXJS949FNWRP":47},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":27,"repositoryHtmlUrl":28,"repositoryHomepageUrl":29,"ownerHtmlUrl":30,"ownerBlogUrl":6,"ownerDescription":31,"updatedAt":32,"install":33,"resources":46},"01M7BTYDY6HDWZPXJS949FNWRP","godot-gdscript-patterns",null,"Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.","YYC-Cube\u002FYYC3-Knowledge-Base","YYC-Cube","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F235006543?v=4","01M79J2PH4SWD2X0825X6PN748","yyc3","01M79J2PHVK3F6ZBYT8K2SCZCW",1,"MIT","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Plugin-插件协议\u002FYYC3-Plugin-插件协议-external_plugins\u002Fgame-development\u002Fskills\u002Fgodot-gdscript-patterns\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: godot-gdscript-patterns\ndescription: Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.\n---\n\n# Godot GDScript Patterns\n\nProduction patterns for Godot 4.x game development with GDScript, covering architecture, signals, scenes, and optimization.\n\n## When to Use This Skill\n\n- Building games with Godot 4\n- Implementing game systems in GDScript\n- Designing scene architecture\n- Managing game state\n- Optimizing GDScript performance\n- Learning Godot best practices\n\n## Core Concepts\n\n### 1. Godot Architecture\n\n```\nNode: Base building block\n├── Scene: Reusable node tree (saved as .tscn)\n├── Resource: Data container (saved as .tres)\n├── Signal: Event communication\n└── Group: Node categorization\n```\n\n### 2. GDScript Basics\n\n```gdscript\nclass_name Player\nextends CharacterBody2D\n\n# Signals\nsignal health_changed(new_health: int)\nsignal died\n\n# Exports (Inspector-editable)\n@export var speed: float = 200.0\n@export var max_health: int = 100\n@export_range(0, 1) var damage_reduction: float = 0.0\n@export_group(\"Combat\")\n@export var attack_damage: int = 10\n@export var attack_cooldown: float = 0.5\n\n# Onready (initialized when ready)\n@onready var sprite: Sprite2D = $Sprite2D\n@onready var animation: AnimationPlayer = $AnimationPlayer\n@onready var hitbox: Area2D = $Hitbox\n\n# Private variables (convention: underscore prefix)\nvar _health: int\nvar _can_attack: bool = true\n\nfunc _ready() -> void:\n    _health = max_health\n\nfunc _physics_process(delta: float) -> void:\n    var direction := Input.get_vector(\"left\", \"right\", \"up\", \"down\")\n    velocity = direction * speed\n    move_and_slide()\n\nfunc take_damage(amount: int) -> void:\n    var actual_damage := int(amount * (1.0 - damage_reduction))\n    _health = max(_health - actual_damage, 0)\n    health_changed.emit(_health)\n\n    if _health \u003C= 0:\n        died.emit()\n```\n\n## Patterns\n\n### Pattern 1: State Machine\n\n```gdscript\n# state_machine.gd\nclass_name StateMachine\nextends Node\n\nsignal state_changed(from_state: StringName, to_state: StringName)\n\n@export var initial_state: State\n\nvar current_state: State\nvar states: Dictionary = {}\n\nfunc _ready() -> void:\n    # Register all State children\n    for child in get_children():\n        if child is State:\n            states[child.name] = child\n            child.state_machine = self\n            child.process_mode = Node.PROCESS_MODE_DISABLED\n\n    # Start initial state\n    if initial_state:\n        current_state = initial_state\n        current_state.process_mode = Node.PROCESS_MODE_INHERIT\n        current_state.enter()\n\nfunc _process(delta: float) -> void:\n    if current_state:\n        current_state.update(delta)\n\nfunc _physics_process(delta: float) -> void:\n    if current_state:\n        current_state.physics_update(delta)\n\nfunc _unhandled_input(event: InputEvent) -> void:\n    if current_state:\n        current_state.handle_input(event)\n\nfunc transition_to(state_name: StringName, msg: Dictionary = {}) -> void:\n    if not states.has(state_name):\n        push_error(\"State '%s' not found\" % state_name)\n        return\n\n    var previous_state := current_state\n    previous_state.exit()\n    previous_state.process_mode = Node.PROCESS_MODE_DISABLED\n\n    current_state = states[state_name]\n    current_state.process_mode = Node.PROCESS_MODE_INHERIT\n    current_state.enter(msg)\n\n    state_changed.emit(previous_state.name, current_state.name)\n```\n\n```gdscript\n# state.gd\nclass_name State\nextends Node\n\nvar state_machine: StateMachine\n\nfunc enter(_msg: Dictionary = {}) -> void:\n    pass\n\nfunc exit() -> void:\n    pass\n\nfunc update(_delta: float) -> void:\n    pass\n\nfunc physics_update(_delta: float) -> void:\n    pass\n\nfunc handle_input(_event: InputEvent) -> void:\n    pass\n```\n\n```gdscript\n# player_idle.gd\nclass_name PlayerIdle\nextends State\n\n@export var player: Player\n\nfunc enter(_msg: Dictionary = {}) -> void:\n    player.animation.play(\"idle\")\n\nfunc physics_update(_delta: float) -> void:\n    var direction := Input.get_vector(\"left\", \"right\", \"up\", \"down\")\n\n    if direction != Vector2.ZERO:\n        state_machine.transition_to(\"Move\")\n\nfunc handle_input(event: InputEvent) -> void:\n    if event.is_action_pressed(\"attack\"):\n        state_machine.transition_to(\"Attack\")\n    elif event.is_action_pressed(\"jump\"):\n        state_machine.transition_to(\"Jump\")\n```\n\n### Pattern 2: Autoload Singletons\n\n```gdscript\n# game_manager.gd (Add to Project Settings > Autoload)\nextends Node\n\nsignal game_started\nsignal game_paused(is_paused: bool)\nsignal game_over(won: bool)\nsignal score_changed(new_score: int)\n\nenum GameState { MENU, PLAYING, PAUSED, GAME_OVER }\n\nvar state: GameState = GameState.MENU\nvar score: int = 0:\n    set(value):\n        score = value\n        score_changed.emit(score)\n\nvar high_score: int = 0\n\nfunc _ready() -> void:\n    process_mode = Node.PROCESS_MODE_ALWAYS\n    _load_high_score()\n\nfunc _input(event: InputEvent) -> void:\n    if event.is_action_pressed(\"pause\") and state == GameState.PLAYING:\n        toggle_pause()\n\nfunc start_game() -> void:\n    score = 0\n    state = GameState.PLAYING\n    game_started.emit()\n\nfunc toggle_pause() -> void:\n    var is_paused := state != GameState.PAUSED\n\n    if is_paused:\n        state = GameState.PAUSED\n        get_tree().paused = true\n    else:\n        state = GameState.PLAYING\n        get_tree().paused = false\n\n    game_paused.emit(is_paused)\n\nfunc end_game(won: bool) -> void:\n    state = GameState.GAME_OVER\n\n    if score > high_score:\n        high_score = score\n        _save_high_score()\n\n    game_over.emit(won)\n\nfunc add_score(points: int) -> void:\n    score += points\n\nfunc _load_high_score() -> void:\n    if FileAccess.file_exists(\"user:\u002F\u002Fhigh_score.save\"):\n        var file := FileAccess.open(\"user:\u002F\u002Fhigh_score.save\", FileAccess.READ)\n        high_score = file.get_32()\n\nfunc _save_high_score() -> void:\n    var file := FileAccess.open(\"user:\u002F\u002Fhigh_score.save\", FileAccess.WRITE)\n    file.store_32(high_score)\n```\n\n```gdscript\n# event_bus.gd (Global signal bus)\nextends Node\n\n# Player events\nsignal player_spawned(player: Node2D)\nsignal player_died(player: Node2D)\nsignal player_health_changed(health: int, max_health: int)\n\n# Enemy events\nsignal enemy_spawned(enemy: Node2D)\nsignal enemy_died(enemy: Node2D, position: Vector2)\n\n# Item events\nsignal item_collected(item_type: StringName, value: int)\nsignal powerup_activated(powerup_type: StringName)\n\n# Level events\nsignal level_started(level_number: int)\nsignal level_completed(level_number: int, time: float)\nsignal checkpoint_reached(checkpoint_id: int)\n```\n\n### Pattern 3: Resource-based Data\n\n```gdscript\n# weapon_data.gd\nclass_name WeaponData\nextends Resource\n\n@export var name: StringName\n@export var damage: int\n@export var attack_speed: float\n@export var range: float\n@export_multiline var description: String\n@export var icon: Texture2D\n@export var projectile_scene: PackedScene\n@export var sound_attack: AudioStream\n```\n\n```gdscript\n# character_stats.gd\nclass_name CharacterStats\nextends Resource\n\nsignal stat_changed(stat_name: StringName, new_value: float)\n\n@export var max_health: float = 100.0\n@export var attack: float = 10.0\n@export var defense: float = 5.0\n@export var speed: float = 200.0\n\n# Runtime values (not saved)\nvar _current_health: float\n\nfunc _init() -> void:\n    _current_health = max_health\n\nfunc get_current_health() -> float:\n    return _current_health\n\nfunc take_damage(amount: float) -> float:\n    var actual_damage := maxf(amount - defense, 1.0)\n    _current_health = maxf(_current_health - actual_damage, 0.0)\n    stat_changed.emit(\"health\", _current_health)\n    return actual_damage\n\nfunc heal(amount: float) -> void:\n    _current_health = minf(_current_health + amount, max_health)\n    stat_changed.emit(\"health\", _current_health)\n\nfunc duplicate_for_runtime() -> CharacterStats:\n    var copy := duplicate() as CharacterStats\n    copy._current_health = copy.max_health\n    return copy\n```\n\n```gdscript\n# Using resources\nclass_name Character\nextends CharacterBody2D\n\n@export var base_stats: CharacterStats\n@export var weapon: WeaponData\n\nvar stats: CharacterStats\n\nfunc _ready() -> void:\n    # Create runtime copy to avoid modifying the resource\n    stats = base_stats.duplicate_for_runtime()\n    stats.stat_changed.connect(_on_stat_changed)\n\nfunc attack() -> void:\n    if weapon:\n        print(\"Attacking with %s for %d damage\" % [weapon.name, weapon.damage])\n\nfunc _on_stat_changed(stat_name: StringName, value: float) -> void:\n    if stat_name == \"health\" and value \u003C= 0:\n        die()\n```\n\n### Pattern 4: Object Pooling\n\n```gdscript\n# object_pool.gd\nclass_name ObjectPool\nextends Node\n\n@export var pooled_scene: PackedScene\n@export var initial_size: int = 10\n@export var can_grow: bool = true\n\nvar _available: Array[Node] = []\nvar _in_use: Array[Node] = []\n\nfunc _ready() -> void:\n    _initialize_pool()\n\nfunc _initialize_pool() -> void:\n    for i in initial_size:\n        _create_instance()\n\nfunc _create_instance() -> Node:\n    var instance := pooled_scene.instantiate()\n    instance.process_mode = Node.PROCESS_MODE_DISABLED\n    instance.visible = false\n    add_child(instance)\n    _available.append(instance)\n\n    # Connect return signal if exists\n    if instance.has_signal(\"returned_to_pool\"):\n        instance.returned_to_pool.connect(_return_to_pool.bind(instance))\n\n    return instance\n\nfunc get_instance() -> Node:\n    var instance: Node\n\n    if _available.is_empty():\n        if can_grow:\n            instance = _create_instance()\n            _available.erase(instance)\n        else:\n            push_warning(\"Pool exhausted and cannot grow\")\n            return null\n    else:\n        instance = _available.pop_back()\n\n    instance.process_mode = Node.PROCESS_MODE_INHERIT\n    instance.visible = true\n    _in_use.append(instance)\n\n    if instance.has_method(\"on_spawn\"):\n        instance.on_spawn()\n\n    return instance\n\nfunc _return_to_pool(instance: Node) -> void:\n    if not instance in _in_use:\n        return\n\n    _in_use.erase(instance)\n\n    if instance.has_method(\"on_despawn\"):\n        instance.on_despawn()\n\n    instance.process_mode = Node.PROCESS_MODE_DISABLED\n    instance.visible = false\n    _available.append(instance)\n\nfunc return_all() -> void:\n    for instance in _in_use.duplicate():\n        _return_to_pool(instance)\n```\n\n```gdscript\n# pooled_bullet.gd\nclass_name PooledBullet\nextends Area2D\n\nsignal returned_to_pool\n\n@export var speed: float = 500.0\n@export var lifetime: float = 5.0\n\nvar direction: Vector2\nvar _timer: float\n\nfunc on_spawn() -> void:\n    _timer = lifetime\n\nfunc on_despawn() -> void:\n    direction = Vector2.ZERO\n\nfunc initialize(pos: Vector2, dir: Vector2) -> void:\n    global_position = pos\n    direction = dir.normalized()\n    rotation = direction.angle()\n\nfunc _physics_process(delta: float) -> void:\n    position += direction * speed * delta\n\n    _timer -= delta\n    if _timer \u003C= 0:\n        returned_to_pool.emit()\n\nfunc _on_body_entered(body: Node2D) -> void:\n    if body.has_method(\"take_damage\"):\n        body.take_damage(10)\n    returned_to_pool.emit()\n```\n\n### Pattern 5: Component System\n\n```gdscript\n# health_component.gd\nclass_name HealthComponent\nextends Node\n\nsignal health_changed(current: int, maximum: int)\nsignal damaged(amount: int, source: Node)\nsignal healed(amount: int)\nsignal died\n\n@export var max_health: int = 100\n@export var invincibility_time: float = 0.0\n\nvar current_health: int:\n    set(value):\n        var old := current_health\n        current_health = clampi(value, 0, max_health)\n        if current_health != old:\n            health_changed.emit(current_health, max_health)\n\nvar _invincible: bool = false\n\nfunc _ready() -> void:\n    current_health = max_health\n\nfunc take_damage(amount: int, source: Node = null) -> int:\n    if _invincible or current_health \u003C= 0:\n        return 0\n\n    var actual := mini(amount, current_health)\n    current_health -= actual\n    damaged.emit(actual, source)\n\n    if current_health \u003C= 0:\n        died.emit()\n    elif invincibility_time > 0:\n        _start_invincibility()\n\n    return actual\n\nfunc heal(amount: int) -> int:\n    var actual := mini(amount, max_health - current_health)\n    current_health += actual\n    if actual > 0:\n        healed.emit(actual)\n    return actual\n\nfunc _start_invincibility() -> void:\n    _invincible = true\n    await get_tree().create_timer(invincibility_time).timeout\n    _invincible = false\n```\n\n```gdscript\n# hitbox_component.gd\nclass_name HitboxComponent\nextends Area2D\n\nsignal hit(hurtbox: HurtboxComponent)\n\n@export var damage: int = 10\n@export var knockback_force: float = 200.0\n\nvar owner_node: Node\n\nfunc _ready() -> void:\n    owner_node = get_parent()\n    area_entered.connect(_on_area_entered)\n\nfunc _on_area_entered(area: Area2D) -> void:\n    if area is HurtboxComponent:\n        var hurtbox := area as HurtboxComponent\n        if hurtbox.owner_node != owner_node:\n            hit.emit(hurtbox)\n            hurtbox.receive_hit(self)\n```\n\n```gdscript\n# hurtbox_component.gd\nclass_name HurtboxComponent\nextends Area2D\n\nsignal hurt(hitbox: HitboxComponent)\n\n@export var health_component: HealthComponent\n\nvar owner_node: Node\n\nfunc _ready() -> void:\n    owner_node = get_parent()\n\nfunc receive_hit(hitbox: HitboxComponent) -> void:\n    hurt.emit(hitbox)\n\n    if health_component:\n        health_component.take_damage(hitbox.damage, hitbox.owner_node)\n```\n\n### Pattern 6: Scene Management\n\n```gdscript\n# scene_manager.gd (Autoload)\nextends Node\n\nsignal scene_loading_started(scene_path: String)\nsignal scene_loading_progress(progress: float)\nsignal scene_loaded(scene: Node)\nsignal transition_started\nsignal transition_finished\n\n@export var transition_scene: PackedScene\n@export var loading_scene: PackedScene\n\nvar _current_scene: Node\nvar _transition: CanvasLayer\nvar _loader: ResourceLoader\n\nfunc _ready() -> void:\n    _current_scene = get_tree().current_scene\n\n    if transition_scene:\n        _transition = transition_scene.instantiate()\n        add_child(_transition)\n        _transition.visible = false\n\nfunc change_scene(scene_path: String, with_transition: bool = true) -> void:\n    if with_transition:\n        await _play_transition_out()\n\n    _load_scene(scene_path)\n\nfunc change_scene_packed(scene: PackedScene, with_transition: bool = true) -> void:\n    if with_transition:\n        await _play_transition_out()\n\n    _swap_scene(scene.instantiate())\n\nfunc _load_scene(path: String) -> void:\n    scene_loading_started.emit(path)\n\n    # Check if already loaded\n    if ResourceLoader.has_cached(path):\n        var scene := load(path) as PackedScene\n        _swap_scene(scene.instantiate())\n        return\n\n    # Async loading\n    ResourceLoader.load_threaded_request(path)\n\n    while true:\n        var progress := []\n        var status := ResourceLoader.load_threaded_get_status(path, progress)\n\n        match status:\n            ResourceLoader.THREAD_LOAD_IN_PROGRESS:\n                scene_loading_progress.emit(progress[0])\n                await get_tree().process_frame\n            ResourceLoader.THREAD_LOAD_LOADED:\n                var scene := ResourceLoader.load_threaded_get(path) as PackedScene\n                _swap_scene(scene.instantiate())\n                return\n            _:\n                push_error(\"Failed to load scene: %s\" % path)\n                return\n\nfunc _swap_scene(new_scene: Node) -> void:\n    if _current_scene:\n        _current_scene.queue_free()\n\n    _current_scene = new_scene\n    get_tree().root.add_child(_current_scene)\n    get_tree().current_scene = _current_scene\n\n    scene_loaded.emit(_current_scene)\n    await _play_transition_in()\n\nfunc _play_transition_out() -> void:\n    if not _transition:\n        return\n\n    transition_started.emit()\n    _transition.visible = true\n\n    if _transition.has_method(\"transition_out\"):\n        await _transition.transition_out()\n    else:\n        await get_tree().create_timer(0.3).timeout\n\nfunc _play_transition_in() -> void:\n    if not _transition:\n        transition_finished.emit()\n        return\n\n    if _transition.has_method(\"transition_in\"):\n        await _transition.transition_in()\n    else:\n        await get_tree().create_timer(0.3).timeout\n\n    _transition.visible = false\n    transition_finished.emit()\n```\n\n### Pattern 7: Save System\n\n```gdscript\n# save_manager.gd (Autoload)\nextends Node\n\nconst SAVE_PATH := \"user:\u002F\u002Fsavegame.save\"\nconst ENCRYPTION_KEY := \"your_secret_key_here\"\n\nsignal save_completed\nsignal load_completed\nsignal save_error(message: String)\n\nfunc save_game(data: Dictionary) -> void:\n    var file := FileAccess.open_encrypted_with_pass(\n        SAVE_PATH,\n        FileAccess.WRITE,\n        ENCRYPTION_KEY\n    )\n\n    if file == null:\n        save_error.emit(\"Could not open save file\")\n        return\n\n    var json := JSON.stringify(data)\n    file.store_string(json)\n    file.close()\n\n    save_completed.emit()\n\nfunc load_game() -> Dictionary:\n    if not FileAccess.file_exists(SAVE_PATH):\n        return {}\n\n    var file := FileAccess.open_encrypted_with_pass(\n        SAVE_PATH,\n        FileAccess.READ,\n        ENCRYPTION_KEY\n    )\n\n    if file == null:\n        save_error.emit(\"Could not open save file\")\n        return {}\n\n    var json := file.get_as_text()\n    file.close()\n\n    var parsed := JSON.parse_string(json)\n    if parsed == null:\n        save_error.emit(\"Could not parse save data\")\n        return {}\n\n    load_completed.emit()\n    return parsed\n\nfunc delete_save() -> void:\n    if FileAccess.file_exists(SAVE_PATH):\n        DirAccess.remove_absolute(SAVE_PATH)\n\nfunc has_save() -> bool:\n    return FileAccess.file_exists(SAVE_PATH)\n```\n\n```gdscript\n# saveable.gd (Attach to saveable nodes)\nclass_name Saveable\nextends Node\n\n@export var save_id: String\n\nfunc _ready() -> void:\n    if save_id.is_empty():\n        save_id = str(get_path())\n\nfunc get_save_data() -> Dictionary:\n    var parent := get_parent()\n    var data := {\"id\": save_id}\n\n    if parent is Node2D:\n        data[\"position\"] = {\"x\": parent.position.x, \"y\": parent.position.y}\n\n    if parent.has_method(\"get_custom_save_data\"):\n        data.merge(parent.get_custom_save_data())\n\n    return data\n\nfunc load_save_data(data: Dictionary) -> void:\n    var parent := get_parent()\n\n    if data.has(\"position\") and parent is Node2D:\n        parent.position = Vector2(data.position.x, data.position.y)\n\n    if parent.has_method(\"load_custom_save_data\"):\n        parent.load_custom_save_data(data)\n```\n\n## Performance Tips\n\n```gdscript\n# 1. Cache node references\n@onready var sprite := $Sprite2D  # Good\n# $Sprite2D in _process()  # Bad - repeated lookup\n\n# 2. Use object pooling for frequent spawning\n# See Pattern 4\n\n# 3. Avoid allocations in hot paths\nvar _reusable_array: Array = []\n\nfunc _process(_delta: float) -> void:\n    _reusable_array.clear()  # Reuse instead of creating new\n\n# 4. Use static typing\nfunc calculate(value: float) -> float:  # Good\n    return value * 2.0\n\n# 5. Disable processing when not needed\nfunc _on_off_screen() -> void:\n    set_process(false)\n    set_physics_process(false)\n```\n\n## Best Practices\n\n### Do's\n- **Use signals for decoupling** - Avoid direct references\n- **Type everything** - Static typing catches errors\n- **Use resources for data** - Separate data from logic\n- **Pool frequently spawned objects** - Avoid GC hitches\n- **Use Autoloads sparingly** - Only for truly global systems\n\n### Don'ts\n- **Don't use `get_node()` in loops** - Cache references\n- **Don't couple scenes tightly** - Use signals\n- **Don't put logic in resources** - Keep them data-only\n- **Don't ignore the Profiler** - Monitor performance\n- **Don't fight the scene tree** - Work with Godot's design\n\n## Resources\n\n- [Godot Documentation](https:\u002F\u002Fdocs.godotengine.org\u002Fen\u002Fstable\u002F)\n- [GDQuest Tutorials](https:\u002F\u002Fwww.gdquest.com\u002F)\n- [Godot Recipes](https:\u002F\u002Fkidscancode.org\u002Fgodot_recipes\u002F)\n","# Godot GDScript Patterns\n\nProduction patterns for Godot 4.x game development with GDScript, covering architecture, signals, scenes, and optimization.\n\n## When to Use This Skill\n\n- Building games with Godot 4\n- Implementing game systems in GDScript\n- Designing scene architecture\n- Managing game state\n- Optimizing GDScript performance\n- Learning Godot best practices\n\n## Core Concepts\n\n### 1. Godot Architecture\n\n```\nNode: Base building block\n├── Scene: Reusable node tree (saved as .tscn)\n├── Resource: Data container (saved as .tres)\n├── Signal: Event communication\n└── Group: Node categorization\n```\n\n### 2. GDScript Basics\n\n```gdscript\nclass_name Player\nextends CharacterBody2D\n\n# Signals\nsignal health_changed(new_health: int)\nsignal died\n\n# Exports (Inspector-editable)\n@export var speed: float = 200.0\n@export var max_health: int = 100\n@export_range(0, 1) var damage_reduction: float = 0.0\n@export_group(\"Combat\")\n@export var attack_damage: int = 10\n@export var attack_cooldown: float = 0.5\n\n# Onready (initialized when ready)\n@onready var sprite: Sprite2D = $Sprite2D\n@onready var animation: AnimationPlayer = $AnimationPlayer\n@onready var hitbox: Area2D = $Hitbox\n\n# Private variables (convention: underscore prefix)\nvar _health: int\nvar _can_attack: bool = true\n\nfunc _ready() -> void:\n    _health = max_health\n\nfunc _physics_process(delta: float) -> void:\n    var direction := Input.get_vector(\"left\", \"right\", \"up\", \"down\")\n    velocity = direction * speed\n    move_and_slide()\n\nfunc take_damage(amount: int) -> void:\n    var actual_damage := int(amount * (1.0 - damage_reduction))\n    _health = max(_health - actual_damage, 0)\n    health_changed.emit(_health)\n\n    if _health \u003C= 0:\n        died.emit()\n```\n\n## Patterns\n\n### Pattern 1: State Machine\n\n```gdscript\n# state_machine.gd\nclass_name StateMachine\nextends Node\n\nsignal state_changed(from_state: StringName, to_state: StringName)\n\n@export var initial_state: State\n\nvar current_state: State\nvar states: Dictionary = {}\n\nfunc _ready() -> void:\n    # Register all State children\n    for child in get_children():\n        if child is State:\n            states[child.name] = child\n            child.state_machine = self\n            child.process_mode = Node.PROCESS_MODE_DISABLED\n\n    # Start initial state\n    if initial_state:\n        current_state = initial_state\n        current_state.process_mode = Node.PROCESS_MODE_INHERIT\n        current_state.enter()\n\nfunc _process(delta: float) -> void:\n    if current_state:\n        current_state.update(delta)\n\nfunc _physics_process(delta: float) -> void:\n    if current_state:\n        current_state.physics_update(delta)\n\nfunc _unhandled_input(event: InputEvent) -> void:\n    if current_state:\n        current_state.handle_input(event)\n\nfunc transition_to(state_name: StringName, msg: Dictionary = {}) -> void:\n    if not states.has(state_name):\n        push_error(\"State '%s' not found\" % state_name)\n        return\n\n    var previous_state := current_state\n    previous_state.exit()\n    previous_state.process_mode = Node.PROCESS_MODE_DISABLED\n\n    current_state = states[state_name]\n    current_state.process_mode = Node.PROCESS_MODE_INHERIT\n    current_state.enter(msg)\n\n    state_changed.emit(previous_state.name, current_state.name)\n```\n\n```gdscript\n# state.gd\nclass_name State\nextends Node\n\nvar state_machine: StateMachine\n\nfunc enter(_msg: Dictionary = {}) -> void:\n    pass\n\nfunc exit() -> void:\n    pass\n\nfunc update(_delta: float) -> void:\n    pass\n\nfunc physics_update(_delta: float) -> void:\n    pass\n\nfunc handle_input(_event: InputEvent) -> void:\n    pass\n```\n\n```gdscript\n# player_idle.gd\nclass_name PlayerIdle\nextends State\n\n@export var player: Player\n\nfunc enter(_msg: Dictionary = {}) -> void:\n    player.animation.play(\"idle\")\n\nfunc physics_update(_delta: float) -> void:\n    var direction := Input.get_vector(\"left\", \"right\", \"up\", \"down\")\n\n    if direction != Vector2.ZERO:\n        state_machine.transition_to(\"Move\")\n\nfunc handle_input(event: InputEvent) -> void:\n    if event.is_action_pressed(\"attack\"):\n        state_machine.transition_to(\"Attack\")\n    elif event.is_action_pressed(\"jump\"):\n        state_machine.transition_to(\"Jump\")\n```\n\n### Pattern 2: Autoload Singletons\n\n```gdscript\n# game_manager.gd (Add to Project Settings > Autoload)\nextends Node\n\nsignal game_started\nsignal game_paused(is_paused: bool)\nsignal game_over(won: bool)\nsignal score_changed(new_score: int)\n\nenum GameState { MENU, PLAYING, PAUSED, GAME_OVER }\n\nvar state: GameState = GameState.MENU\nvar score: int = 0:\n    set(value):\n        score = value\n        score_changed.emit(score)\n\nvar high_score: int = 0\n\nfunc _ready() -> void:\n    process_mode = Node.PROCESS_MODE_ALWAYS\n    _load_high_score()\n\nfunc _input(event: InputEvent) -> void:\n    if event.is_action_pressed(\"pause\") and state == GameState.PLAYING:\n        toggle_pause()\n\nfunc start_game() -> void:\n    score = 0\n    state = GameState.PLAYING\n    game_started.emit()\n\nfunc toggle_pause() -> void:\n    var is_paused := state != GameState.PAUSED\n\n    if is_paused:\n        state = GameState.PAUSED\n        get_tree().paused = true\n    else:\n        state = GameState.PLAYING\n        get_tree().paused = false\n\n    game_paused.emit(is_paused)\n\nfunc end_game(won: bool) -> void:\n    state = GameState.GAME_OVER\n\n    if score > high_score:\n        high_score = score\n        _save_high_score()\n\n    game_over.emit(won)\n\nfunc add_score(points: int) -> void:\n    score += points\n\nfunc _load_high_score() -> void:\n    if FileAccess.file_exists(\"user:\u002F\u002Fhigh_score.save\"):\n        var file := FileAccess.open(\"user:\u002F\u002Fhigh_score.save\", FileAccess.READ)\n        high_score = file.get_32()\n\nfunc _save_high_score() -> void:\n    var file := FileAccess.open(\"user:\u002F\u002Fhigh_score.save\", FileAccess.WRITE)\n    file.store_32(high_score)\n```\n\n```gdscript\n# event_bus.gd (Global signal bus)\nextends Node\n\n# Player events\nsignal player_spawned(player: Node2D)\nsignal player_died(player: Node2D)\nsignal player_health_changed(health: int, max_health: int)\n\n# Enemy events\nsignal enemy_spawned(enemy: Node2D)\nsignal enemy_died(enemy: Node2D, position: Vector2)\n\n# Item events\nsignal item_collected(item_type: StringName, value: int)\nsignal powerup_activated(powerup_type: StringName)\n\n# Level events\nsignal level_started(level_number: int)\nsignal level_completed(level_number: int, time: float)\nsignal checkpoint_reached(checkpoint_id: int)\n```\n\n### Pattern 3: Resource-based Data\n\n```gdscript\n# weapon_data.gd\nclass_name WeaponData\nextends Resource\n\n@export var name: StringName\n@export var damage: int\n@export var attack_speed: float\n@export var range: float\n@export_multiline var description: String\n@export var icon: Texture2D\n@export var projectile_scene: PackedScene\n@export var sound_attack: AudioStream\n```\n\n```gdscript\n# character_stats.gd\nclass_name CharacterStats\nextends Resource\n\nsignal stat_changed(stat_name: StringName, new_value: float)\n\n@export var max_health: float = 100.0\n@export var attack: float = 10.0\n@export var defense: float = 5.0\n@export var speed: float = 200.0\n\n# Runtime values (not saved)\nvar _current_health: float\n\nfunc _init() -> void:\n    _current_health = max_health\n\nfunc get_current_health() -> float:\n    return _current_health\n\nfunc take_damage(amount: float) -> float:\n    var actual_damage := maxf(amount - defense, 1.0)\n    _current_health = maxf(_current_health - actual_damage, 0.0)\n    stat_changed.emit(\"health\", _current_health)\n    return actual_damage\n\nfunc heal(amount: float) -> void:\n    _current_health = minf(_current_health + amount, max_health)\n    stat_changed.emit(\"health\", _current_health)\n\nfunc duplicate_for_runtime() -> CharacterStats:\n    var copy := duplicate() as CharacterStats\n    copy._current_health = copy.max_health\n    return copy\n```\n\n```gdscript\n# Using resources\nclass_name Character\nextends CharacterBody2D\n\n@export var base_stats: CharacterStats\n@export var weapon: WeaponData\n\nvar stats: CharacterStats\n\nfunc _ready() -> void:\n    # Create runtime copy to avoid modifying the resource\n    stats = base_stats.duplicate_for_runtime()\n    stats.stat_changed.connect(_on_stat_changed)\n\nfunc attack() -> void:\n    if weapon:\n        print(\"Attacking with %s for %d damage\" % [weapon.name, weapon.damage])\n\nfunc _on_stat_changed(stat_name: StringName, value: float) -> void:\n    if stat_name == \"health\" and value \u003C= 0:\n        die()\n```\n\n### Pattern 4: Object Pooling\n\n```gdscript\n# object_pool.gd\nclass_name ObjectPool\nextends Node\n\n@export var pooled_scene: PackedScene\n@export var initial_size: int = 10\n@export var can_grow: bool = true\n\nvar _available: Array[Node] = []\nvar _in_use: Array[Node] = []\n\nfunc _ready() -> void:\n    _initialize_pool()\n\nfunc _initialize_pool() -> void:\n    for i in initial_size:\n        _create_instance()\n\nfunc _create_instance() -> Node:\n    var instance := pooled_scene.instantiate()\n    instance.process_mode = Node.PROCESS_MODE_DISABLED\n    instance.visible = false\n    add_child(instance)\n    _available.append(instance)\n\n    # Connect return signal if exists\n    if instance.has_signal(\"returned_to_pool\"):\n        instance.returned_to_pool.connect(_return_to_pool.bind(instance))\n\n    return instance\n\nfunc get_instance() -> Node:\n    var instance: Node\n\n    if _available.is_empty():\n        if can_grow:\n            instance = _create_instance()\n            _available.erase(instance)\n        else:\n            push_warning(\"Pool exhausted and cannot grow\")\n            return null\n    else:\n        instance = _available.pop_back()\n\n    instance.process_mode = Node.PROCESS_MODE_INHERIT\n    instance.visible = true\n    _in_use.append(instance)\n\n    if instance.has_method(\"on_spawn\"):\n        instance.on_spawn()\n\n    return instance\n\nfunc _return_to_pool(instance: Node) -> void:\n    if not instance in _in_use:\n        return\n\n    _in_use.erase(instance)\n\n    if instance.has_method(\"on_despawn\"):\n        instance.on_despawn()\n\n    instance.process_mode = Node.PROCESS_MODE_DISABLED\n    instance.visible = false\n    _available.append(instance)\n\nfunc return_all() -> void:\n    for instance in _in_use.duplicate():\n        _return_to_pool(instance)\n```\n\n```gdscript\n# pooled_bullet.gd\nclass_name PooledBullet\nextends Area2D\n\nsignal returned_to_pool\n\n@export var speed: float = 500.0\n@export var lifetime: float = 5.0\n\nvar direction: Vector2\nvar _timer: float\n\nfunc on_spawn() -> void:\n    _timer = lifetime\n\nfunc on_despawn() -> void:\n    direction = Vector2.ZERO\n\nfunc initialize(pos: Vector2, dir: Vector2) -> void:\n    global_position = pos\n    direction = dir.normalized()\n    rotation = direction.angle()\n\nfunc _physics_process(delta: float) -> void:\n    position += direction * speed * delta\n\n    _timer -= delta\n    if _timer \u003C= 0:\n        returned_to_pool.emit()\n\nfunc _on_body_entered(body: Node2D) -> void:\n    if body.has_method(\"take_damage\"):\n        body.take_damage(10)\n    returned_to_pool.emit()\n```\n\n### Pattern 5: Component System\n\n```gdscript\n# health_component.gd\nclass_name HealthComponent\nextends Node\n\nsignal health_changed(current: int, maximum: int)\nsignal damaged(amount: int, source: Node)\nsignal healed(amount: int)\nsignal died\n\n@export var max_health: int = 100\n@export var invincibility_time: float = 0.0\n\nvar current_health: int:\n    set(value):\n        var old := current_health\n        current_health = clampi(value, 0, max_health)\n        if current_health != old:\n            health_changed.emit(current_health, max_health)\n\nvar _invincible: bool = false\n\nfunc _ready() -> void:\n    current_health = max_health\n\nfunc take_damage(amount: int, source: Node = null) -> int:\n    if _invincible or current_health \u003C= 0:\n        return 0\n\n    var actual := mini(amount, current_health)\n    current_health -= actual\n    damaged.emit(actual, source)\n\n    if current_health \u003C= 0:\n        died.emit()\n    elif invincibility_time > 0:\n        _start_invincibility()\n\n    return actual\n\nfunc heal(amount: int) -> int:\n    var actual := mini(amount, max_health - current_health)\n    current_health += actual\n    if actual > 0:\n        healed.emit(actual)\n    return actual\n\nfunc _start_invincibility() -> void:\n    _invincible = true\n    await get_tree().create_timer(invincibility_time).timeout\n    _invincible = false\n```\n\n```gdscript\n# hitbox_component.gd\nclass_name HitboxComponent\nextends Area2D\n\nsignal hit(hurtbox: HurtboxComponent)\n\n@export var damage: int = 10\n@export var knockback_force: float = 200.0\n\nvar owner_node: Node\n\nfunc _ready() -> void:\n    owner_node = get_parent()\n    area_entered.connect(_on_area_entered)\n\nfunc _on_area_entered(area: Area2D) -> void:\n    if area is HurtboxComponent:\n        var hurtbox := area as HurtboxComponent\n        if hurtbox.owner_node != owner_node:\n            hit.emit(hurtbox)\n            hurtbox.receive_hit(self)\n```\n\n```gdscript\n# hurtbox_component.gd\nclass_name HurtboxComponent\nextends Area2D\n\nsignal hurt(hitbox: HitboxComponent)\n\n@export var health_component: HealthComponent\n\nvar owner_node: Node\n\nfunc _ready() -> void:\n    owner_node = get_parent()\n\nfunc receive_hit(hitbox: HitboxComponent) -> void:\n    hurt.emit(hitbox)\n\n    if health_component:\n        health_component.take_damage(hitbox.damage, hitbox.owner_node)\n```\n\n### Pattern 6: Scene Management\n\n```gdscript\n# scene_manager.gd (Autoload)\nextends Node\n\nsignal scene_loading_started(scene_path: String)\nsignal scene_loading_progress(progress: float)\nsignal scene_loaded(scene: Node)\nsignal transition_started\nsignal transition_finished\n\n@export var transition_scene: PackedScene\n@export var loading_scene: PackedScene\n\nvar _current_scene: Node\nvar _transition: CanvasLayer\nvar _loader: ResourceLoader\n\nfunc _ready() -> void:\n    _current_scene = get_tree().current_scene\n\n    if transition_scene:\n        _transition = transition_scene.instantiate()\n        add_child(_transition)\n        _transition.visible = false\n\nfunc change_scene(scene_path: String, with_transition: bool = true) -> void:\n    if with_transition:\n        await _play_transition_out()\n\n    _load_scene(scene_path)\n\nfunc change_scene_packed(scene: PackedScene, with_transition: bool = true) -> void:\n    if with_transition:\n        await _play_transition_out()\n\n    _swap_scene(scene.instantiate())\n\nfunc _load_scene(path: String) -> void:\n    scene_loading_started.emit(path)\n\n    # Check if already loaded\n    if ResourceLoader.has_cached(path):\n        var scene := load(path) as PackedScene\n        _swap_scene(scene.instantiate())\n        return\n\n    # Async loading\n    ResourceLoader.load_threaded_request(path)\n\n    while true:\n        var progress := []\n        var status := ResourceLoader.load_threaded_get_status(path, progress)\n\n        match status:\n            ResourceLoader.THREAD_LOAD_IN_PROGRESS:\n                scene_loading_progress.emit(progress[0])\n                await get_tree().process_frame\n            ResourceLoader.THREAD_LOAD_LOADED:\n                var scene := ResourceLoader.load_threaded_get(path) as PackedScene\n                _swap_scene(scene.instantiate())\n                return\n            _:\n                push_error(\"Failed to load scene: %s\" % path)\n                return\n\nfunc _swap_scene(new_scene: Node) -> void:\n    if _current_scene:\n        _current_scene.queue_free()\n\n    _current_scene = new_scene\n    get_tree().root.add_child(_current_scene)\n    get_tree().current_scene = _current_scene\n\n    scene_loaded.emit(_current_scene)\n    await _play_transition_in()\n\nfunc _play_transition_out() -> void:\n    if not _transition:\n        return\n\n    transition_started.emit()\n    _transition.visible = true\n\n    if _transition.has_method(\"transition_out\"):\n        await _transition.transition_out()\n    else:\n        await get_tree().create_timer(0.3).timeout\n\nfunc _play_transition_in() -> void:\n    if not _transition:\n        transition_finished.emit()\n        return\n\n    if _transition.has_method(\"transition_in\"):\n        await _transition.transition_in()\n    else:\n        await get_tree().create_timer(0.3).timeout\n\n    _transition.visible = false\n    transition_finished.emit()\n```\n\n### Pattern 7: Save System\n\n```gdscript\n# save_manager.gd (Autoload)\nextends Node\n\nconst SAVE_PATH := \"user:\u002F\u002Fsavegame.save\"\nconst ENCRYPTION_KEY := \"your_secret_key_here\"\n\nsignal save_completed\nsignal load_completed\nsignal save_error(message: String)\n\nfunc save_game(data: Dictionary) -> void:\n    var file := FileAccess.open_encrypted_with_pass(\n        SAVE_PATH,\n        FileAccess.WRITE,\n        ENCRYPTION_KEY\n    )\n\n    if file == null:\n        save_error.emit(\"Could not open save file\")\n        return\n\n    var json := JSON.stringify(data)\n    file.store_string(json)\n    file.close()\n\n    save_completed.emit()\n\nfunc load_game() -> Dictionary:\n    if not FileAccess.file_exists(SAVE_PATH):\n        return {}\n\n    var file := FileAccess.open_encrypted_with_pass(\n        SAVE_PATH,\n        FileAccess.READ,\n        ENCRYPTION_KEY\n    )\n\n    if file == null:\n        save_error.emit(\"Could not open save file\")\n        return {}\n\n    var json := file.get_as_text()\n    file.close()\n\n    var parsed := JSON.parse_string(json)\n    if parsed == null:\n        save_error.emit(\"Could not parse save data\")\n        return {}\n\n    load_completed.emit()\n    return parsed\n\nfunc delete_save() -> void:\n    if FileAccess.file_exists(SAVE_PATH):\n        DirAccess.remove_absolute(SAVE_PATH)\n\nfunc has_save() -> bool:\n    return FileAccess.file_exists(SAVE_PATH)\n```\n\n```gdscript\n# saveable.gd (Attach to saveable nodes)\nclass_name Saveable\nextends Node\n\n@export var save_id: String\n\nfunc _ready() -> void:\n    if save_id.is_empty():\n        save_id = str(get_path())\n\nfunc get_save_data() -> Dictionary:\n    var parent := get_parent()\n    var data := {\"id\": save_id}\n\n    if parent is Node2D:\n        data[\"position\"] = {\"x\": parent.position.x, \"y\": parent.position.y}\n\n    if parent.has_method(\"get_custom_save_data\"):\n        data.merge(parent.get_custom_save_data())\n\n    return data\n\nfunc load_save_data(data: Dictionary) -> void:\n    var parent := get_parent()\n\n    if data.has(\"position\") and parent is Node2D:\n        parent.position = Vector2(data.position.x, data.position.y)\n\n    if parent.has_method(\"load_custom_save_data\"):\n        parent.load_custom_save_data(data)\n```\n\n## Performance Tips\n\n```gdscript\n# 1. Cache node references\n@onready var sprite := $Sprite2D  # Good\n# $Sprite2D in _process()  # Bad - repeated lookup\n\n# 2. Use object pooling for frequent spawning\n# See Pattern 4\n\n# 3. Avoid allocations in hot paths\nvar _reusable_array: Array = []\n\nfunc _process(_delta: float) -> void:\n    _reusable_array.clear()  # Reuse instead of creating new\n\n# 4. Use static typing\nfunc calculate(value: float) -> float:  # Good\n    return value * 2.0\n\n# 5. Disable processing when not needed\nfunc _on_off_screen() -> void:\n    set_process(false)\n    set_physics_process(false)\n```\n\n## Best Practices\n\n### Do's\n- **Use signals for decoupling** - Avoid direct references\n- **Type everything** - Static typing catches errors\n- **Use resources for data** - Separate data from logic\n- **Pool frequently spawned objects** - Avoid GC hitches\n- **Use Autoloads sparingly** - Only for truly global systems\n\n### Don'ts\n- **Don't use `get_node()` in loops** - Cache references\n- **Don't couple scenes tightly** - Use signals\n- **Don't put logic in resources** - Keep them data-only\n- **Don't ignore the Profiler** - Monitor performance\n- **Don't fight the scene tree** - Work with Godot's design\n\n## Resources\n\n- [Godot Documentation](https:\u002F\u002Fdocs.godotengine.org\u002Fen\u002Fstable\u002F)\n- [GDQuest Tutorials](https:\u002F\u002Fwww.gdquest.com\u002F)\n- [Godot Recipes](https:\u002F\u002Fkidscancode.org\u002Fgodot_recipes\u002F)\n",{"name":5,"description":7},"main","87afe6ce96220accf50a345dea46784c8c9f210e","YYC3-Plugin-插件协议\u002FYYC3-Plugin-插件协议-external_plugins\u002Fgame-development\u002Fskills\u002Fgodot-gdscript-patterns",0,"https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base","https:\u002F\u002Fkb.yyc3.top\u002F","https:\u002F\u002Fgithub.com\u002FYYC-Cube","万象归元于云枢 | 深栈智启新纪元","2026-11-10 10:37:32",{"source":8,"skillName":5,"commands":34,"prompt":44,"zipAvailable":45},[35,38,41],{"tool":36,"command":37},"npx","npx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill godot-gdscript-patterns",{"tool":39,"command":40},"bunx","bunx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill godot-gdscript-patterns",{"tool":42,"command":43},"pnpm","pnpm dlx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill godot-gdscript-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Plugin-插件协议\u002FYYC3-Plugin-插件协议-external_plugins\u002Fgame-development\u002Fskills\u002Fgodot-gdscript-patterns\u002FSKILL.md",true,[],[48,55,62,69,76,83],{"skillNo":49,"name":50,"nameZh":6,"description":51,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":52,"categories":53,"keywords":54,"publishedAt":19},"01M7CTDWHERCXPQCP0K03A86AS","vercel-sandbox","Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include \"Vercel Sandbox browser\", \"microVM Chrome\", \"agent-browser in sandbox\", \"browser automation on Vercel\", or any task requiring Chrome in a Vercel Sandbox.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fvercel-sandbox\u002FSKILL.md",[],[],{"skillNo":56,"name":57,"nameZh":6,"description":58,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":59,"categories":60,"keywords":61,"publishedAt":19},"01M7CTD8D2BNJY54S65M47HEJA","slack","Interact with Slack workspaces using browser automation. Use when the user needs to check unread channels, navigate Slack, send messages, extract data, find information, search conversations, or automate any Slack task. Triggers include \"check my Slack\", \"what channels have unreads\", \"send a message to\", \"search Slack for\", \"extract from Slack\", \"find who said\", or any task requiring programmatic Slack interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fslack\u002FSKILL.md",[],[],{"skillNo":63,"name":64,"nameZh":6,"description":65,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":68,"publishedAt":19},"01M7CTCSSB8ZP3Q58C3YZDZK1W","electron","Automate Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify, etc.) using agent-browser via Chrome DevTools Protocol. Use when the user needs to interact with an Electron app, automate a desktop app, connect to a running app, control a native app, or test an Electron application. Triggers include \"automate Slack app\", \"control VS Code\", \"interact with Discord app\", \"test this Electron app\", \"connect to desktop app\", or any task requiring automation of a native Electron application.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Felectron\u002FSKILL.md",[],[],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":19},"01M7CTCBJY76688V4DCZGWFHGQ","dogfood","Systematically explore and test a web application to find bugs, UX issues, and other problems. Use when asked to \"dogfood\", \"QA\", \"exploratory test\", \"find issues\", \"bug hunt\", \"test this app\u002Fsite\u002Fplatform\", or review the quality of a web application. Produces a structured report with full reproduction evidence -- step-by-step screenshots, repro videos, and detailed repro steps for every issue -- so findings can be handed directly to the responsible teams.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fdogfood\u002FSKILL.md",[],[],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":82,"publishedAt":19},"01M7CTC4H0FFGVEAQR9PA4GA1A","agent-browser","Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fagent-browser\u002FSKILL.md",[],[],{"skillNo":84,"name":85,"nameZh":6,"description":86,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":89,"publishedAt":19},"01M7CTBKVFYBG78FDSABSDJTTQ","shadcn","Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn\u002Fui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", \"switch to --preset\", and for building a design system or applying a DESIGN.md or brand spec to shadcn\u002Fui.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-UI-界面组件\u002FYYC3-UI-界面组件-ui\u002Fskills\u002Fshadcn\u002FSKILL.md",[],[]]