Глава 2.1: 5-уровневая иерархия скриптов
Главная | 5-уровневая иерархия скриптов | Следующая: Подробный разбор config.cpp >>
Содержание
- Обзор
- Стек уровней
- Уровень 1: 1_Core (engineScriptModule)
- Уровень 2: 2_GameLib (gameLibScriptModule)
- Уровень 3: 3_Game (gameScriptModule)
- Уровень 4: 4_World (worldScriptModule)
- Уровень 5: 5_Mission (missionScriptModule)
- Критическое правило
- Порядок загрузки и тайминг
- Когда выполняется код каждого уровня
- Практические рекомендации
- Краткое руководство по выбору
- Распространённые ошибки
Обзор
Движок DayZ компилирует скрипты за пять отдельных проходов, называемых модулями скриптов. Каждый модуль соответствует пронумерованной папке в директории Scripts/ вашего мода:
Scripts/
1_Core/ --> engineScriptModule
2_GameLib/ --> gameLibScriptModule
3_Game/ --> gameScriptModule
4_World/ --> worldScriptModule
5_Mission/ --> missionScriptModuleКаждый уровень строится поверх предыдущих. Номера не произвольны --- они определяют строгий порядок компиляции и зависимостей, обеспечиваемый движком.
Стек уровней
+---------------------------------------------------------------+
| |
| 5_Mission (missionScriptModule) |
| UI, HUD, жизненный цикл миссии, экраны меню |
| Может ссылаться на: всё ниже (1-4) |
| |
+---------------------------------------------------------------+
| |
| 4_World (worldScriptModule) |
| Сущности, предметы, транспорт, менеджеры, игровая логика |
| Может ссылаться на: 1_Core, 2_GameLib, 3_Game |
| |
+---------------------------------------------------------------+
| |
| 3_Game (gameScriptModule) |
| Конфиги, регистрация RPC, классы данных, привязки ввода |
| Может ссылаться на: 1_Core, 2_GameLib |
| |
+---------------------------------------------------------------+
| |
| 2_GameLib (gameLibScriptModule) |
| Низкоуровневые привязки движка (редко используются модами) |
| Может ссылаться на: только 1_Core |
| |
+---------------------------------------------------------------+
| |
| 1_Core (engineScriptModule) |
| Фундаментальные типы, константы, чистые утилитарные функции |
| Может ссылаться на: ничего (это фундамент) |
| |
+---------------------------------------------------------------+
ПОРЯДОК КОМПИЛЯЦИИ: 1 --> 2 --> 3 --> 4 --> 5
НАПРАВЛЕНИЕ ЗАВИСИМОСТЕЙ: только вверх (нижние не видят верхние)Уровень 1: 1_Core (engineScriptModule)
Назначение
Абсолютный фундамент. Код здесь выполняется на уровне движка до существования каких-либо игровых систем. Это самая ранняя точка, где может выполняться код мода.
Что размещать здесь
- Константы и перечисления, общие для всех уровней
- Чистые утилитарные функции (математические хелперы, строковые утилиты)
- Инфраструктура логирования (сам логгер, а не то, что логируется)
- Определения препроцессора и typedef
- Определения базовых классов, которые должны быть видны везде
Реальные примеры
Community Framework размещает свою базовую систему модулей здесь:
// 1_Core/CF_ModuleCoreManager.c
class CF_ModuleCoreManager
{
static ref array<typename> s_Modules = new array<typename>;
static void _Insert(typename module)
{
s_Modules.Insert(module);
}
};MyFramework размещает свои константы логирования здесь:
// 1_Core/MyLogLevel.c
enum MyLogLevel
{
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4
};Когда использовать
Используйте 1_Core только когда вам нужно что-то доступное всем другим уровням, и это не имеет зависимостей от игровых типов вроде PlayerBase, ItemBase или MissionBase. Большинству модов этот уровень вообще не нужен.
Уровень 2: 2_GameLib (gameLibScriptModule)
Назначение
Низкоуровневые привязки библиотек движка. Этот уровень существует в ванильной иерархии скриптов, но редко используется модами. Он находится между сырым движком и игровой логикой.
Что размещать здесь
- Абстракции уровня движка (привязки рендеринга, звукового движка)
- Математические библиотеки сверх того, что предоставляет
1_Core - Базовые типы виджетов/UI-движка
Реальные примеры
DabsFramework --- один из немногих модов, использующих этот уровень:
// 2_GameLib/DabsFramework/MVC/ScriptView.c
// Низкоуровневая инфраструктура привязки представлений
class ScriptView : ScriptedWidgetEventHandler
{
// ...
};Когда использовать
Почти никогда. Если вы не строите фреймворк, которому нужны привязки уровня движка ниже игрового уровня, полностью пропустите 2_GameLib. Подавляющее большинство модов используют только уровни 3, 4 и 5.
Уровень 3: 3_Game (gameScriptModule)
Назначение
Рабочий уровень для конфигурации, определений данных и систем, не взаимодействующих напрямую с мировыми сущностями. Это первый уровень, где доступны игровые типы.
Что размещать здесь
- Классы конфигурации (настройки, которые можно загружать/сохранять)
- Регистрация RPC и идентификаторы
- Классы данных и DTO (объекты передачи данных)
- Регистрация привязок ввода
- Системы регистрации плагинов/модулей
- Общие перечисления и константы, зависящие от игровых типов
- Обработчики пользовательских клавиатурных привязок
Реальные примеры
MyFramework --- система конфигурации:
// 3_Game/MyMod/Config/MyConfigBase.c
class MyConfigBase
{
// Базовая конфигурация с автоматической JSON-персистентностью
void Load();
void Save();
string GetConfigPath();
};COT определяет свои идентификаторы RPC здесь:
// 3_Game/COT/RPCData.c
class JMRPCData
{
static const int WEATHER_SET = 0x1001;
static const int PLAYER_HEAL = 0x1002;
// ...
};VPP Admin Tools регистрирует свои чат-команды:
// 3_Game/VPPAdminTools/ChatCommands/ChatCommandBase.c
class ChatCommandBase
{
string GetCommand();
bool Execute(PlayerIdentity sender, array<string> args);
};Когда использовать
Если сомневаетесь, размещайте в 3_Game. Это уровень по умолчанию для большей части не-сущностного кода. Классы конфигурации, перечисления, константы, определения RPC, классы данных --- всё это принадлежит сюда.
Уровень 4: 4_World (worldScriptModule)
Назначение
Игровая логика, взаимодействующая с 3D-миром. Этот уровень имеет доступ к сущностям, предметам, транспорту, зданиям и всем мировым объектам.
Что размещать здесь
- Пользовательские предметы и оружие (расширение
ItemBase,Weapon_Base) - Пользовательские сущности (расширение
Building,DayZAnimalи т.д.) - Мировые менеджеры (системы спавна, менеджеры лута, AI-директоры)
- Расширения игрока (модифицированное поведение
PlayerBase) - Кастомизация транспорта
- Системы действий (расширение
ActionBase) - Триггерные зоны и эффекты области
Реальные примеры
MyMissions Mod спавнит маркеры миссий в мире:
// 4_World/Missions/MyMissionMarker.c
class MyMissionMarker : House
{
void MyMissionMarker()
{
SetFlags(EntityFlags.VISIBLE, true);
}
void SetPosition(vector pos)
{
SetPosition(pos);
}
};MyAI Mod реализует сущности ботов здесь:
// 4_World/AI/MyAIBot.c
class MyAIBot : SurvivorBase
{
protected ref MyAIBrain m_Brain;
override void EOnInit(IEntity other, int extra)
{
super.EOnInit(other, extra);
m_Brain = new MyAIBrain(this);
}
};Ванильный DayZ определяет все предметы здесь:
// 4_World/Entities/ItemBase/Edible_Base.c
class Edible_Base extends ItemBase
{
// Все продукты питания наследуют от этого
};Когда использовать
Всё, что касается физического игрового мира: создание сущностей, модификация предметов, обработка взаимодействий игроков, управление мировым состоянием. Если ваш класс расширяет EntityAI, ItemBase, PlayerBase, Building или взаимодействует с GetGame().GetWorld(), он принадлежит 4_World.
Уровень 5: 5_Mission (missionScriptModule)
Назначение
Самый верхний уровень. Жизненный цикл миссии, UI-панели, HUD-оверлеи и финальная точка инициализации. Здесь находится код запуска клиентской и серверной стороны.
Что размещать здесь
- Хуки классов миссий (переопределения
MissionServer,MissionGameplay) - HUD и UI-панели
- Экраны меню
- Регистрация и инициализация модов (последовательность «загрузки»)
- Клиентские оверлеи рендеринга
- Обработчики запуска/остановки сервера
Реальные примеры
MyFramework подключается к миссии для инициализации всех подсистем:
// 5_Mission/MyMod/MyModMissionClient.c
modded class MissionGameplay
{
override void OnInit()
{
super.OnInit();
MyFramework.Init();
}
override void OnMissionFinish()
{
MyFramework.ShutdownAll();
super.OnMissionFinish();
}
};COT добавляет своё меню администратора здесь:
// 5_Mission/COT/gui/COT_Menu.c
class COT_Menu : UIScriptedMenu
{
override Widget Init()
{
// Построение UI панели администратора
}
};MyMissions Mod регистрируется в Core:
// 5_Mission/Missions/MyMissionsRegister.c
class MyMissionsRegister
{
void MyMissionsRegister()
{
MyFramework.RegisterMod("Missions", "1.0.0");
MyFramework.RegisterModConfig(new MyMissionsConfig());
}
};Когда использовать
UI, HUD, экраны меню и инициализация мода, зависящая от активной миссии. Также финальное место, где сервер подключается к жизненному циклу запуска/завершения.
Критическое правило
Нижние уровни НЕ МОГУТ ссылаться на типы из верхних уровней.
Это самое важное правило в архитектуре скриптов DayZ. Движок обеспечивает его на этапе компиляции.
РАЗРЕШЕНО:
Код 5_Mission ссылается на класс из 4_World OK
Код 4_World ссылается на класс из 3_Game OK
Код 3_Game ссылается на класс из 1_Core OK
ЗАПРЕЩЕНО:
Код 3_Game ссылается на класс из 4_World ОШИБКА КОМПИЛЯЦИИ
Код 4_World ссылается на класс из 5_Mission ОШИБКА КОМПИЛЯЦИИ
Код 1_Core ссылается на класс из 3_Game ОШИБКА КОМПИЛЯЦИИПочему это существует
Каждый уровень компилируется отдельно и последовательно. Когда компилируется 3_Game, 4_World и 5_Mission ещё не существуют. Компилятор не знает об этих типах.
Что происходит при нарушении
Сообщение об ошибке часто бывает неинформативным:
SCRIPT (E): Undefined type 'PlayerBase'Обычно это означает, что вы разместили код в 3_Game, который ссылается на PlayerBase, определённый в 4_World. Решение --- переместить ваш код в 4_World или выше.
Обходное решение: приведение через базовые типы
Когда коду 3_Game нужно работать с объектом, который в рантайме будет PlayerBase, используйте базовый тип Object или Man (определённый в 3_Game) и приведите позже:
// В 3_Game -- мы не можем напрямую ссылаться на PlayerBase
class MyConfig
{
void HandlePlayer(Man player)
{
// 'Man' доступен в 3_Game
// В рантайме это будет PlayerBase, но мы не можем назвать его здесь
}
};
// В 4_World -- теперь можно безопасно привести
class MyWorldLogic
{
void ProcessPlayer(Man player)
{
PlayerBase pb;
if (Class.CastTo(pb, player))
{
// Теперь у нас полный доступ к PlayerBase
}
}
};Порядок загрузки и тайминг
Порядок компиляции
Движок компилирует скрипты всех модов для каждого уровня перед переходом к следующему:
Шаг 1: Компиляция скриптов 1_Core ВСЕХ модов
Шаг 2: Компиляция скриптов 2_GameLib ВСЕХ модов
Шаг 3: Компиляция скриптов 3_Game ВСЕХ модов
Шаг 4: Компиляция скриптов 4_World ВСЕХ модов
Шаг 5: Компиляция скриптов 5_Mission ВСЕХ модовНа каждом шаге моды упорядочиваются по цепочке зависимостей requiredAddons в config.cpp. Если ModB зависит от ModA, скрипты ModA для данного уровня компилируются первыми.
Порядок инициализации
После компиляции инициализация в рантайме следует другой последовательности:
1. Движок загружается, считывает конфиги
2. Скрипты 1_Core доступны (статические конструкторы выполняются)
3. Скрипты 2_GameLib доступны
4. Скрипты 3_Game доступны
--> Функции входа CfgMods выполняются (напр., "CreateGameMod")
--> Привязки ввода регистрируются
5. Скрипты 4_World доступны
--> Можно создавать сущности
6. Миссия загружается
7. Скрипты 5_Mission доступны
--> MissionServer.OnInit() / MissionGameplay.OnInit() срабатывают
--> UI и HUD становятся доступнымиКогда выполняется код каждого уровня
| Уровень | Статическая инициализация | Готов в рантайме | Ключевое событие |
|---|---|---|---|
1_Core | Первый | Немедленно | Загрузка движка |
2_GameLib | Второй | После инициализации движка | Подсистемы движка готовы |
3_Game | Третий | После инициализации игры | CreateGame() / пользовательская функция входа |
4_World | Четвёртый | После загрузки мира | Начинается спавн сущностей |
5_Mission | Пятый (последний) | После старта миссии | MissionServer.OnInit() / MissionGameplay.OnInit() |
Важно: Статические переменные и код глобальной области видимости каждого уровня выполняются на этапе компиляции/линковки, до вызова OnInit(). Не размещайте сложную логику инициализации в статических инициализаторах.
Практические рекомендации
«Если сомневаетесь, размещайте в 3_Game»
Это наиболее распространённый уровень для кода модов. Если только ваш код:
- Не должен быть доступен до существования игровых типов -->
1_Core - Не расширяет сущность/предмет/транспорт/игрока -->
4_World - Не касается UI, HUD или жизненного цикла миссии -->
5_Mission
...то он принадлежит 3_Game.
Чек-лист по уровням
Перед размещением файла задайте эти вопросы:
Расширяет ли он
EntityAI,ItemBase,PlayerBase,Buildingили другую мировую сущность? Размещайте в4_World.Ссылается ли он на
MissionServer,MissionGameplayили создаёт UI-виджеты? Размещайте в5_Mission.Это чистый класс данных, конфиг, перечисление или определение RPC? Размещайте в
3_Game.Это фундаментальная константа или утилита без игровых зависимостей? Размещайте в
1_Core.Ничего из вышеперечисленного? По умолчанию
3_Game.
Держите уровни тонкими
Типичная ошибка --- сваливание всего в 4_World. Это создаёт сильно связанный код. Вместо этого:
ХОРОШО:
3_Game/ --> Класс конфига, перечисления, ID RPC, структуры данных
4_World/ --> Менеджер, использующий конфиг, классы сущностей
5_Mission/ --> UI, отображающий состояние менеджера
ПЛОХО:
4_World/ --> Конфиги, перечисления, RPC, менеджеры И классы сущностей вперемешкуКраткое руководство по выбору
Расширяет ли мировую сущность?
(EntityAI, ItemBase и т.д.)
/ \
ДА НЕТ
| |
4_World Касается UI/HUD/миссии?
/ \
ДА НЕТ
| |
5_Mission Это чистая утилита
без игровых зависимостей?
/ \
ДА НЕТ
| |
1_Core 3_GameРаспространённые ошибки
1. Ссылка на PlayerBase из 3_Game
// НЕПРАВИЛЬНО: в 3_Game/MyConfig.c
class MyConfig
{
void ApplyToPlayer(PlayerBase player) // ОШИБКА: PlayerBase ещё не определён
{
}
};
// ПРАВИЛЬНО: в 3_Game/MyConfig.c
class MyConfig
{
ref array<float> m_Values; // Чистые данные, без ссылок на сущности
};
// ПРАВИЛЬНО: в 4_World/MyManager.c
class MyManager
{
void ApplyConfig(PlayerBase player, MyConfig config)
{
// Теперь можно использовать оба
}
};2. Размещение UI-кода в 4_World
// НЕПРАВИЛЬНО: в 4_World/MyPanel.c
class MyPanel : UIScriptedMenu // UIScriptedMenu работает в 4_World,
{ // но хуки MissionGameplay в 5_Mission
// Это вызовет проблемы при попытке регистрации UI
};
// ПРАВИЛЬНО: в 5_Mission/MyPanel.c
class MyPanel : UIScriptedMenu
{
// UI принадлежит 5_Mission, где доступен жизненный цикл миссии
};3. Размещение констант в 4_World, когда они нужны в 3_Game
// НЕПРАВИЛЬНО: Константы определены в 4_World
// 4_World/MyConstants.c
const int MY_RPC_ID = 12345;
// 3_Game/MyRPCHandler.c
class MyRPCHandler
{
void Register()
{
// ОШИБКА: MY_RPC_ID не виден здесь (определён на верхнем уровне)
}
};
// ПРАВИЛЬНО: Константы определены в 3_Game (или 1_Core)
// 3_Game/MyConstants.c
const int MY_RPC_ID = 12345; // Теперь виден и в 3_Game, и в 4_World, и в 5_Mission4. Излишнее усложнение с 1_Core
Если ваши «константы» ссылаются на любой игровой тип, они не могут быть в 1_Core. Даже такая вещь как const string PLAYER_CONFIG_PATH допустима в 1_Core, но класс, принимающий параметр CGame --- нет.
Итоги
| Уровень | Папка | Запись в конфиге | Основное назначение | Частота |
|---|---|---|---|---|
| 1 | 1_Core/ | engineScriptModule | Константы, утилиты, база логирования | Редко |
| 2 | 2_GameLib/ | gameLibScriptModule | Привязки движка | Очень редко |
| 3 | 3_Game/ | gameScriptModule | Конфиги, RPC, классы данных | Наиболее частый |
| 4 | 4_World/ | worldScriptModule | Сущности, предметы, менеджеры | Часто |
| 5 | 5_Mission/ | missionScriptModule | UI, HUD, хуки миссий | Часто |
Запомните: Нижние уровни не видят верхние. Если сомневаетесь, используйте 3_Game. Перемещайте код наверх только когда вам нужен доступ к типам, определённым на верхнем уровне.
Следующая: Глава 2.2: Подробный разбор config.cpp
