Примеры Godot Engine

Примеры Godot Engine, включая 2D/3D игры, GDScript, C# и возможности движка

💻 Godot Hello World gdscript

🟢 simple ⭐⭐

Базовая настройка Godot Engine и первые примеры игр на GDScript

⏱️ 20 min 🏷️ godot, game development, gdscript, 2d, 3d
Prerequisites: Basic programming concepts, Game development basics
# Godot Engine Hello World Examples

# 1. Basic Scene Setup
# Create a new scene with a Sprite and Camera2D

# main.gd - Main scene script
extends Node

func _ready():
    print("Hello, Godot World!")
    setup_scene()

func setup_scene():
    # Create a simple sprite
    var sprite = Sprite2D.new()
    var texture = load("res://icon.png")
    sprite.texture = texture
    sprite.position = Vector2(100, 100)
    add_child(sprite)

    # Add a label
    var label = Label.new()
    label.text = "Hello, Godot!"
    label.position = Vector2(50, 50)
    label.add_theme_font_size_override("font_size", 24)
    add_child(label)

# 2. Basic Player Movement
# player.gd - Attach to a CharacterBody2D
extends CharacterBody2D

@export var speed = 300.0
@export var jump_velocity = -400.0

# Get the gravity from the project settings
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta):
    # Add gravity
    if not is_on_floor():
        velocity.y += gravity * delta

    # Handle Jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity

    # Get input direction
    var direction = Input.get_axis("ui_left", "ui_right")

    # Apply movement
    if direction:
        velocity.x = direction * speed
    else:
        velocity.x = move_toward(velocity.x, 0, speed)

    move_and_slide()

# 3. Basic 2D Platformer Setup
# Level.gd - Level management
extends Node2D

@export var respawn_position: Vector2 = Vector2(100, 100)
var player: CharacterBody2D

func _ready():
    player = $Player
    player.global_position = respawn_position

func _process(_delta):
    # Check for reset
    if Input.is_action_just_pressed("reset"):
        reset_level()

func reset_level():
    player.global_position = respawn_position
    player.velocity = Vector2.ZERO

# 4. Simple Collectible System
# collectible.gd - Attach to Area2D
extends Area2D

signal collected

func _ready():
    # Connect the body entered signal
    body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.name == "Player":
        # Emit signal and disappear
        collected.emit()
        queue_free()

# 5. Basic UI System
# ui.gd - UI Controller
extends Control

@onready var score_label: Label = $ScoreLabel
@onready var lives_label: Label = $LivesLabel
@onready var game_over_label: Label = $GameOverLabel

var score = 0
var lives = 3

func _ready():
    update_ui()

func add_score(points: int):
    score += points
    update_ui()

func lose_life():
    lives -= 1
    update_ui()

    if lives <= 0:
        game_over()

func update_ui():
    score_label.text = "Score: " + str(score)
    lives_label.text = "Lives: " + str(lives)

func game_over():
    game_over_label.visible = true
    get_tree().paused = true

# 6. Input Map Setup
# project.godot - Input configuration
[application]
config/name="Hello Godot"
run/main_scene="res://scenes/main.tscn"

[input]
move_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
jump={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null)
]
}

# 7. Basic 3D Scene
# player_3d.gd - First person controller
extends CharacterBody3D

@export var speed = 5.0
@export var jump_velocity = 4.5
@export var sensitivity = 0.003

# Get gravity from project settings
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var camera = $Head/Camera3D

func _ready():
    Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _unhandled_input(event):
    if event is InputEventMouseMotion:
        rotate_y(-event.relative.x * sensitivity)
        camera.rotate_x(-event.relative.y * sensitivity)
        camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-80), deg_to_rad(80))

func _physics_process(delta):
    # Add gravity
    if not is_on_floor():
        velocity.y -= gravity * delta

    # Handle jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity

    # Get input direction
    var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    if direction:
        velocity.x = direction.x * speed
        velocity.z = direction.z * speed
    else:
        velocity.x = move_toward(velocity.x, 0, speed)
        velocity.z = move_toward(velocity.z, 0, speed)

    move_and_slide()

# 8. Basic Animation Player
# animated_sprite.gd - Animation controller
extends AnimatedSprite2D

func _ready():
    # Play idle animation by default
    play("idle")

# Connect to character body movement
func play_walk_animation(is_moving: bool):
    if is_moving:
        if animation != "walk":
            play("walk")
    else:
        if animation != "idle":
            play("idle")

# 9. Scene Transitions
# scene_manager.gd
extends Node

@export var transition_duration: float = 1.0
@onready var color_rect: ColorRect = $ColorRect

func _ready():
    color_rect.visible = false

func transition_to_scene(scene_path: String):
    # Fade to black
    var tween = create_tween()
    color_rect.visible = true
    color_rect.modulate.a = 0.0

    tween.tween_property(color_rect, "modulate:a", 1.0, transition_duration / 2)
    tween.tween_callback(_load_scene.bind(scene_path))
    tween.tween_property(color_rect, "modulate:a", 0.0, transition_duration / 2)
    tween.tween_callback(_hide_transition)

func _load_scene(scene_path: String):
    get_tree().change_scene_to_file(scene_path)

func _hide_transition():
    color_rect.visible = false

# 10. Basic Audio Manager
# audio_manager.gd
extends Node

@onready var music_player: AudioStreamPlayer = $MusicPlayer
@onready var sfx_player: AudioStreamPlayer2D = $SFXPlayer

var music_volume: float = 0.8
var sfx_volume: float = 0.9

func play_music(stream: AudioStream, volume: float = music_volume):
    music_player.stream = stream
    music_player.volume_db = linear_to_db(volume)
    music_player.play()

func play_sfx(stream: AudioStream, position: Vector2 = Vector2.ZERO, volume: float = sfx_volume):
    if sfx_player is AudioStreamPlayer2D:
        sfx_player.global_position = position

    sfx_player.stream = stream
    sfx_player.volume_db = linear_to_db(volume)
    sfx_player.play()

func set_music_volume(volume: float):
    music_volume = clamp(volume, 0.0, 1.0)
    music_player.volume_db = linear_to_db(music_volume)

func set_sfx_volume(volume: float):
    sfx_volume = clamp(volume, 0.0, 1.0)
    sfx_player.volume_db = linear_to_db(sfx_volume)

# Usage examples and project structure

# File structure:
# res://
# ├── scenes/
# │   ├── main.tscn
# │   ├── player.tscn
# │   ├── level.tscn
# │   └── ui.tscn
# ├── scripts/
# │   ├── main.gd
# │   ├── player.gd
# │   ├── collectible.gd
# │   └── ui.gd
# ├── assets/
# │   ├── textures/
# │   ├── sounds/
# │   └── fonts/
# └── project.godot

print("Godot Hello World examples loaded!")
print("1. Create your first scene with Node2D")
print("2. Add a Sprite2D with a texture")
print("3. Create a player with CharacterBody2D")
print("4. Set up input actions in Project Settings")
print("5. Add UI elements with Control nodes")
print("6. Experiment with 3D scenes")
print("7. Add animations with AnimationPlayer")
print("8. Implement scene transitions")
print("9. Add sound effects and music")
print("10. Package your game for export")

💻 Продвинутая GDScript программирование gdscript

🟡 intermediate ⭐⭐⭐⭐

Продвинутые паттерны GDScript, концепции ООП и техники интеграции с движком

⏱️ 40 min 🏷️ godot, gdscript, advanced, design patterns
Prerequisites: GDScript basics, Godot engine fundamentals
# Advanced GDScript Programming Examples

# 1. Singleton Pattern - Global Manager
# AutoLoad Singleton: GameManager.gd
extends Node

signal game_state_changed(new_state: GameState)
signal score_changed(new_score: int)
signal lives_changed(new_lives: int)

enum GameState {
    MENU,
    PLAYING,
    PAUSED,
    GAME_OVER
}

var current_state: GameState = GameState.MENU : set = _set_game_state
var score: int = 0 : set = _set_score
var lives: int = 3 : set = _set_lives
var high_score: int = 0

var current_level: int = 1
var player_data: Dictionary = {}

func _ready():
    load_game_data()

func _set_game_state(new_state: GameState):
    if current_state != new_state:
        current_state = new_state
        game_state_changed.emit(new_state)

        match new_state:
            GameState.PLAYING:
                Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
            GameState.MENU, GameState.PAUSED:
                Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

func _set_score(new_score: int):
    if new_score != score:
        score = new_score
        score_changed.emit(score)

        if score > high_score:
            high_score = score
            save_game_data()

func _set_lives(new_lives: int):
    if new_lives != lives:
        lives = new_lives
        lives_changed.emit(lives)

        if lives <= 0:
            current_state = GameState.GAME_OVER

func save_game_data():
    var save_data = {
        "high_score": high_score,
        "current_level": current_level,
        "player_data": player_data
    }

    var file = FileAccess.open("user://savegame.save", FileAccess.WRITE)
    if file:
        file.store_var(save_data)
        file.close()

func load_game_data():
    var file = FileAccess.open("user://savegame.save", FileAccess.READ)
    if file:
        var save_data = file.get_var()
        file.close()

        high_score = save_data.get("high_score", 0)
        current_level = save_data.get("current_level", 1)
        player_data = save_data.get("player_data", {})

# 2. State Machine Pattern
# state_machine.gd
extends Node

class_name StateMachine

var state: State = null
var states: Dictionary = {}

func _ready():
    # Add states from child nodes
    for child in get_children():
        if child is State:
            states[child.name.to_lower()] = child
            child.state_machine = self

func change_state(state_name: String) -> void:
    var new_state = states.get(state_name.to_lower())

    if new_state and new_state != state:
        if state:
            state.exit()

        state = new_state
        state.enter()

func _physics_process(delta):
    if state:
        state._physics_process(delta)

func _process(delta):
    if state:
        state._process(delta)

# Base State Class
class State extends Node:
    var state_machine: StateMachine

    func enter():
        pass

    func exit():
        pass

    func _process(delta):
        pass

    func _physics_process(delta):
        pass

# Player States
# IdleState.gd
extends "res://scripts/state.gd"

func enter():
    owner.animation_player.play("idle")
    owner.velocity = Vector2.ZERO

func _physics_process(delta):
    if Input.is_action_just_pressed("jump") and owner.is_on_floor():
        state_machine.change_state("jump")
        return

    var direction = Input.get_axis("move_left", "move_right")
    if direction != 0:
        state_machine.change_state("run")
        return

    if not owner.is_on_floor():
        state_machine.change_state("fall")

# 3. Event System - Observer Pattern
# event_bus.gd
extends Node

signal player_died
signal level_completed
signal enemy_spawned(enemy_path: String)
signal item_collected(item_type: String, value: int)

static var instance: EventBus

func _ready():
    if instance == null:
        instance = self
    else:
        queue_free()

# Usage in other scripts:
# EventBus.instance.enemy_spawned.emit("res://scenes/enemies/goblin.tscn")

# 4. Object Pooling Pattern
# object_pool.gd
extends Node

class_name ObjectPool

@export var scene: PackedScene
@export var initial_size: int = 10

var pool: Array[Node] = []
var active_objects: Array[Node] = []

func _ready():
    for i in initial_size:
        var instance = scene.instantiate()
        add_child(instance)
        instance.visible = false
        pool.push_back(instance)

func get_object():
    var obj: Node

    if pool.size() > 0:
        obj = pool.pop_back()
    else:
        obj = scene.instantiate()
        add_child(obj)

    obj.visible = true
    active_objects.push_back(obj)
    return obj

func return_object(obj: Node):
    if obj in active_objects:
        active_objects.erase(obj)
        obj.visible = false
        pool.push_back(obj)

func clear():
    for obj in active_objects:
        obj.queue_free()
    active_objects.clear()

    for obj in pool:
        obj.queue_free()
    pool.clear()

# 5. Component System
# component.gd
extends Node
class_name Component

var entity: Node

func set_entity(new_entity: Node):
    entity = new_entity

# Health Component
class_name HealthComponent
extends Component

@export var max_health: float = 100.0
var current_health: float

signal health_changed(new_health: float)
signal died

func _ready():
    current_health = max_health

func take_damage(damage: float):
    current_health = max(0, current_health - damage)
    health_changed.emit(current_health)

    if current_health <= 0:
        died.emit()

func heal(amount: float):
    current_health = min(max_health, current_health + amount)
    health_changed.emit(current_health)

# 6. Data Persistence with JSON
# data_manager.gd
extends Node

class_name DataManager

var player_data_path = "user://player_data.json"

func save_player_data(data: Dictionary):
    var file = FileAccess.open(player_data_path, FileAccess.WRITE)
    if file:
        file.store_string(JSON.stringify(data))
        file.close()

func load_player_data() -> Dictionary:
    var file = FileAccess.open(player_data_path, FileAccess.READ)
    if file:
        var json_string = file.get_as_text()
        file.close()

        var json = JSON.new()
        var parse_result = json.parse(json_string)

        if parse_result == OK:
            return json.data
        else:
            print("Error parsing JSON: ", parse_result)

    return {}

func delete_player_data():
    if FileAccess.file_exists(player_data_path):
        DirAccess.remove_absolute(player_data_path)

# 7. Custom Resource Types
# item_resource.gd
extends Resource
class_name ItemResource

@export var name: String
@export var description: String
@export var icon: Texture2D
@export var value: int
@export var stack_size: int = 1
@export var rarity: String = "common"

func use(user: Node):
    # Override in child classes
    pass

# Weapon Resource
class WeaponResource extends ItemResource
@export var damage: int
@export var attack_speed: float
@export var range: float

func use(user: Node):
    if user.has_method("attack"):
        user.attack(damage, range)

# 8. Procedural Generation
# dungeon_generator.gd
extends Node

class_name DungeonGenerator

@export var width: int = 50
@export var height: int = 50
@export var room_count: int = 10
@export var min_room_size: int = 3
@export var max_room_size: int = 10

var grid: Array[int] = []
var rooms: Array[Rect2i] = []

func generate() -> Array[Vector2i]:
    # Initialize grid with walls (1 = wall, 0 = floor)
    grid.clear()
    grid.resize(width * height)
    grid.fill(1)

    # Generate rooms
    rooms.clear()
    for i in room_count:
        var room = generate_room()
        if place_room(room):
            rooms.push_back(room)

    # Connect rooms with corridors
    connect_rooms()

    # Convert grid to coordinates
    var floor_positions: Array[Vector2i] = []
    for y in range(height):
        for x in range(width):
            if grid[y * width + x] == 0:
                floor_positions.push_back(Vector2i(x, y))

    return floor_positions

func generate_room() -> Rect2i:
    var room_width = randi_range(min_room_size, max_room_size)
    var room_height = randi_range(min_room_size, max_room_size)
    var x = randi_range(1, width - room_width - 1)
    var y = randi_range(1, height - room_height - 1)

    return Rect2i(x, y, room_width, room_height)

func place_room(room: Rect2i) -> bool:
    # Check if room overlaps with existing rooms
    for existing_room in rooms:
        if room.intersects(existing_room, true):
            return false

    # Carve room into grid
    for y in range(room.position.y, room.position.y + room.size.y):
        for x in range(room.position.x, room.position.x + room.size.x):
            grid[y * width + x] = 0

    return true

func connect_rooms():
    for i in range(rooms.size() - 1):
        var room1 = rooms[i]
        var room2 = rooms[i + 1]

        var start = room1.get_center()
        var end = room2.get_center()

        # Create L-shaped corridor
        create_corridor(start.x, start.y, end.x, start.y)
        create_corridor(end.x, start.y, end.x, end.y)

func create_corridor(x1: int, y1: int, x2: int, y2: int):
    # Horizontal corridor
    var x_start = min(x1, x2)
    var x_end = max(x1, x2)
    for x in range(x_start, x_end + 1):
        if x > 0 and x < width - 1 and y1 > 0 and y1 < height - 1:
            grid[y1 * width + x] = 0

    # Vertical corridor
    var y_start = min(y1, y2)
    var y_end = max(y1, y2)
    for y in range(y_start, y_end + 1):
        if x2 > 0 and x2 < width - 1 and y > 0 and y < height - 1:
            grid[y * width + x2] = 0

# 9. Advanced Animation System
# animation_controller.gd
extends Node

class_name AnimationController

@onready var animation_player: AnimationPlayer = get_parent().get_node("AnimationPlayer")
@onready var sprite: Sprite2D = get_parent()

var current_animation: String = ""
var is_facing_right: bool = true

func play_animation(anim_name: String, forced: bool = false):
    if anim_name != current_animation or forced:
        current_animation = anim_name
        animation_player.play(anim_name)

func set_facing_direction(direction: float):
    if direction > 0 and not is_facing_right:
        flip_horizontal()
    elif direction < 0 and is_facing_right:
        flip_horizontal()

func flip_horizontal():
    is_facing_right = not is_facing_right
    sprite.flip_h = not sprite.flip_h

# 10. AI Behavior Tree
# behavior_tree.gd
extends Node

class_name BehaviorTree

var root_node: BTNode

func _ready():
    build_tree()

func build_tree():
    # Example behavior tree structure
    root_node = BTSelector.new([
        BTPriority.new([
            BTCondition.new(check_health),
            BTSequence.new([
                BTAction.new(find_enemy),
                BTAction.new(attack_enemy)
            ])
        ]),
        BTAction.new(patrol)
    ])

func _process(delta):
    if root_node:
        root_node.execute()

# Behavior Tree Node Base Class
class BTNode:
    var parent: BTNode
    var children: Array[BTNode] = []

    func add_child(node: BTNode):
        children.push_back(node)
        node.parent = self

    func execute() -> bool:
        return false

# Action Node
class BTAction extends BTNode:
    var action_callable: Callable

    func _init(callable: Callable):
        action_callable = callable

    func execute() -> bool:
        return action_callable.call()

# Condition Node
class BTCondition extends BTNode:
    var condition_callable: Callable

    func _init(callable: Callable):
        condition_callable = callable

    func execute() -> bool:
        return condition_callable.call()

# Selector Node (OR)
class BTSelector extends BTNode:
    func _init(nodes: Array[BTNode] = []):
        for node in nodes:
            add_child(node)

    func execute() -> bool:
        for child in children:
            if child.execute():
                return true
        return false

# Sequence Node (AND)
class BTSequence extends BTNode:
    func _init(nodes: Array[BTNode] = []):
        for node in nodes:
            add_child(node)

    func execute() -> bool:
        for child in children:
            if not child.execute():
                return false
        return true

print("Advanced GDScript examples loaded!")
print("These patterns demonstrate professional game development practices.")

💻 Интеграция Godot C# csharp

🟡 intermediate ⭐⭐⭐⭐

Написание скриптов на C# в Godot с интеграцией .NET, расширенными функциями и оптимизацией производительности

⏱️ 50 min 🏷️ godot, csharp, dotnet, advanced
Prerequisites: C# programming, Godot basics, OOP concepts
// Godot C# Integration Examples
// Requires Godot 4.x with .NET support enabled

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;

// 1. Basic C# Player Controller
public partial class PlayerController : CharacterBody2D
{
    [ExportGroup("Movement Settings")]
    [Export] public float Speed { get; set; } = 300.0f;
    [Export] public float JumpVelocity { get; set; } = -400.0f;
    [Export] public float Acceleration { get; set; } = 1000.0f;
    [Export] public float Friction { get; set; } = 1000.0f;

    [ExportGroup("References")]
    [Export] public AnimatedSprite2D AnimatedSprite { get; set; }
    [Export] public CollisionShape2D CollisionShape { get; set; }

    private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
    private PlayerState _currentState = PlayerState.Idle;

    private enum PlayerState
    {
        Idle,
        Running,
        Jumping,
        Falling,
        Attacking,
        Hurt
    }

    public override void _Ready()
    {
        GD.Print("Player controller initialized with C#!");

        // Initialize components
        if (AnimatedSprite == null)
            AnimatedSprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");

        if (CollisionShape == null)
            CollisionShape = GetNode<CollisionShape2D>("CollisionShape2D");

        // Connect signals
        AnimatedSprite.AnimationFinished += OnAnimationFinished;
    }

    public override void _PhysicsProcess(double delta)
    {
        HandleGravity(delta);
        HandleInput(delta);
        HandleStates();
        MoveAndSlide();
    }

    private void HandleGravity(double delta)
    {
        if (!IsOnFloor())
        {
            Velocity = Velocity with { Y = (float)(Velocity.Y + _gravity * delta) };
        }
    }

    private void HandleInput(double delta)
    {
        // Handle Jump
        if (Input.IsActionJustPressed("jump") && IsOnFloor())
        {
            Velocity = Velocity with { Y = JumpVelocity };
            ChangeState(PlayerState.Jumping);
        }

        // Handle Attack
        if (Input.IsActionJustPressed("attack"))
        {
            ChangeState(PlayerState.Attacking);
        }

        // Handle Movement
        var direction = Input.GetAxis("ui_left", "ui_right");

        if (direction != 0)
        {
            Velocity = Velocity with {
                X = Mathf.MoveToward(Velocity.X, direction * Speed, (float)(Acceleration * delta))
            };
            AnimatedSprite.FlipH = direction < 0;
        }
        else
        {
            Velocity = Velocity with {
                X = Mathf.MoveToward(Velocity.X, 0, (float)(Friction * delta))
            };
        }
    }

    private void HandleStates()
    {
        var newstate = PlayerState.Idle;

        if (_currentState == PlayerState.Attacking || _currentState == PlayerState.Hurt)
        {
            return; // Don't change state during attack or hurt
        }

        if (!IsOnFloor())
        {
            newstate = Velocity.Y < 0 ? PlayerState.Jumping : PlayerState.Falling;
        }
        else if (Mathf.Abs(Velocity.X) > 10f)
        {
            newstate = PlayerState.Running;
        }

        ChangeState(newstate);
    }

    private void ChangeState(PlayerState newState)
    {
        if (_currentState == newState) return;

        _currentState = newState;

        switch (_currentState)
        {
            case PlayerState.Idle:
                AnimatedSprite.Play("idle");
                break;
            case PlayerState.Running:
                AnimatedSprite.Play("run");
                break;
            case PlayerState.Jumping:
                AnimatedSprite.Play("jump");
                break;
            case PlayerState.Falling:
                AnimatedSprite.Play("fall");
                break;
            case PlayerState.Attacking:
                AnimatedSprite.Play("attack");
                break;
            case PlayerState.Hurt:
                AnimatedSprite.Play("hurt");
                break;
        }
    }

    private void OnAnimationFinished()
    {
        if (_currentState == PlayerState.Attacking || _currentState == PlayerState.Hurt)
        {
            ChangeState(PlayerState.Idle);
        }
    }
}

// 2. Singleton GameManager in C#
public partial class GameManager : Node
{
    public static GameManager Instance { get; private set; }

    [Signal] public delegate void ScoreChangedEventHandler(int newScore);
    [Signal] public delegate void LivesChangedEventHandler(int newLives);
    [Signal] public delegate void GameStateChangedEventHandler(GameState newState);

    public enum GameState
    {
        Menu,
        Playing,
        Paused,
        GameOver
    }

    private GameState _currentState = GameState.Menu;
    private int _score = 0;
    private int _lives = 3;
    private int _highScore = 0;
    private int _currentLevel = 1;

    // Properties with setters that emit signals
    public GameState CurrentState
    {
        get => _currentState;
        set
        {
            if (_currentState != value)
            {
                _currentState = value;
                EmitSignal(GameStateChangedSignalName, (int)value);

                switch (value)
                {
                    case GameState.Playing:
                        Input.MouseMode = Input.MouseModeEnum.Captured;
                        break;
                    case GameState.Menu:
                    case GameState.Paused:
                        Input.MouseMode = Input.MouseModeEnum.Visible;
                        break;
                }
            }
        }
    }

    public int Score
    {
        get => _score;
        set
        {
            if (_score != value)
            {
                _score = value;
                EmitSignal(ScoreChangedSignalName, value);

                if (_score > _highScore)
                {
                    _highScore = _score;
                    SaveGameData();
                }
            }
        }
    }

    public int Lives
    {
        get => _lives;
        set
        {
            if (_lives != value)
            {
                _lives = Mathf.Max(0, value);
                EmitSignal(LivesChangedSignalName, _lives);

                if (_lives <= 0)
                {
                    CurrentState = GameState.GameOver;
                }
            }
        }
    }

    public override void _Ready()
    {
        if (Instance == null)
        {
            Instance = this;
            LoadGameData();
        }
        else
        {
            QueueFree();
        }
    }

    public void AddScore(int points)
    {
        Score += points;
    }

    public void LoseLife()
    {
        Lives--;
    }

    public void ResetGame()
    {
        Score = 0;
        Lives = 3;
        CurrentLevel = 1;
        CurrentState = GameState.Menu;
    }

    private const string SavePath = "user://savegame.json";

    public void SaveGameData()
    {
        var saveData = new
        {
            highScore = _highScore,
            currentLevel = _currentLevel,
            lastPlayed = DateTime.Now.ToString("O")
        };

        var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write);
        if (file != null)
        {
            var json = Json.Stringify(saveData);
            file.StoreString(json);
            file.Close();
        }
    }

    public void LoadGameData()
    {
        if (!FileAccess.FileExists(SavePath)) return;

        var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read);
        if (file != null)
        {
            var json = file.GetAsText();
            file.Close();

            var jsonParse = new Json();
            var parseResult = jsonParse.Parse(json);

            if (parseResult == Error.Ok)
            {
                var data = jsonParse.Data.AsGodotDictionary();
                _highScore = data.Get("highScore", 0).AsInt32();
                _currentLevel = data.Get("currentLevel", 1).AsInt32();
            }
        }
    }
}

// 3. Enemy AI with C#
public partial class EnemyAI : CharacterBody2D
{
    [Export] public float Speed { get; set; } = 100.0f;
    [Export] public float DetectionRange { get; set; } = 150.0f;
    [Export] public float AttackRange { get; set; } = 50.0f;
    [Export] public float AttackCooldown { get; set; } = 1.0f;

    private Node2D _target;
    private Vector2 _patrolDirection;
    private float _attackTimer = 0f;
    private EnemyState _currentState = EnemyState.Patrol;

    private enum EnemyState
    {
        Patrol,
        Chase,
        Attack,
        Idle
    }

    [Export] public PackedScene ProjectileScene { get; set; }

    public override void _Ready()
    {
        _patrolDirection = new Vector2(GD.Randf(), GD.Randf()).Normalized();

        // Find player
        _target = GetTree().GetFirstNodeInGroup("player") as Node2D;
    }

    public override void _PhysicsProcess(double delta)
    {
        UpdateState();
        HandleMovement(delta);
        HandleAttack(delta);
        MoveAndSlide();
    }

    private void UpdateState()
    {
        if (_target == null)
        {
            _currentState = EnemyState.Idle;
            return;
        }

        var distanceToTarget = GlobalPosition.DistanceTo(_target.GlobalPosition);

        switch (_currentState)
        {
            case EnemyState.Patrol:
                if (distanceToTarget <= DetectionRange)
                {
                    _currentState = EnemyState.Chase;
                }
                break;

            case EnemyState.Chase:
                if (distanceToTarget <= AttackRange)
                {
                    _currentState = EnemyState.Attack;
                }
                else if (distanceToTarget > DetectionRange * 1.5f)
                {
                    _currentState = EnemyState.Patrol;
                }
                break;

            case EnemyState.Attack:
                if (distanceToTarget > AttackRange)
                {
                    _currentState = EnemyState.Chase;
                }
                break;
        }
    }

    private void HandleMovement(double delta)
    {
        switch (_currentState)
        {
            case EnemyState.Patrol:
                Velocity = _patrolDirection * Speed;

                // Check for walls and change direction
                if (IsOnWall())
                {
                    _patrolDirection = -_patrolDirection;
                }
                break;

            case EnemyState.Chase:
                if (_target != null)
                {
                    var direction = (_target.GlobalPosition - GlobalPosition).Normalized();
                    Velocity = direction * Speed;
                }
                break;

            default:
                Velocity = Vector2.Zero;
                break;
        }
    }

    private void HandleAttack(double delta)
    {
        if (_currentState == EnemyState.Attack)
        {
            _attackTimer -= (float)delta;

            if (_attackTimer <= 0f && _target != null)
            {
                PerformAttack();
                _attackTimer = AttackCooldown;
            }
        }
    }

    private void PerformAttack()
    {
        if (ProjectileScene == null || _target == null) return;

        var projectile = ProjectileScene.Instantiate() as Node2D;
        if (projectile != null)
        {
            GetParent().AddChild(projectile);
            projectile.GlobalPosition = GlobalPosition;

            // Set projectile direction towards target
            var direction = (_target.GlobalPosition - GlobalPosition).Normalized();
            projectile.Call("SetDirection", direction);
        }
    }
}

// 4. Inventory System with C#
public partial class InventoryManager : Node
{
    [Signal] public delegate void ItemAddedEventHandler(ItemData item);
    [Signal] public delegate void ItemRemovedEventHandler(ItemData item);
    [Signal] public delegate void InventoryChangedEventHandler();

    public List<InventorySlot> Slots { get; private set; } = new List<InventorySlot>();
    public int Capacity { get; private set; } = 20;

    public InventoryManager()
    {
        // Initialize inventory slots
        for (int i = 0; i < Capacity; i++)
        {
            Slots.Add(new InventorySlot());
        }
    }

    public bool AddItem(ItemData item, int quantity = 1)
    {
        // Try to stack with existing items
        if (item.IsStackable)
        {
            foreach (var slot in Slots)
            {
                if (slot.Item?.Id == item.Id && slot.Quantity < item.MaxStackSize)
                {
                    var canAdd = Mathf.Min(quantity, item.MaxStackSize - slot.Quantity);
                    slot.Quantity += canAdd;
                    quantity -= canAdd;

                    EmitSignal(ItemAddedSignalName, item);
                    EmitSignal(InventoryChangedSignalName);

                    if (quantity <= 0) return true;
                }
            }
        }

        // Try to add to empty slots
        foreach (var slot in Slots)
        {
            if (slot.Item == null)
            {
                slot.Item = item;
                slot.Quantity = quantity;

                EmitSignal(ItemAddedSignalName, item);
                EmitSignal(InventoryChangedSignalName);

                return true;
            }
        }

        return false; // No space available
    }

    public bool RemoveItem(ItemData item, int quantity = 1)
    {
        foreach (var slot in Slots)
        {
            if (slot.Item?.Id == item.Id)
            {
                if (slot.Quantity >= quantity)
                {
                    slot.Quantity -= quantity;

                    if (slot.Quantity <= 0)
                    {
                        slot.Item = null;
                        slot.Quantity = 0;
                    }

                    EmitSignal(ItemRemovedSignalName, item);
                    EmitSignal(InventoryChangedSignalName);

                    return true;
                }
            }
        }

        return false; // Item not found
    }

    public int GetItemCount(ItemData item)
    {
        int total = 0;
        foreach (var slot in Slots)
        {
            if (slot.Item?.Id == item.Id)
            {
                total += slot.Quantity;
            }
        }
        return total;
    }

    public List<InventorySlot> GetItemsByType(Type itemType)
    {
        return Slots.Where(slot => slot.Item?.GetType() == itemType).ToList();
    }
}

// Data classes
public partial class ItemData : Resource
{
    [Export] public string Id { get; set; }
    [Export] public string Name { get; set; }
    [Export] public string Description { get; set; }
    [Export] public Texture2D Icon { get; set; }
    [Export] public int MaxStackSize { get; set; } = 1;
    [Export] public bool IsStackable => MaxStackSize > 1;
    [Export] public int Value { get; set; }

    public virtual void Use(Node user)
    {
        GD.Print($"Using {Name}");
    }
}

public partial class WeaponData : ItemData
{
    [Export] public int Damage { get; set; }
    [Export] public float AttackSpeed { get; set; } = 1.0f;
    [Export] public float Range { get; set; } = 50.0f;

    public override void Use(Node user)
    {
        GD.Print($"Attacking with {Name} for {Damage} damage");

        // Find enemies in range and deal damage
        var spaceState = user.GetWorld2D().DirectSpaceState;
        var query = PhysicsShapeQueryParameters2D.CreateCircle(user.GlobalPosition, Range);
        query.CollisionMask = 1 << 1; // Enemy layer

        var results = spaceState.IntersectShape(query);
        foreach (var result in results)
        {
            var enemy = result.Collider as EnemyAI;
            enemy?.Call("TakeDamage", Damage);
        }
    }
}

public class InventorySlot
{
    public ItemData Item { get; set; }
    public int Quantity { get; set; }
    public bool IsEmpty => Item == null || Quantity <= 0;
}

// 5. UI Manager with C#
public partial class UIManager : Control
{
    [Export] public PackedScene DamageNumberScene { get; set; }

    private Control _hudContainer;
    private Label _scoreLabel;
    private Label _livesLabel;
    private Control _pauseMenu;
    private Control _gameOverScreen;

    public override void _Ready()
    {
        // Find UI elements
        _hudContainer = GetNode<Control>("HUDContainer");
        _scoreLabel = GetNode<Label>("HUDContainer/ScoreLabel");
        _livesLabel = GetNode<Label>("HUDContainer/LivesLabel");
        _pauseMenu = GetNode<Control>("PauseMenu");
        _gameOverScreen = GetNode<Control>("GameOverScreen");

        // Connect to GameManager signals
        if (GameManager.Instance != null)
        {
            GameManager.Instance.ScoreChanged += OnScoreChanged;
            GameManager.Instance.LivesChanged += OnLivesChanged;
            GameManager.Instance.GameStateChanged += OnGameStateChanged;
        }

        // Initialize UI state
        _pauseMenu.Visible = false;
        _gameOverScreen.Visible = false;
    }

    private void OnScoreChanged(int newScore)
    {
        if (_scoreLabel != null)
        {
            _scoreLabel.Text = $"Score: {newScore}";
        }
    }

    private void OnLivesChanged(int newLives)
    {
        if (_livesLabel != null)
        {
            _livesLabel.Text = $"Lives: {newLives}";
        }
    }

    private void OnGameStateChanged(GameManager.GameState newState)
    {
        switch (newState)
        {
            case GameManager.GameState.Menu:
                _hudContainer.Visible = false;
                _pauseMenu.Visible = false;
                _gameOverScreen.Visible = false;
                break;

            case GameManager.GameState.Playing:
                _hudContainer.Visible = true;
                _pauseMenu.Visible = false;
                _gameOverScreen.Visible = false;
                break;

            case GameManager.GameState.Paused:
                _pauseMenu.Visible = true;
                break;

            case GameManager.GameState.GameOver:
                _gameOverScreen.Visible = true;
                break;
        }
    }

    public void ShowDamageNumber(Vector2 position, int damage, bool isCrit = false)
    {
        if (DamageNumberScene == null) return;

        var damageNumber = DamageNumberScene.Instantiate() as Control;
        if (damageNumber != null)
        {
            AddChild(damageNumber);
            damageNumber.GlobalPosition = position;

            var label = damageNumber.GetNode<Label>("Label");
            if (label != null)
            {
                label.Text = damage.ToString();
                label.Modulate = isCrit ? Colors.Red : Colors.Yellow;
            }

            // Animate the damage number
            var tween = CreateTween();
            tween.TweenProperty(damageNumber, "global_position", position + Vector2.Up * 50, 1.0f);
            tween.TweenProperty(damageNumber, "modulate:a", 0f, 0.5f);
            tween.TweenCallback(Callable.From(() => damageNumber.QueueFree()));
        }
    }
}

// 6. Performance Optimizations
public partial class ObjectPoolCSharp : Node
{
    [Export] public PackedScene PooledScene { get; set; }
    [Export] public int InitialSize { get; set; } = 10;

    private readonly Queue<Node> _pool = new Queue<Node>();
    private readonly List<Node> _activeObjects = new List<Node>();

    public override void _Ready()
    {
        // Pre-allocate objects
        for (int i = 0; i < InitialSize; i++)
        {
            var obj = PooledScene.Instantiate();
            AddChild(obj);
            obj.Visible = false;
            _pool.Enqueue(obj);
        }
    }

    public Node GetObject()
    {
        Node obj;

        if (_pool.Count > 0)
        {
            obj = _pool.Dequeue();
        }
        else
        {
            obj = PooledScene.Instantiate();
            AddChild(obj);
        }

        obj.Visible = true;
        _activeObjects.Add(obj);

        return obj;
    }

    public void ReturnObject(Node obj)
    {
        if (_activeObjects.Contains(obj))
        {
            _activeObjects.Remove(obj);
            obj.Visible = false;
            _pool.Enqueue(obj);
        }
    }

    public void Clear()
    {
        foreach (var obj in _activeObjects)
        {
            obj.QueueFree();
        }
        _activeObjects.Clear();

        while (_pool.Count > 0)
        {
            var obj = _pool.Dequeue();
            obj.QueueFree();
        }
    }
}

// Project configuration for C#
// project.godot (C# enabled section)
/*
[application]
config/name="Godot C# Game"
run/main_scene="res://scenes/Main.tscn"

[dotnet]
project/assembly_name="MyGame"

[rendering]
renderer/rendering_method="mobile"
renderer/rendering_method.mobile="gl_compatibility"
*/

/*
To use C# in Godot:
1. Create new project with C# template
2. Enable .NET support in Project Settings
3. Install .NET SDK 6.0 or later
4. Create .cs files in the C# project structure
5. Use Godot editor or Visual Studio/VS Code for development

C# benefits in Godot:
- Strong typing and better IDE support
- Access to .NET ecosystem
- Better performance for complex logic
- Advanced debugging and profiling tools
- LINQ and modern C# features
- Async/await support
*/