[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F1E6BXVF4S3B4WWG5YJSZ2":3,"skill-same:01M6F1E6BXVF4S3B4WWG5YJSZ2":68},{"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":34,"linkOnly":35,"markdownContent":36,"markdownBody":37,"frontMatter":38,"refName":39,"commitSha":40,"skillDirectory":41,"forksCount":42,"repositoryHtmlUrl":43,"repositoryHomepageUrl":44,"ownerHtmlUrl":45,"ownerBlogUrl":6,"ownerDescription":46,"updatedAt":47,"install":48,"resources":61},"01M6F1E6BXVF4S3B4WWG5YJSZ2","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.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fgodot-gdscript-patterns\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"godot gdscript patterns","patterns","godot","best practices","state","Optimization","learning","game","use","master","scenes","signals","systems","state machines","games","2026-11-10 10:12:30",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","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fgodot-gdscript-patterns",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill godot-gdscript-patterns",{"tool":54,"command":55},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill godot-gdscript-patterns",{"tool":57,"command":58},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill godot-gdscript-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fgodot-gdscript-patterns\u002FSKILL.md",true,[62],{"path":63,"resourceType":64,"resourceTypeDesc":65,"sizeBytes":66,"sourceHtmlUrl":67},"skill-report.json","REFERENCE","参考文档",12938,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fgodot-gdscript-patterns\u002Fskill-report.json",[69,83,93,102,108,115],{"skillNo":70,"name":71,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":72,"categories":73,"keywords":79,"publishedAt":34},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[74],{"categoryNo":75,"categoryName":76,"taxonomyType":77,"taxonomyTypeDesc":78},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[80,81,82],"model","model hierarchy","model hierarchy skill",{"skillNo":84,"name":85,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":86,"categories":87,"keywords":89,"publishedAt":34},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[88],{"categoryNo":75,"categoryName":76,"taxonomyType":77,"taxonomyTypeDesc":78},[90,91,92],"MCP","zoom","zoom mcp",{"skillNo":94,"name":95,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":98,"publishedAt":34},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[99,100,101,91],"sdk","zoom apps sdk","apps",{"skillNo":103,"name":104,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":107,"publishedAt":34},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[104],{"skillNo":109,"name":110,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":114,"publishedAt":34},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[113],{"categoryNo":75,"categoryName":76,"taxonomyType":77,"taxonomyTypeDesc":78},[110],{"skillNo":116,"name":117,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":120,"publishedAt":34},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[117]]