217 lines
7.5 KiB
Markdown
217 lines
7.5 KiB
Markdown
# Задача: Unity / C# Middle
|
||
|
||
## Кратко
|
||
|
||
Нужно реализовать загрузочный поток приложения в Unity через собственную state machine.
|
||
|
||
Проект создается с нуля. Базовые абстракции, сервисы, View/ViewModel и state machine нужно написать самостоятельно, потому что это часть оценки.
|
||
|
||
Ориентировочное время выполнения: около 1,5 часов. Если ушло больше или меньше времени, это нормально, но нужно честно указать это в документации.
|
||
|
||
## Технические требования
|
||
|
||
- Unity 2022 LTS+.
|
||
- C#.
|
||
- Разрешено использовать VContainer, UniTask, UniRx/R3 или собственную реактивную реализацию.
|
||
- Опционально можно использовать Odin Inspector и DOTween.
|
||
|
||
## Архитектурные требования
|
||
|
||
### Reactive
|
||
|
||
Можно использовать UniRx/R3 или собственный `ReactiveValue<T>`.
|
||
|
||
Если используется собственная реализация, ожидаемая сигнатура подписки:
|
||
|
||
```csharp
|
||
IDisposable Subscribe(Action<T> cb, bool invokeImmediately = true)
|
||
```
|
||
|
||
Требования:
|
||
|
||
- каждая подписка должна корректно диспозиться;
|
||
- нельзя использовать голые `event Action` без отписки.
|
||
|
||
### Async
|
||
|
||
Все async-операции должны быть реализованы только через UniTask и `CancellationToken`.
|
||
|
||
Запрещено использовать `async void`, кроме Unity-колбэков.
|
||
|
||
### Dependency Injection
|
||
|
||
Использовать VContainer.
|
||
|
||
Все сервисы регистрируются как интерфейсы:
|
||
|
||
```csharp
|
||
builder.Register<Impl>(Lifetime.Singleton).As<IInterface>();
|
||
```
|
||
|
||
Запрещено использовать:
|
||
|
||
- `FindObjectOfType`;
|
||
- `Singleton.Instance`;
|
||
- `static`-хранилища состояния.
|
||
|
||
### UI
|
||
|
||
Нужно сделать базовую пару View/ViewModel:
|
||
|
||
```csharp
|
||
public class UIView : MonoBehaviour
|
||
{
|
||
public virtual void Initialize() { }
|
||
public virtual void Release() { }
|
||
}
|
||
|
||
public class UIView<TVm> : UIView where TVm : IUIViewModel
|
||
{
|
||
}
|
||
```
|
||
|
||
Требования:
|
||
|
||
- ViewModel должна быть обычным C#-классом, не `MonoBehaviour`;
|
||
- логика находится во ViewModel;
|
||
- View хранит только Unity-ссылки и биндинги.
|
||
|
||
### Services
|
||
|
||
Сервисы должны иметь асинхронный жизненный цикл:
|
||
|
||
```csharp
|
||
UniTask InitializeAsync(CancellationToken ct)
|
||
UniTask ReleaseAsync(CancellationToken ct)
|
||
```
|
||
|
||
Требования:
|
||
|
||
- сервисы оформляются как `Service : IService`;
|
||
- фоновые циклы стартуют в `InitializeAsync`;
|
||
- фоновые циклы останавливаются через `CancellationTokenSource.Cancel()` в `ReleaseAsync`.
|
||
|
||
### Config
|
||
|
||
Конфиги должны быть `ScriptableObject` с суффиксом `*Settings`.
|
||
|
||
Регистрация в `LifetimeScope`:
|
||
|
||
```csharp
|
||
[SerializeField] private SomeSettings _settings;
|
||
|
||
protected override void Configure(IContainerBuilder builder)
|
||
{
|
||
builder.RegisterInstance(_settings);
|
||
}
|
||
```
|
||
|
||
## Задача: Boot Flow
|
||
|
||
Реализовать загрузочный поток приложения через собственную state machine из трех состояний.
|
||
|
||
### 1. База state machine
|
||
|
||
Нужно реализовать собственные абстракции:
|
||
|
||
- `IState`;
|
||
- `IStatesController<TEnum>`;
|
||
- `StatesController<TEnum>`.
|
||
|
||
В `StatesController<TEnum>` должен быть метод:
|
||
|
||
```csharp
|
||
UniTask EnterStateAsync(TEnum code, CancellationToken ct)
|
||
```
|
||
|
||
Контракт перехода между состояниями:
|
||
|
||
```csharp
|
||
await currentState.ExitAsync(ct);
|
||
await newState.EnterAsync(ct);
|
||
```
|
||
|
||
Требования:
|
||
|
||
- `CancellationToken` пробрасывается через всю цепочку вызовов;
|
||
- `CancellationToken` должен реально отменять внутренние ожидания;
|
||
- повторные входы в состояния не должны ломать UI и подписки.
|
||
|
||
### 2. Состояния
|
||
|
||
Нужно реализовать три состояния.
|
||
|
||
#### SplashState
|
||
|
||
Поведение:
|
||
|
||
- показывает лого;
|
||
- ждет 1 секунду через `UniTask.Delay(..., ct)`;
|
||
- переходит в следующее состояние.
|
||
|
||
#### LoadState
|
||
|
||
Поведение:
|
||
|
||
- имитирует загрузку;
|
||
- выполняет 5 шагов по 200 мс;
|
||
- хранит `ReactiveValue<float> Progress` со значением от `0` до `1`;
|
||
- обновляет `Progress` после каждого шага.
|
||
|
||
#### MenuState
|
||
|
||
Поведение:
|
||
|
||
- показывает `MenuUIView`;
|
||
- содержит одну кнопку `Restart`;
|
||
- по клику на `Restart` возвращает приложение в `LoadState`.
|
||
|
||
### 3. UI
|
||
|
||
Нужно реализовать `LoadingUIView`.
|
||
|
||
Требования:
|
||
|
||
- `LoadingUIView` подписывается на `LoadState.Progress`;
|
||
- прогресс-бар обновляется через DOTween или ручной Lerp;
|
||
- при `ExitAsync` все подписки должны корректно очищаться;
|
||
- при повторном входе в `LoadState` не должно быть `NullReferenceException` и дублирующихся подписок.
|
||
|
||
## Что сдать
|
||
|
||
- GitHub-репозиторий или zip-архив без `Library/`, `Temp/`, `obj/`.
|
||
- `README.md` с инструкцией запуска.
|
||
- В `README.md` добавить 5-10 строк о том, что было бы доделано при наличии еще 2 часов.
|
||
- `SELF_NOTES.md` с описанием собственных решений.
|
||
- `AI_LOG.md` с описанием использования AI. Если AI не использовался, нужно написать почему.
|
||
|
||
## SELF_NOTES.md
|
||
|
||
В `SELF_NOTES.md` нужно своими словами описать:
|
||
|
||
- какие идеи были рассмотрены и почему выбрана текущая реализация;
|
||
- какие места в коде были придуманы и написаны самостоятельно, без AI;
|
||
- что в коде понятно до последней строки;
|
||
- что осталось непонятным или воспринимается как магия;
|
||
- ответы на 2-3 ключевых вопроса вида: почему здесь сделано именно так.
|
||
|
||
## AI_LOG.md
|
||
|
||
В `AI_LOG.md` нужно описать:
|
||
|
||
- какие промпты использовались;
|
||
- где AI ошибся;
|
||
- что было переписано руками;
|
||
- если AI не использовался, почему было принято такое решение.
|
||
|
||
## Что не требуется
|
||
|
||
Не нужно тратить время на:
|
||
|
||
- красивый арт;
|
||
- сложные анимации;
|
||
- звук;
|
||
- мобильную сборку.
|
||
|
||
Главное: архитектурные слои, стиль кода, корректная работа жизненного цикла и понимание собственных решений.
|