Введение
Этот гайд охватывает расширенное использование спрайтов, включая создание пользовательских спрайтов для систем, модификацию существующих спрайтов и работу с большими спрайтами. Он строится на основах базовой спрайтовой системы, описанной в `ss14-sprite-system.html`.
Создание пользовательских спрайтов для систем
1. ContentSpriteSystem - Инструмент для экспорта спрайтов
ContentSpriteSystem предоставляет инструмент для администраторов для экспорта спрайтов во всех 4 направлениях:
Использование инструмента для экспорта спрайтов
- Включите отладочные глаголы - используйте команду `/adminverbs`
- Щелкните правой кнопкой мыши любую сущность в игровом мире
- Выберите "Export Entity" из меню отладки
- Спрайты будут экспортированы в директорию `/Exports` в формате: `
- - .png`
Настройка поведения экспорта
// Content.Client/Sprite/ContentSpriteSystem.cs (упрощённый вариант)
public sealed class ContentSpriteSystem : EntitySystem
{
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
public static readonly ResPath Exports = new ResPath("/Exports");
public override void Initialize()
{
base.Initialize();
_resManager.UserData.CreateDir(Exports);
_ui.RootControl.AddChild(new ContentSpriteControl());
SubscribeLocalEvent>(GetVerbs);
}
public async Task Export(EntityUid entity, bool includeId = true, CancellationToken cancelToken = default)
{
var tasks = new Task[4];
var i = 0;
foreach (var dir in new Direction[] { Direction.South, Direction.East, Direction.North, Direction.West })
{
tasks[i++] = Export(entity, dir, includeId: includeId, cancelToken);
}
await Task.WhenAll(tasks);
}
private void GetVerbs(GetVerbsEvent ev)
{
if (!_adminManager.IsAdmin())
return;
var verb = new Verb
{
Text = Loc.GetString("export-entity-verb-get-data-text"),
Category = VerbCategory.Debug,
Act = async () => await Export(ev.Target)
};
ev.Verbs.Add(verb);
}
}
2. Пользовательские спрайтовые системы в Content.Client
Пример 1: Использование RandomSpriteComponent
Движок SS14 предоставляет встроенный `RandomSpriteComponent`, который позволяет использовать случайные вариации спрайтов:
# Resources/Prototypes/Entities/MyEntity.yml
- type: entity
name: My Random Entity
parent: BaseItem
id: MyRandomEntity
components:
- type: Sprite
sprite: Objects/MyCategory/my_sprite.rsi
state: default
- type: RandomSpriteComponent
getAllGroups: false # Если true, использует все доступные группы, а не одну случайную
available:
# Группа 1: Красная вариация
- base:
state: red_base
detail:
state: red_detail
color: "#ff0000"
# Группа 2: Синяя вариация
- base:
state: blue_base
detail:
state: blue_detail
color: "#0000ff"
# Группа 3: Зелёная вариация
- base:
state: green_base
detail:
state: green_detail
color: "#00ff00"
Пример 2: Пользовательский спрайтовый компонент для мини-игры
// Content.Client/MyMinigame/Components/MyMinigameSpriteComponent.cs
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Client.MyMinigame.Components;
[RegisterComponent]
public sealed partial class MyMinigameSpriteComponent : Component
{
[DataField("sprite")]
public SpriteSpecifier Sprite { get; set; } = SpriteSpecifier.Invalid;
[DataField("animationSpeed")]
public float AnimationSpeed { get; set; } = 0.1f;
[DataField("maxFrames")]
public int MaxFrames { get; set; } = 8;
[DataField("currentFrame")]
public int CurrentFrame { get; set; } = 0;
}
Система для контроля спрайта
// Content.Client/MyMinigame/Systems/MyMinigameSpriteSystem.cs
using Content.Client.MyMinigame.Components;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
namespace Content.Client.MyMinigame.Systems;
public sealed class MyMinigameSpriteSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
private float _timeSinceLastFrame;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnComponentInit);
}
private void OnComponentInit(EntityUid uid, MyMinigameSpriteComponent component, ComponentInit args)
{
if (component.Sprite is not SpriteSpecifier.Rsi rsi)
return;
// Устанавливаем начальное состояние спрайта
if (TryComp(uid, out var sprite))
{
_sprite.LayerSetSprite((uid, sprite), 0, component.Sprite);
_sprite.LayerSetRsiState((uid, sprite), 0, $"frame{component.CurrentFrame}");
}
}
public override void FrameUpdate(float frameTime)
{
_timeSinceLastFrame += frameTime;
// Обновляем все спрайты мини-игры
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var comp, out var sprite))
{
if (_timeSinceLastFrame >= comp.AnimationSpeed)
{
comp.CurrentFrame = (comp.CurrentFrame + 1) % comp.MaxFrames;
_sprite.LayerSetRsiState((uid, sprite), 0, $"frame{comp.CurrentFrame}");
_timeSinceLastFrame = 0;
}
}
}
public void SetFrame(EntityUid uid, int frame, MyMinigameSpriteComponent? component = null, SpriteComponent? sprite = null)
{
if (!Resolve(uid, ref component, ref sprite))
return;
if (frame < 0 || frame >= component.MaxFrames)
return;
component.CurrentFrame = frame;
_sprite.LayerSetRsiState((uid, sprite), 0, $"frame{frame}");
}
}
Конфигурация прототипа
# Resources/Prototypes/Entities/MyMinigame/my_minigame_entity.yml
- type: entity
name: My Minigame Entity
parent: BaseItem
id: MyMinigameEntity
components:
- type: Sprite
sprite: Objects/MyMinigame/my_minigame.rsi
state: frame0
- type: MyMinigameSpriteComponent
sprite: Objects/MyMinigame/my_minigame.rsi
animationSpeed: 0.2
maxFrames: 16
3. Система затемнения спрайтов
Движок SS14 предоставляет встроенную систему затемнения спрайтов, которая автоматически затемняет сущности, когда игрок за ними:
Использование SpriteFadeComponent
# Resources/Prototypes/Entities/MyFadingEntity.yml
- type: entity
name: My Fading Entity
parent: BaseItem
id: MyFadingEntity
components:
- type: Sprite
sprite: Objects/MyCategory/my_entity.rsi
state: icon
- type: SpriteFadeComponent # Этот компонент включает затемнение
Настройка логики затемнения
// Content.Client/MySystem/Components/MyCustomFadeComponent.cs
using Content.Shared.Sprite;
using Robust.Shared.GameObjects;
namespace Content.Client.MySystem.Components;
[RegisterComponent]
public sealed partial class MyCustomFadeComponent : Component
{
[DataField("fadeDistance")]
public float FadeDistance { get; set; } = 5.0f;
[DataField("targetAlpha")]
public float TargetAlpha { get; set; } = 0.3f;
[DataField("fadeSpeed")]
public float FadeSpeed { get; set; } = 2.0f;
}
Пользовательская система затемнения
// Content.Client/MySystem/Systems/MyCustomFadeSystem.cs
using Content.Client.MySystem.Components;
using Content.Shared.Sprite;
using Robust.Client.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
namespace Content.Client.MySystem.Systems;
public sealed class MyCustomFadeSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnComponentShutdown);
}
private void OnComponentInit(EntityUid uid, MyCustomFadeComponent component, ComponentInit args)
{
// Добавляем FadingSpriteComponent для отслеживания
if (!HasComp(uid))
{
var fadeComp = AddComp(uid);
if (TryComp(uid, out var sprite))
{
fadeComp.OriginalAlpha = sprite.Color.A;
}
}
}
private void OnComponentShutdown(EntityUid uid, MyCustomFadeComponent component, ComponentShutdown args)
{
// Восстанавливаем исходную прозрачность
if (TryComp(uid, out var fade) &&
TryComp(uid, out var sprite))
{
_sprite.SetColor((uid, sprite), sprite.Color.WithAlpha(fade.OriginalAlpha));
RemComp(uid);
}
}
public override void FrameUpdate(float frameTime)
{
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var comp, out var sprite))
{
// Пользовательская логика затемнения здесь
// Пример: Затемнение на основе расстояния от локального игрока
var distance = GetDistanceFromPlayer(uid);
var alpha = CalculateFadeAlpha(distance, comp);
_sprite.SetColor((uid, sprite), sprite.Color.WithAlpha(alpha));
}
}
private float GetDistanceFromPlayer(EntityUid uid)
{
// Реализация расчета расстояния от локального игрока
return 0;
}
private float CalculateFadeAlpha(float distance, MyCustomFadeComponent comp)
{
if (distance <= 0) return comp.TargetAlpha;
if (distance >= comp.FadeDistance) return 1.0f;
var normalized = distance / comp.FadeDistance;
return MathHelper.Lerp(comp.TargetAlpha, 1.0f, normalized);
}
}
4. Управление масштабом спрайтов
Движок SS14 предоставляет управление масштабом через `ScaleVisualsComponent` и `SharedScaleVisualsSystem`:
Базовая конфигурация масштаба
# Resources/Prototypes/Entities/MyScaledEntity.yml
- type: entity
name: My Scaled Entity
parent: BaseItem
id: MyScaledEntity
components:
- type: Sprite
sprite: Objects/MyCategory/my_entity.rsi
state: icon
- type: ScaleVisualsComponent
scale: 1.5 # 150% масштаб
Серверный контроль масштаба
// Content.Shared/MySystem/Systems/SharedMySystem.cs
using Content.Shared.Sprite;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Shared.MySystem.Systems;
public abstract class SharedMySystem : EntitySystem
{
[Dependency] private readonly SharedScaleVisualsSystem _scaleSystem = default!;
public void SetEntityScale(EntityUid uid, Vector2 scale)
{
_scaleSystem.SetSpriteScale(uid, scale);
}
public Vector2 GetEntityScale(EntityUid uid)
{
return _scaleSystem.GetSpriteScale(uid);
}
}
Клиентская обработка масштаба
// Content.Client/MySystem/Systems/MySystem.cs
using Content.Shared.MySystem.Systems;
using Content.Shared.Sprite;
using Robust.Client.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.MySystem.Systems;
public sealed class MySystem : SharedMySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnScaleEntity);
}
private void OnScaleEntity(ScaleEntityEvent ev)
{
if (TryComp(ev.Uid, out var sprite))
{
// Опционально: Пользовательская клиентская обработка масштаба
_sprite.SetScale((ev.Uid, sprite), ev.Scale * 1.2f); // 20% больше, чем указано на сервере
}
}
public void ToggleScale(EntityUid uid)
{
var currentScale = GetEntityScale(uid);
var newScale = currentScale.X == 1.0f ? new Vector2(1.5f, 1.5f) : Vector2.One;
SetEntityScale(uid, newScale);
}
}
5. Создание больших спрайтов
Структура RSI для больших спрайтов
Для больших спрайтов (например, транспортных средств, структур) используйте большие размеры:
// Resources/Textures/Objects/Vehicles/MyTank.rsi/meta.json
{
"version": 1,
"license": "CC-BY-SA-3.0",
"size": { "x": 64, "y": 64 },
"states": [
{ "name": "idle", "directions": 4 },
{ "name": "moving", "directions": 4, "delays": [[0.1, 0.1]] },
{ "name": "shooting", "directions": 4, "delays": [[0.05, 0.1, 0.15]] }
]
}
Обработка больших спрайтов в коде
// Content.Client/Vehicles/Components/VehicleSpriteComponent.cs
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Client.Vehicles.Components;
[RegisterComponent]
public sealed partial class VehicleSpriteComponent : Component
{
[DataField("baseSprite")]
public SpriteSpecifier BaseSprite { get; set; } = SpriteSpecifier.Invalid;
[DataField("turretSprite")]
public SpriteSpecifier TurretSprite { get; set; } = SpriteSpecifier.Invalid;
[DataField("turretAngle")]
public float TurretAngle { get; set; } = 0f;
public int TurretLayer { get; set; } = -1;
}
// Content.Client/Vehicles/Systems/VehicleSpriteSystem.cs
using Content.Client.Vehicles.Components;
using Robust.Client.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.Vehicles.Systems;
public sealed class VehicleSpriteSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnComponentShutdown);
}
private void OnComponentInit(EntityUid uid, VehicleSpriteComponent component, ComponentInit args)
{
if (!TryComp(uid, out var sprite))
return;
// Устанавливаем основную спрайт транспортного средства
if (component.BaseSprite is SpriteSpecifier.Rsi baseRsi)
{
_sprite.LayerSetSprite((uid, sprite), 0, component.BaseSprite);
_sprite.LayerSetRsiState((uid, sprite), 0, "idle");
}
// Устанавливаем спрайт турели
if (component.TurretSprite is SpriteSpecifier.Rsi turretRsi)
{
component.TurretLayer = _sprite.LayerAdd((uid, sprite));
_sprite.LayerSetSprite((uid, sprite), component.TurretLayer, component.TurretSprite);
_sprite.LayerSetRsiState((uid, sprite), component.TurretLayer, "turret");
_sprite.LayerSetRotation((uid, sprite), component.TurretLayer, Angle.FromDegrees(component.TurretAngle));
}
}
private void OnComponentShutdown(EntityUid uid, VehicleSpriteComponent component, ComponentShutdown args)
{
// Очищаем ресурсы при выключении компонента
}
public void SetTurretAngle(EntityUid uid, float angle, VehicleSpriteComponent? component = null, SpriteComponent? sprite = null)
{
if (!Resolve(uid, ref component, ref sprite))
return;
if (component.TurretLayer == -1)
return;
component.TurretAngle = angle;
_sprite.LayerSetRotation((uid, sprite), component.TurretLayer, Angle.FromDegrees(angle));
}
}
Советы по оптимизации спрайтов
Лучшие практики
- Используйте спрайты одинакового размера для упрощения работы с атласами
- Избегайте слишком мелких или слишком больших спрайтов
- Оптимизируйте количество состояний спрайтов
- Используйте сжатие PNG для уменьшения размера файлов
- Избегайте прозрачности там, где она не нужна
- Используйте спрайтовые атласы для группировки связанных спрайтов
Заключение
Расширенное использование спрайтов в Space Station 14 открывает широкие возможности для создания уникальных визуальных эффектов и высококачественного контента. Понимание инструментов экспорта, работы с большими спрайтами и динамической модификации спрайтов помогает разработчикам создавать более интересные и привлекательные игры.