Denis53513 часов назад
Фреймворк GameFramework.Pro (.Net)
Уровень сложностиПростойВремя на прочтение7 минОхват и читатели6.9K.NET*Unity*C#*Разработка игр*ТуториалВ этой статье я хочу представить вам мой фреймворк, реализующий идеи чистой архитектуры адаптированные для игровых проектов. Данный фреймворк определяет основные слои вашего проекта, сущности и сервисы, а так же содержит минимальный набор утилит.
Обзор
Для начала давайте рассмотрим из чего состоит этот фреймворк.
System
• Check - набор проверок для аргументов и операций.
• DisposableBase - освобождаемый объект. Содержит некоторый дополнительный функционал.
• IDependencyProvider - локатор служб. Предоставляет запрашиваемые объекты и значения.
GameFramework.Pro
• Main - корень проекта.
• ProgramBase - главная сущность проекта. Создает другие сущности и предоставляет запрашиваемые зависимости.
• UI - аудио-графический пользовательский интерфейс.
• ThemeBase - сущность аудио темы. Проигрывает музыкальные плейлисты.
• PlayListBase
• ScreenBase - сущность графического экрана. Показывает дерево виджетов.
• WidgetBase
• ViewableWidgetBase
• RouterBase - сервис менеджера состояния. Предоставляет методы для загрузки главного меню, загрузки/перезагрузки/выгрузки игры, а так же выхода из приложения.
• App - модель приложения.
• ApplicationBase - сущность приложения. Выполняет инициализацию, запуск главного цикла, запуск игры, а так же предоставляет хранилище и подобное.
• Game - модель домена / бизнеса (в нашем случае самой игры).
• GameBase - сущность игры. Содержит информацию об игре, игровые правила, состояние и сущности игроков.
• PlayerBase - сущность игрока. Содержит информацию об игроке и состояние. Так же может обрабатывать ввод.
• WorldBase - сущность мира. Содержит все остальные сущности.
• EntityBase - любой значимый для игры объект.
Детальный обзор
Теперь давайте рассмотрим некоторые компоненты этого фреймворка более детально.
Program
// Программа содержит другие сущности и сервисы,
// а так же реализует паттерн локатор служб.
// Заметьте, что локатор служб считается анти-паттерном,
// но другие решения приводят к оверкодингу.
public abstract class ProgramBase : DisposableBase {
public ProgramBase(); private protected override void OnDisposeInternal();
}// Каждая сущность и сервис разделены на две класса: базовый и расширенный.
// Все расширенные классы добавляют поддержку IDependencyProvider.
public abstract class ProgramBase2<TTheme, TScreen, TRouter, TApplication> : ProgramBase, IDependencyProvider
where TTheme : ThemeBase
where TScreen : ScreenBase
where TRouter : RouterBase
where TApplication : ApplicationBase {
protected TTheme Theme { get; init; }
protected TScreen Screen { get; init; }
protected TRouter Router { get; init; }
protected TApplication Application { get; init; }
public ProgramBase2(); private protected override void OnDisposeInternal();
object? IDependencyProvider.GetValue(Type type, object? argument);
}
Theme
// Тема содержит плейлист,
// который проигрывает текущий список аудио треков.
// Заметьте, что я использовал машину состояний,
// которая позволляет вам легко управлять текущим плейлистом.
public abstract class ThemeBase : DisposableBase {
protected StateMachine Machine { get; }
public ThemeBase(); private protected override void OnDisposeInternal();
}
// Заметьте, что вместо наследования,
// я использовал двухсторонюю связь между плейлистом и состоянием.
public abstract class PlayListBase {
public sealed class State2 : State {
public PlayListBase PlayList { get; }
public State2(PlayListBase playList); protected override void OnDispose();
protected override void OnActivate(object? argument);
protected override void OnDeactivate(object? argument);
}
public State2 State { get; }
public PlayListBase();
protected internal abstract void OnDispose();
private protected virtual void OnDisposeInternal();
protected internal abstract void OnActivate(object? argument);
protected internal abstract void OnDeactivate(object? argument);
}public abstract class ThemeBase2<TRouter, TApplication> : ThemeBase
where TRouter : RouterBase
where TApplication : ApplicationBase {
protected IDependencyProvider Provider { get; }
protected TRouter Router { get; }
protected TApplication Application { get; }
public ThemeBase2(); private protected override void OnDisposeInternal();
} public abstract class PlayListBase2 : PlayListBase {
protected IDependencyProvider Provider { get; }
public PlayListBase2(); private protected override void OnDisposeInternal();
}
Screen
// Экран содержит дерево виджетов,
// которые рисуют текущий пользовательский интерфейс.
// Заметьте, что я использовал машину дерева,
// которая позволляет вам легко управлять иерархией виджетов.
public abstract class ScreenBase : DisposableBase {
protected TreeMachine Machine { get; }
public ScreenBase(); private protected override void OnDisposeInternal();
}
// Заметьте, что вместо наследования,
// я использовал двухсторонюю связь между виджетом и нодой.
public abstract class WidgetBase {
public sealed class Node2 : Node {
public WidgetBase Widget { get; }
public Node2(WidgetBase widget); protected override void OnDispose();
protected override void OnActivate(object? argument);
protected override void OnDeactivate(object? argument);
protected override void Sort(List<INode> children);
}
public Node2 Node { get; }
public WidgetBase();
protected internal abstract void OnDispose();
private protected virtual void OnDisposeInternal();
protected internal abstract void OnActivate(object? argument);
protected internal abstract void OnDeactivate(object? argument);
protected internal virtual void OnBeforeDescendantActivate(INode descendant, object? argument);
protected internal virtual void OnAfterDescendantActivate(INode descendant, object? argument);
protected internal virtual void OnBeforeDescendantDeactivate(INode descendant, object? argument);
protected internal virtual void OnAfterDescendantDeactivate(INode descendant, object? argument);
protected internal virtual void Sort(List<INode> children);
} public abstract class ViewableWidgetBase : WidgetBase {
public object View { get; protected init; }
internal ViewableWidgetBase(); private protected override void OnDisposeInternal();
} public abstract class ViewableWidgetBase<TView> : ViewableWidgetBase where TView : notnull {
protected new TView View { get; init; }
public ViewableWidgetBase(); private protected override void OnDisposeInternal();
}public abstract class ScreenBase2<TRouter, TApplication> : ScreenBase
where TRouter : RouterBase
where TApplication : ApplicationBase {
protected IDependencyProvider Provider { get; }
protected TRouter Router { get; }
protected TApplication Application { get; }
public ScreenBase2(); private protected override void OnDisposeInternal();
} public abstract class WidgetBase2 : WidgetBase {
protected IDependencyProvider Provider { get;}
public WidgetBase2(); private protected override void OnDisposeInternal();
}
public abstract class ViewableWidgetBase2<TView> : ViewableWidgetBase<TView>
where TView : notnull {
protected IDependencyProvider Provider { get;}
public ViewableWidgetBase2(); private protected override void OnDisposeInternal();
}
Router
// Роутер предоставляет ссылки на другие сущности.
public abstract class RouterBase : DisposableBase {
public RouterBase(); private protected override void OnDisposeInternal();
}
public abstract class RouterBase2<TTheme, TScreen, TApplication> : RouterBase
where TTheme : ThemeBase
where TScreen : ScreenBase
where TApplication : ApplicationBase {
protected IDependencyProvider Provider { get; }
protected TTheme Theme { get; }
protected TScreen Screen { get; }
protected TApplication Application { get; }
public RouterBase2(); private protected override void OnDisposeInternal();
}
Application
// Приложение предоставляет лишь провайдер зависимостей.
// Остальные сущности являются такими же простыми,
// поэтому я не буду продолжать описывать их.
public abstract class ApplicationBase : DisposableBase {
public ApplicationBase(); private protected override void OnDisposeInternal();
} public abstract class ApplicationBase2 : ApplicationBase {
protected IDependencyProvider Provider { get; }
public ApplicationBase2(); private protected override void OnDisposeInternal();
}
Дополнение
Все фреймворки определяют глобальную архитектуру проекта. А дизайн отдельных компонентов проекта, в лучшем случае, определяется соглашениями о стиле. Но одних стилей - недостаточно. Чтобы грамотно проектировать классы, вы должны понимать какие бывают классы и из чего они состоят. К сожалению, я не смог найти хоть какую-то информацию на эту тему. А классифицировать такие мелочи оказалось крайне сложно. Но в итоге, я все-таки смог прийти к интересным идеям, о которых хочу далее рассказать.
Я могу предложить следующую классификацию классов:
• Сущность - класс названный существительным, который, в идеале, только обрабатывает события и не предоставляет API.
• Сервис - класс названный отглагольным существительным, который, в идеале, только предоставляет API для других сущностей.
• Информация (Info, Desc) - структура, описывающая существующий объект или процесс. Или описывающая создание объекта или запуска процесса т.е. просто аргументы.
• Утилита - набор полезных методов.Так же я могу предложить следующие классификации:
• Для свойств классов:
• Property/Data
• Property/Info - информация об объекте.
• Property/Info/Question - вопрос к объекту.
• Property/Info/Assertion - утверждение об объекте.
• Property/Info/Directive - инструкция объекту.
• Property/Reference/Object - ссылка на объект.
• Property/Reference/Delegate - ссылка на логику.
• Property/Reference/Event - ссылка на обработчики события.
• Для методов классов:
• Method/Constructor
• Method/Destructor
• Method/Command/Query - команда на получение данных.
• Method/Command/Request - команда на выполнение работы.
• Method/Handler - реакция на событие.
• Для атрибутов классов:
• Attribute/Info • Attribute/Info/Directive
Заключение
В заключении, я хочу отметить, что большую сложность фреймворка я выделил в две отдельные библиотеки StateMachine.Pro и TreeMachine.Pro. Эти библиотеки могли бы быть достойны отдельных статей, но в целом их суть ясна из их названий.
Ссылки
• https://github.com/Denis535/DotNet-Libraries
• https://github.com/Denis535/Kotlin-Libraries
• https://www.nuget.org/packages/GameFramework.Pro
• https://www.nuget.org/packages/StateMachine.Pro
• https://www.nuget.org/packages/TreeMachine.Pro
• https://central.sonatype.com/artifact/io.github.denis535/game-framework-pro
• https://central.sonatype.com/artifact/io.github.denis535/state-machine-pro
• https://central.sonatype.com/artifact/io.github.denis535/tree-machine-proТеги:• gamedev
• best-practices
• design patterns
• architecture
• framework
• game architecture
• game framework
• unity
• unity3d
• tutorialХабы:• .NET
• Unity
• C#
• Разработка игр
Получайте больше инсайтов о систематизации бизнеса
Подписывайтесь на Telegram-канал Business Operations — ежедневные материалы о бизнес-процессах, операционном управлении и повышении эффективности
💬 Подписаться на канал→ Оригинальная статья