🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Примеры 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
*/