惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
Recent Announcements
Recent Announcements
GbyAI
GbyAI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Microsoft Azure Blog
Microsoft Azure Blog
N
Netflix TechBlog - Medium
人人都是产品经理
人人都是产品经理
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
U
Unit 42
腾讯CDC
D
Docker
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
Vercel News
Vercel News
I
InfoQ
Jina AI
Jina AI
爱范儿
爱范儿
宝玉的分享
宝玉的分享
博客园 - Franky
G
Google Developers Blog
P
Proofpoint News Feed

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Фреймворк GameFramework.Pro (.Net)
Denis535 · 2026-05-17 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение7 мин

Охват и читатели162

Туториал

В этой статье я хочу представить вам мой фреймворк, реализующий идеи чистой архитектуры адаптированные для игровых проектов. Данный фреймворк определяет основные слои вашего проекта, сущности и сервисы, а так же содержит минимальный набор утилит.

Обзор

Для начала давайте рассмотрим из чего состоит этот фреймворк.

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. Эти библиотеки могли бы быть достойны отдельных статей, но в целом их суть ясна из их названий.

Ссылки