Настройка проекта Godot 4.5.1 + .NET 10
Важно!
Godot 4.5.1 требует .NET SDK 8.0 или новее для работы с C#. Убедитесь, что у вас установлен .NET 10 для максимальной совместимости.
Создание 2D C# проекта
Настройка
Шаги для создания нового 2D проекта с поддержкой C# в Godot 4.5.1:
// 1. Создайте новый проект через менеджер проектов
// 2. Выберите шаблон "2D"
// 3. В настройках проекта включите C# поддержку
// 4. Убедитесь, что в Project Settings → .NET:
// - Build Configuration: Debug
// - Target Framework: net8.0 (или net10.0)
// 5. Создайте главную сцену с Node2D корнем
csproj файл для .NET 10
Конфигурация
Пример файла проекта для Godot 4.5.1 с .NET 10:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GodotSharp" Version="4.5.1" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="4.5.1" />
</ItemGroup>
</Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GodotSharp" Version="4.5.1" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="4.5.1" />
</ItemGroup>
</Project>
Основные 2D Ноды
Node2D
Базовый класс
public class MyNode2D : Node2D
Базовый класс для всех 2D объектов. Имеет позицию, поворот и масштаб.
Основные свойства:
| Свойство | Тип | Описание |
|---|---|---|
| Position | Vector2 | Позиция ноды в пикселях |
| Rotation | float | Поворот в радианах |
| Scale | Vector2 | Масштаб по осям X и Y |
| GlobalPosition | Vector2 | Глобальная позиция в дереве сцен |
using Godot;
public partial class Player : Node2D
{
[Export]
public float Speed { get; set; } = 200.0f;
private Sprite2D _sprite;
public override void _Ready()
{
// Получаем дочерний Sprite2D
_sprite = GetNode<Sprite2D>("Sprite2D");
}
public override void _Process(double delta)
{
// Движение с клавиатуры
Vector2 direction = Input.GetVector("move_left", "move_right", "move_up", "move_down");
Position += direction * Speed * (float)delta;
// Поворот спрайта к курсору мыши
if (direction != Vector2.Zero)
{
Rotation = direction.Angle();
}
}
}
public partial class Player : Node2D
{
[Export]
public float Speed { get; set; } = 200.0f;
private Sprite2D _sprite;
public override void _Ready()
{
// Получаем дочерний Sprite2D
_sprite = GetNode<Sprite2D>("Sprite2D");
}
public override void _Process(double delta)
{
// Движение с клавиатуры
Vector2 direction = Input.GetVector("move_left", "move_right", "move_up", "move_down");
Position += direction * Speed * (float)delta;
// Поворот спрайта к курсору мыши
if (direction != Vector2.Zero)
{
Rotation = direction.Angle();
}
}
}
Sprite2D
2D Спрайт
public class MySprite : Sprite2D
Нода для отображения 2D текстур. Поддерживает анимацию через SpriteFrames.
public partial class AnimatedSprite : Sprite2D
{
[Export]
public SpriteFrames SpriteFrames { get; set; }
private string _currentAnimation = "idle";
public override void _Ready()
{
if (SpriteFrames != null)
{
PlayAnimation("idle");
}
}
public void PlayAnimation(string animationName)
{
if (SpriteFrames.HasAnimation(animationName) && _currentAnimation != animationName)
{
_currentAnimation = animationName;
Frame = 0;
// В Sprite2D анимация управляется вручную
// Для автоматической анимации используйте AnimatedSprite2D
}
}
public override void _Process(double delta)
{
// Ручное управление кадрами
if (SpriteFrames != null)
{
int frameCount = SpriteFrames.GetFrameCount(_currentAnimation);
Frame = (Frame + 1) % frameCount;
}
}
}
{
[Export]
public SpriteFrames SpriteFrames { get; set; }
private string _currentAnimation = "idle";
public override void _Ready()
{
if (SpriteFrames != null)
{
PlayAnimation("idle");
}
}
public void PlayAnimation(string animationName)
{
if (SpriteFrames.HasAnimation(animationName) && _currentAnimation != animationName)
{
_currentAnimation = animationName;
Frame = 0;
// В Sprite2D анимация управляется вручную
// Для автоматической анимации используйте AnimatedSprite2D
}
}
public override void _Process(double delta)
{
// Ручное управление кадрами
if (SpriteFrames != null)
{
int frameCount = SpriteFrames.GetFrameCount(_currentAnimation);
Frame = (Frame + 1) % frameCount;
}
}
}
Camera2D
Камера 2D
Камера для отображения определенной области 2D мира. Поддерживает сглаживание, ограничения и зум.
public partial class PlayerCamera : Camera2D
{
[Export]
public Node2D Target { get; set; }
[Export]
public float FollowSpeed { get; set; } = 5.0f;
[Export]
public Vector2 LimitsMin { get; set; } = new Vector2(-1000, -1000);
[Export]
public Vector2 LimitsMax { get; set; } = new Vector2(1000, 1000);
public override void _Ready()
{
// Установка ограничений камеры
LimitLeft = (int)LimitsMin.X;
LimitTop = (int)LimitsMin.Y;
LimitRight = (int)LimitsMax.X;
LimitBottom = (int)LimitsMax.Y;
}
public override void _Process(double delta)
{
if (Target != null)
{
// Плавное следование за целью
Vector2 targetPosition = Target.GlobalPosition;
GlobalPosition = GlobalPosition.Lerp(targetPosition, FollowSpeed * (float)delta);
}
}
// Метод для встряски камеры (screen shake)
public async Task Shake(float intensity, float duration)
{
Vector2 originalOffset = Offset;
float elapsed = 0;
while (elapsed < duration)
{
Vector2 shakeOffset = new Vector2(
GD.Randf() * 2 - 1,
GD.Randf() * 2 - 1
) * intensity * (1 - elapsed / duration);
Offset = originalOffset + shakeOffset;
elapsed += (float)GetProcessDeltaTime();
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}
Offset = originalOffset;
}
}
{
[Export]
public Node2D Target { get; set; }
[Export]
public float FollowSpeed { get; set; } = 5.0f;
[Export]
public Vector2 LimitsMin { get; set; } = new Vector2(-1000, -1000);
[Export]
public Vector2 LimitsMax { get; set; } = new Vector2(1000, 1000);
public override void _Ready()
{
// Установка ограничений камеры
LimitLeft = (int)LimitsMin.X;
LimitTop = (int)LimitsMin.Y;
LimitRight = (int)LimitsMax.X;
LimitBottom = (int)LimitsMax.Y;
}
public override void _Process(double delta)
{
if (Target != null)
{
// Плавное следование за целью
Vector2 targetPosition = Target.GlobalPosition;
GlobalPosition = GlobalPosition.Lerp(targetPosition, FollowSpeed * (float)delta);
}
}
// Метод для встряски камеры (screen shake)
public async Task Shake(float intensity, float duration)
{
Vector2 originalOffset = Offset;
float elapsed = 0;
while (elapsed < duration)
{
Vector2 shakeOffset = new Vector2(
GD.Randf() * 2 - 1,
GD.Randf() * 2 - 1
) * intensity * (1 - elapsed / duration);
Offset = originalOffset + shakeOffset;
elapsed += (float)GetProcessDeltaTime();
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}
Offset = originalOffset;
}
}
Движение и физика 2D
CharacterBody2D
Важно в 4.5.1
В Godot 4.5.1 KinematicBody2D заменен на CharacterBody2D. Это основной класс для управляемых персонажей с коллизиями.
public partial class Player : CharacterBody2D
{
[Export]
public float Speed { get; set; } = 300.0f;
[Export]
public float JumpVelocity { get; set; } = -400.0f;
// Получаем гравитацию из Project Settings
private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
public override void _PhysicsProcess(double delta)
{
Vector2 velocity = Velocity;
// Добавляем гравитацию если не на земле
if (!IsOnFloor())
velocity.Y += _gravity * (float)delta;
// Обработка прыжка
if (Input.IsActionJustPressed("jump") && IsOnFloor())
velocity.Y = JumpVelocity;
// Получение ввода движения
Vector2 direction = Input.GetVector("move_left", "move_right", "move_up", "move_down");
if (direction != Vector2.Zero)
{
velocity.X = direction.X * Speed;
}
else
{
// Постепенная остановка
velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
}
Velocity = velocity;
MoveAndSlide();
// Обработка скольжения по стенам (wall sliding)
if (IsOnWall() && !IsOnFloor() && velocity.Y > 0)
{
// Уменьшаем скорость падения при скольжении по стене
velocity.Y *= 0.7f;
}
}
}
{
[Export]
public float Speed { get; set; } = 300.0f;
[Export]
public float JumpVelocity { get; set; } = -400.0f;
// Получаем гравитацию из Project Settings
private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
public override void _PhysicsProcess(double delta)
{
Vector2 velocity = Velocity;
// Добавляем гравитацию если не на земле
if (!IsOnFloor())
velocity.Y += _gravity * (float)delta;
// Обработка прыжка
if (Input.IsActionJustPressed("jump") && IsOnFloor())
velocity.Y = JumpVelocity;
// Получение ввода движения
Vector2 direction = Input.GetVector("move_left", "move_right", "move_up", "move_down");
if (direction != Vector2.Zero)
{
velocity.X = direction.X * Speed;
}
else
{
// Постепенная остановка
velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
}
Velocity = velocity;
MoveAndSlide();
// Обработка скольжения по стенам (wall sliding)
if (IsOnWall() && !IsOnFloor() && velocity.Y > 0)
{
// Уменьшаем скорость падения при скольжении по стене
velocity.Y *= 0.7f;
}
}
}
RigidBody2D
Физическое тело
Тело с физикой, управляемое движком Godot. Подходит для объектов, которые должны реагировать на физические силы.
public partial class PhysicsObject : RigidBody2D
{
[Export]
public float ExplosionForce { get; set; } = 500.0f;
private bool _hasExploded = false;
public void ApplyExplosion(Vector2 explosionOrigin)
{
if (_hasExploded) return;
_hasExploded = true;
// Вычисляем направление взрыва
Vector2 direction = (GlobalPosition - explosionOrigin).Normalized();
float distance = GlobalPosition.DistanceTo(explosionOrigin);
// Уменьшаем силу взрыва с расстоянием
float force = ExplosionForce / (distance + 1);
// Применяем импульс
ApplyCentralImpulse(direction * force);
// Добавляем случайное вращение
ApplyTorqueImpulse(GD.Randf() * 1000 - 500);
// Запускаем таймер самоуничтожения
GetNode<Timer>("DestroyTimer").Start();
}
// Обработка столкновений
private void OnBodyEntered(Node body)
{
if (body is Player player)
{
player.TakeDamage(10);
}
}
}
{
[Export]
public float ExplosionForce { get; set; } = 500.0f;
private bool _hasExploded = false;
public void ApplyExplosion(Vector2 explosionOrigin)
{
if (_hasExploded) return;
_hasExploded = true;
// Вычисляем направление взрыва
Vector2 direction = (GlobalPosition - explosionOrigin).Normalized();
float distance = GlobalPosition.DistanceTo(explosionOrigin);
// Уменьшаем силу взрыва с расстоянием
float force = ExplosionForce / (distance + 1);
// Применяем импульс
ApplyCentralImpulse(direction * force);
// Добавляем случайное вращение
ApplyTorqueImpulse(GD.Randf() * 1000 - 500);
// Запускаем таймер самоуничтожения
GetNode<Timer>("DestroyTimer").Start();
}
// Обработка столкновений
private void OnBodyEntered(Node body)
{
if (body is Player player)
{
player.TakeDamage(10);
}
}
}
Коллизии и детекты
Area2D
Область детекции
Нода для создания зон детекции. Может обнаруживать вход/выход других Area2D или PhysicsBody2D.
public partial class DetectionZone : Area2D
{
[Signal]
public delegate void PlayerDetectedEventHandler(Node2D player);
[Signal]
public delegate void PlayerLostEventHandler();
private Player _detectedPlayer;
public override void _Ready()
{
// Подключаем сигналы
BodyEntered += OnBodyEntered;
BodyExited += OnBodyExited;
}
private void OnBodyEntered(Node2D body)
{
if (body is Player player)
{
_detectedPlayer = player;
EmitSignal(SignalName.PlayerDetected, player);
}
}
private void OnBodyExited(Node2D body)
{
if (body == _detectedPlayer)
{
_detectedPlayer = null;
EmitSignal(SignalName.PlayerLost);
}
}
public bool CanSeePlayer()
{
return _detectedPlayer != null;
}
public Vector2 GetPlayerPosition()
{
if (_detectedPlayer != null)
return _detectedPlayer.GlobalPosition;
return Vector2.Zero;
}
}
{
[Signal]
public delegate void PlayerDetectedEventHandler(Node2D player);
[Signal]
public delegate void PlayerLostEventHandler();
private Player _detectedPlayer;
public override void _Ready()
{
// Подключаем сигналы
BodyEntered += OnBodyEntered;
BodyExited += OnBodyExited;
}
private void OnBodyEntered(Node2D body)
{
if (body is Player player)
{
_detectedPlayer = player;
EmitSignal(SignalName.PlayerDetected, player);
}
}
private void OnBodyExited(Node2D body)
{
if (body == _detectedPlayer)
{
_detectedPlayer = null;
EmitSignal(SignalName.PlayerLost);
}
}
public bool CanSeePlayer()
{
return _detectedPlayer != null;
}
public Vector2 GetPlayerPosition()
{
if (_detectedPlayer != null)
return _detectedPlayer.GlobalPosition;
return Vector2.Zero;
}
}
RayCast2D
Луч для детекции
Используется для проверки линии прямой видимости или расстояния до объектов.
public partial class EnemySight : RayCast2D
{
[Export]
public float SightDistance { get; set; } = 300.0f;
[Export]
public Node2D Target { get; set; }
public override void _Ready()
{
TargetPosition = new Vector2(SightDistance, 0);
}
public override void _Process(double delta)
{
if (Target != null)
{
// Направляем луч к цели
Vector2 direction = (Target.GlobalPosition - GlobalPosition).Normalized();
TargetPosition = direction * SightDistance;
}
// Обновляем луч
ForceRaycastUpdate();
}
public bool CanSeeTarget()
{
if (!IsColliding()) return false;
var collider = GetCollider();
return collider == Target;
}
public Vector2 GetCollisionPoint()
{
if (IsColliding())
return GetCollisionPoint();
return GlobalPosition + TargetPosition;
}
}
{
[Export]
public float SightDistance { get; set; } = 300.0f;
[Export]
public Node2D Target { get; set; }
public override void _Ready()
{
TargetPosition = new Vector2(SightDistance, 0);
}
public override void _Process(double delta)
{
if (Target != null)
{
// Направляем луч к цели
Vector2 direction = (Target.GlobalPosition - GlobalPosition).Normalized();
TargetPosition = direction * SightDistance;
}
// Обновляем луч
ForceRaycastUpdate();
}
public bool CanSeeTarget()
{
if (!IsColliding()) return false;
var collider = GetCollider();
return collider == Target;
}
public Vector2 GetCollisionPoint()
{
if (IsColliding())
return GetCollisionPoint();
return GlobalPosition + TargetPosition;
}
}
Сигналы и события
Новое в 4.5.1
В Godot 4.5.1 улучшена работа с сигналами в C#. Сигналы теперь могут быть объявлены с использованием атрибута [Signal].
Объявление сигналов
C# 4.5.1
Правильное объявление и использование сигналов в Godot 4.5.1 C#.
public partial class GameEvents : Node
{
// Объявление сигналов с атрибутом [Signal]
[Signal]
public delegate void PlayerHealthChangedEventHandler(int currentHealth, int maxHealth);
[Signal]
public delegate void ScoreChangedEventHandler(int newScore);
[Signal]
public delegate void GameOverEventHandler(bool isWin);
private int _score = 0;
public void AddScore(int points)
{
_score += points;
EmitSignal(SignalName.ScoreChanged, _score);
}
public void OnPlayerHealthChanged(int current, int max)
{
EmitSignal(SignalName.PlayerHealthChanged, current, max);
}
}
{
// Объявление сигналов с атрибутом [Signal]
[Signal]
public delegate void PlayerHealthChangedEventHandler(int currentHealth, int maxHealth);
[Signal]
public delegate void ScoreChangedEventHandler(int newScore);
[Signal]
public delegate void GameOverEventHandler(bool isWin);
private int _score = 0;
public void AddScore(int points)
{
_score += points;
EmitSignal(SignalName.ScoreChanged, _score);
}
public void OnPlayerHealthChanged(int current, int max)
{
EmitSignal(SignalName.PlayerHealthChanged, current, max);
}
}
Подписка на сигналы
Методы подписки
Различные способы подписки на сигналы в Godot 4.5.1 C#.
public partial class UI : Control
{
[Export]
public GameEvents GameEvents { get; set; }
private Label _scoreLabel;
private ProgressBar _healthBar;
public override void _Ready()
{
_scoreLabel = GetNode<Label>("ScoreLabel");
_healthBar = GetNode<ProgressBar>("HealthBar");
if (GameEvents != null)
{
// Способ 1: Через Connect (старый способ)
GameEvents.Connect(
GameEvents.SignalName.ScoreChanged,
Callable.From<int>(OnScoreChanged)
);
// Способ 2: Через += (новый способ в 4.5.1)
GameEvents.PlayerHealthChanged += OnPlayerHealthChanged;
GameEvents.GameOver += OnGameOver;
}
}
private void OnScoreChanged(int newScore)
{
_scoreLabel.Text = $"Score: {newScore}";
}
private void OnPlayerHealthChanged(int current, int max)
{
_healthBar.MaxValue = max;
_healthBar.Value = current;
}
private void OnGameOver(bool isWin)
{
var gameOverScreen = GetNode<Control>("GameOverScreen");
gameOverScreen.Visible = true;
var message = GetNode<Label>("GameOverScreen/Message");
message.Text = isWin ? "You Win!" : "Game Over";
}
public override void _ExitTree()
{
// Важно отписаться от сигналов при уничтожении ноды
if (GameEvents != null)
{
GameEvents.PlayerHealthChanged -= OnPlayerHealthChanged;
GameEvents.GameOver -= OnGameOver;
}
}
}
{
[Export]
public GameEvents GameEvents { get; set; }
private Label _scoreLabel;
private ProgressBar _healthBar;
public override void _Ready()
{
_scoreLabel = GetNode<Label>("ScoreLabel");
_healthBar = GetNode<ProgressBar>("HealthBar");
if (GameEvents != null)
{
// Способ 1: Через Connect (старый способ)
GameEvents.Connect(
GameEvents.SignalName.ScoreChanged,
Callable.From<int>(OnScoreChanged)
);
// Способ 2: Через += (новый способ в 4.5.1)
GameEvents.PlayerHealthChanged += OnPlayerHealthChanged;
GameEvents.GameOver += OnGameOver;
}
}
private void OnScoreChanged(int newScore)
{
_scoreLabel.Text = $"Score: {newScore}";
}
private void OnPlayerHealthChanged(int current, int max)
{
_healthBar.MaxValue = max;
_healthBar.Value = current;
}
private void OnGameOver(bool isWin)
{
var gameOverScreen = GetNode<Control>("GameOverScreen");
gameOverScreen.Visible = true;
var message = GetNode<Label>("GameOverScreen/Message");
message.Text = isWin ? "You Win!" : "Game Over";
}
public override void _ExitTree()
{
// Важно отписаться от сигналов при уничтожении ноды
if (GameEvents != null)
{
GameEvents.PlayerHealthChanged -= OnPlayerHealthChanged;
GameEvents.GameOver -= OnGameOver;
}
}
}
Интерфейс 2D
Control ноды
UI система
Основные Control ноды для создания пользовательского интерфейса в 2D играх.
public partial class GameUI : Control
{
[Export]
public PackedScene PauseMenuScene { get; set; }
private Control _pauseMenu;
private Label _ammoLabel;
private TextureProgressBar _healthBar;
public override void _Ready()
{
_ammoLabel = GetNode<Label>("AmmoLabel");
_healthBar = GetNode<TextureProgressBar>("HealthBar");
// Создаем меню паузы
if (PauseMenuScene != null)
{
_pauseMenu = PauseMenuScene.Instantiate<Control>();
AddChild(_pauseMenu);
_pauseMenu.Visible = false;
}
// Обработка нажатия Escape
SetProcessInput(true);
}
public override void _Input(InputEvent event)
{
if (event.IsActionPressed("ui_cancel"))
{
TogglePauseMenu();
}
}
public void UpdateAmmo(int current, int max)
{
_ammoLabel.Text = $"{current}/{max}";
}
public void UpdateHealth(float current, float max)
{
_healthBar.MaxValue = max;
_healthBar.Value = current;
// Меняем цвет в зависимости от здоровья
float ratio = current / max;
if (ratio > 0.6)
_healthBar.TintProgress = Colors.Green;
else if (ratio > 0.3)
_healthBar.TintProgress = Colors.Yellow;
else
_healthBar.TintProgress = Colors.Red;
}
private void TogglePauseMenu()
{
if (_pauseMenu != null)
{
bool isVisible = !_pauseMenu.Visible;
_pauseMenu.Visible = isVisible;
GetTree().Paused = isVisible;
// Захват/освобождение мыши
if (isVisible)
Input.MouseMode = Input.MouseModeEnum.Visible;
else
Input.MouseMode = Input.MouseModeEnum.Captured;
}
}
}
{
[Export]
public PackedScene PauseMenuScene { get; set; }
private Control _pauseMenu;
private Label _ammoLabel;
private TextureProgressBar _healthBar;
public override void _Ready()
{
_ammoLabel = GetNode<Label>("AmmoLabel");
_healthBar = GetNode<TextureProgressBar>("HealthBar");
// Создаем меню паузы
if (PauseMenuScene != null)
{
_pauseMenu = PauseMenuScene.Instantiate<Control>();
AddChild(_pauseMenu);
_pauseMenu.Visible = false;
}
// Обработка нажатия Escape
SetProcessInput(true);
}
public override void _Input(InputEvent event)
{
if (event.IsActionPressed("ui_cancel"))
{
TogglePauseMenu();
}
}
public void UpdateAmmo(int current, int max)
{
_ammoLabel.Text = $"{current}/{max}";
}
public void UpdateHealth(float current, float max)
{
_healthBar.MaxValue = max;
_healthBar.Value = current;
// Меняем цвет в зависимости от здоровья
float ratio = current / max;
if (ratio > 0.6)
_healthBar.TintProgress = Colors.Green;
else if (ratio > 0.3)
_healthBar.TintProgress = Colors.Yellow;
else
_healthBar.TintProgress = Colors.Red;
}
private void TogglePauseMenu()
{
if (_pauseMenu != null)
{
bool isVisible = !_pauseMenu.Visible;
_pauseMenu.Visible = isVisible;
GetTree().Paused = isVisible;
// Захват/освобождение мыши
if (isVisible)
Input.MouseMode = Input.MouseModeEnum.Visible;
else
Input.MouseMode = Input.MouseModeEnum.Captured;
}
}
}
Ресурсы 2D
Загрузка ресурсов
ResourceLoader
Методы загрузки и управления ресурсами в Godot 4.5.1 C#.
public partial class ResourceManager : Node
{
// Кэш загруженных ресурсов
private Dictionary<string, Resource> _resourceCache = new();
// Предзагрузка ресурсов
public void PreloadResources()
{
string[] resourcesToPreload = new[]
{
"res://assets/player.png",
"res://scenes/enemy.tscn",
"res://audio/hit.wav"
};
foreach (var path in resourcesToPreload)
{
LoadResource<Resource>(path);
}
}
// Универсальный метод загрузки с кэшированием
public T LoadResource<T>(string path) where T : Resource
{
if (_resourceCache.TryGetValue(path, out var cachedResource))
{
return cachedResource as T;
}
try
{
var resource = GD.Load<T>(path);
if (resource != null)
{
_resourceCache[path] = resource;
return resource;
}
}
catch (Exception e)
{
GD.PrintErr($"Failed to load resource: {path}. Error: {e.Message}");
}
return null;
}
// Загрузка текстуры
public Texture2D LoadTexture(string path)
{
return LoadResource<Texture2D>(path);
}
// Загрузка сцены
public PackedScene LoadScene(string path)
{
return LoadResource<PackedScene>(path);
}
// Загрузка аудио
public AudioStream LoadAudio(string path)
{
return LoadResource<AudioStream>(path);
}
// Очистка кэша
public void ClearCache()
{
_resourceCache.Clear();
GC.Collect();
}
}
{
// Кэш загруженных ресурсов
private Dictionary<string, Resource> _resourceCache = new();
// Предзагрузка ресурсов
public void PreloadResources()
{
string[] resourcesToPreload = new[]
{
"res://assets/player.png",
"res://scenes/enemy.tscn",
"res://audio/hit.wav"
};
foreach (var path in resourcesToPreload)
{
LoadResource<Resource>(path);
}
}
// Универсальный метод загрузки с кэшированием
public T LoadResource<T>(string path) where T : Resource
{
if (_resourceCache.TryGetValue(path, out var cachedResource))
{
return cachedResource as T;
}
try
{
var resource = GD.Load<T>(path);
if (resource != null)
{
_resourceCache[path] = resource;
return resource;
}
}
catch (Exception e)
{
GD.PrintErr($"Failed to load resource: {path}. Error: {e.Message}");
}
return null;
}
// Загрузка текстуры
public Texture2D LoadTexture(string path)
{
return LoadResource<Texture2D>(path);
}
// Загрузка сцены
public PackedScene LoadScene(string path)
{
return LoadResource<PackedScene>(path);
}
// Загрузка аудио
public AudioStream LoadAudio(string path)
{
return LoadResource<AudioStream>(path);
}
// Очистка кэша
public void ClearCache()
{
_resourceCache.Clear();
GC.Collect();
}
}
Оптимизация 2D игр
Оптимизация производительности
Godot 4.5.1
Ключевые техники оптимизации для 2D игр в Godot 4.5.1.
public partial class PerformanceOptimizer : Node
{
// Использование VisibleOnScreenNotifier2D
public void SetupVisibilityOptimization(Node2D target)
{
var notifier = new VisibleOnScreenNotifier2D();
target.AddChild(notifier);
// Отключаем обработку когда объект не виден
notifier.ScreenEntered += () => target.SetProcess(true);
notifier.ScreenExited += () => target.SetProcess(false);
}
// Оптимизация пула объектов
public class ObjectPool<T> where T : Node2D, new()
{
private Queue<T> _pool = new Queue<T>();
private int _maxSize;
public ObjectPool(int initialSize, int maxSize)
{
_maxSize = maxSize;
for (int i = 0; i < initialSize; i++)
{
_pool.Enqueue(new T());
}
}
public T GetObject()
{
if (_pool.Count > 0)
return _pool.Dequeue();
if (_pool.Count < _maxSize)
return new T();
return null;
}
public void ReturnObject(T obj)
{
if (_pool.Count < _maxSize)
{
obj.Visible = false;
obj.SetProcess(false);
_pool.Enqueue(obj);
}
else
{
obj.QueueFree();
}
}
}
// Оптимизация через Level of Detail (LOD)
public void SetupLOD(Node2D highDetail, Node2D lowDetail, float distanceThreshold)
{
var camera = GetViewport().GetCamera2D();
if (camera == null) return;
float distance = highDetail.GlobalPosition.DistanceTo(camera.GlobalPosition);
if (distance > distanceThreshold)
{
highDetail.Visible = false;
lowDetail.Visible = true;
}
else
{
highDetail.Visible = true;
lowDetail.Visible = false;
}
}
}
{
// Использование VisibleOnScreenNotifier2D
public void SetupVisibilityOptimization(Node2D target)
{
var notifier = new VisibleOnScreenNotifier2D();
target.AddChild(notifier);
// Отключаем обработку когда объект не виден
notifier.ScreenEntered += () => target.SetProcess(true);
notifier.ScreenExited += () => target.SetProcess(false);
}
// Оптимизация пула объектов
public class ObjectPool<T> where T : Node2D, new()
{
private Queue<T> _pool = new Queue<T>();
private int _maxSize;
public ObjectPool(int initialSize, int maxSize)
{
_maxSize = maxSize;
for (int i = 0; i < initialSize; i++)
{
_pool.Enqueue(new T());
}
}
public T GetObject()
{
if (_pool.Count > 0)
return _pool.Dequeue();
if (_pool.Count < _maxSize)
return new T();
return null;
}
public void ReturnObject(T obj)
{
if (_pool.Count < _maxSize)
{
obj.Visible = false;
obj.SetProcess(false);
_pool.Enqueue(obj);
}
else
{
obj.QueueFree();
}
}
}
// Оптимизация через Level of Detail (LOD)
public void SetupLOD(Node2D highDetail, Node2D lowDetail, float distanceThreshold)
{
var camera = GetViewport().GetCamera2D();
if (camera == null) return;
float distance = highDetail.GlobalPosition.DistanceTo(camera.GlobalPosition);
if (distance > distanceThreshold)
{
highDetail.Visible = false;
lowDetail.Visible = true;
}
else
{
highDetail.Visible = true;
lowDetail.Visible = false;
}
}
}