Модули Runtime и Editor

Интересная тема, которую мало описывают в сети. Хотя информация частями имеется, то там, то там. Точнее что-то состряпать можно из кусков. Последний вариант, как всегда – это посмотреть в исходниках движка. Там очень много модулей, поэтому программисту в общем не составит труда разобраться, если конечно вы знакомы с синтаксисом движка. В общем очень много макросов которые в движке используются в С/С++, даже опытному программисту в начале будет проблематично к этому привыкнуть.

Исходники здесь: UE_MyModulesExample

Editor” модули часто используются для написания инструментов в “UnrealEngine“. Это очень упрощает работу тем людям, которые занимаются например дизайном уровней.

Ну теперь к теме модулей.

Runtime” модули – это стандартные модули, которые работают к в PIE, так и в самой игре после упаковки.

Editor” модули, это модули естественно, которые работают только в PIE и загружаются при запуске PIE.

Модули создаются в основном в двух местах. Первое это папка “Source” в корне проекта. А другое место это в плагине.

Runtime” модуль создаётся автоматически, когда вы добавляете в проект новую классу или создаёте новый плагин. А вот “Editor” модуль нужно создавать самостоятельно. Исключением является только создание плагина по шаблону для PIE.

Модули работают везде одинаково, поэтому где их создавать зависит от ваших целей.

Мы рассмотрим простой вариант с папкой “Source“.

Добавим новую (пустую )классу как на картинке.

Далее идёт генерирование исходного кода и компиляция.

В версия “UnrealEngine” 4.27 и выше, ничего не поменялось, только меню для создания класс в разных местах.

Открываем IDE в котором работаете. Я использую “Rider” – не очень люблю “Visual Studio” ещё с версии 5. Но каждому своё 😉

Здесь мы видим следующую структуру.

MyNewModule.h/cpp” это чистая класса, которую создали.

#pragma once

#include "CoreMinimal.h"

/**
 * 
 */
class MYMODULESEXAMPLE_API MyNewModule
{
public:
	MyNewModule();
	~MyNewModule();
};
#include "MyNewModule.h"

MyNewModule::MyNewModule()
{
}

MyNewModule::~MyNewModule()
{
}

А класса “MyModulesExample.build.cs” содержит стандартные настройки для “Runtime“.

using UnrealBuildTool;

public class MyModulesExample : ModuleRules
{
	public MyModulesExample(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[]
		{
			"Core", 
			"CoreUObject", 
			"Engine", 
			"InputCore"
		});

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

Новая класса в “MyModulesExample.h/cpp” пустая, кроме одной строчи.

IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, MyModulesExample, "MyModulesExample" );

Но также видно, что в файлы настройки “Target.cs” были созданы, как для “Runtime“, так и для “Editor“.

Вырезка из “Runtime” модуля “MyModulesExample.Target.cs“.

using UnrealBuildTool;
using System.Collections.Generic;

public class MyModulesExampleTarget : TargetRules
{
   public MyModulesExampleTarget(TargetInfo Target) : base(Target)
   {
      Type = TargetType.Game;
      DefaultBuildSettings = BuildSettingsVersion.V2;

      ExtraModuleNames.AddRange( new string[] { "MyModulesExample" } );
   }
}

И вырезка из модуля PIE “MyModulesExampleEditor.Target.cs

using UnrealBuildTool;
using System.Collections.Generic;

public class MyModulesExampleEditorTarget : TargetRules
{
	public MyModulesExampleEditorTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Editor;
		DefaultBuildSettings = BuildSettingsVersion.V2;

		ExtraModuleNames.AddRange( new string[] { "MyModulesExample" } );
	}
}

И в первом и во втором файле они отличаются окончанием названия файла и переменными в строчке “Type = TargetType.Game;” и “Type = TargetType.Editor;“. Есть ещё другие целевые настройки например типа “Сервер” итд., но о них не в этой статье.

И последняя вырезка и файла “MyModulesExample.uproject” проекта

{
	"FileVersion": 3,
	"EngineAssociation": "4.27",
	"Category": "",
	"Description": "",
	"Modules": [
		{
			"Name": "MyModulesExample",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		}
	]
}

Здесь мы видим, что наш модуль загружается в “Runtime“. Он используется, как в PIE так и в игре.

Это всё были сгенерированные файлы, но теперь перейдём к созданию PIE модуля или просто “Editor” модуль.

Я создам две папки “Runtime“,”Editor” в папке “MyModulesExample” и перемещю содержимое в папку “Runtime“. А в папку “Editor” скопирую файлы из “Runtime” и переименую их с окончанием “Editor“. Классу-пустышку “MyNewModule.h/cpp” мы оставляем в папке “Runtime“. В общем всё что нужно только в PIE теперь создаётся только в папке “Editor“. К этому относятся различные “Subsystem“, “Slate” итд.. А в папке “Runtime” теперь находятся все игровые функции, которые так же могут использоваться и в PIE. Модулей может быть огромное количество. Но чаще всего они разбиваются на определённые цели и помещаются в плагины для дальнейшего использования в других проектах.

Далее делаем небольшие изменения в только что скопированных файлах.

В проектном файле добавляем новый модуль:

{
	"FileVersion": 3,
	"EngineAssociation": "4.27",
	"Category": "",
	"Description": "",
	"Modules": [
		{
			"Name": "MyModulesExample",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		},
		{
			"Name": "MyModulesExampleEditor",
			"Type": "Editor",
			"LoadingPhase": "Default",
			"AdditionalDependencies": [
				"MyModulesExample"
			]
		}
	]
}

Обратим внимание на некоторые настройки.

Type” это определение модуля и имеет следующие значения.

Мы будем использовать только “Runtime” и “Editor“.

LoadingPhase” это фаза загрузки модуля и имеет следующие настройки.

Здесь мы используем стандартную загрузку “Default“.

Теперь далее к файлам.

В файл “MyModulesExampleEditor.Build.cs” меняем окончание имени классы. Так как мы скопировали этот файл из модуля “Runtime“.

using UnrealBuildTool;

public class MyModulesExampleEditor : ModuleRules
{
	public MyModulesExampleEditor(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[]
		{
			"Core", 
			"CoreUObject", 
			"Engine", 
			"InputCore",
			
			"MyModulesExample",
			
		});

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

Далее в файл “MyModulesExampleEditor.Target.cs” мы добавляем зависимость от модуля “Runtime” и переименовываем окончание имени класса.

using UnrealBuildTool;
using System.Collections.Generic;

public class MyModulesExampleEditorTarget : TargetRules
{
	public MyModulesExampleEditorTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Editor;
		DefaultBuildSettings = BuildSettingsVersion.V2;

		ExtraModuleNames.AddRange( new string[]
		{
			"MyModulesExample",
			"MyModulesExampleEditor"
		} );
	}
}

А в исходном файле “MyModulesExampleEditor.cpp” закомментируйте строчку. Позже мы её переделаем под наши нужды.

// IMPLEMENT_GAME_MODULE( FDefaultGameModuleImpl, MyModulesExampleEditor );

Потому что “Runtime” модуль у нас главный, то он содержит имплементацию главного игрового модуля, а “Editor” модуль у нас обычный модуль. Но об этом позже.

!!! ВАЖНО !!! После все манипуляций над структурой файлов. Стоит удалить все временные папки из проекта. Иначе у вас будут выдаваться предупреждения в IDE с кучей ошибок от зависимостей, которые больше не существуют. Хотя проект сейчас должен собираться нормально без ошибок.

После затирания временных файлов пересоздайте C++ проект.

Соберите проект и проверьте запуск “UnrealEngine“. Если всё нормально, то добавим ещё стартовые функции по загрузке модулей и выведем немного текста в журнал событий. Что бы убедится, что всё работает отлично.

А в файлах модуля “MyModulesExampleEditor.h/.cpp” мы создаём классу со следующим кодом.

#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"

class FMyModulesExampleEditorModule :public IModuleInterface
{
public:
	virtual void StartupModule() override;
};

А в исходнике также правим имплементацию на нашу классу.


#include "MyModulesExampleEditor.h"
#include "Modules/ModuleManager.h"


void FMyModulesExampleEditorModule::StartupModule()
{
	UE_LOG(LogTemp, Warning, TEXT("============ My Editor module started ============="));
}

IMPLEMENT_GAME_MODULE( FMyModulesExampleEditorModule, MyModulesExampleEditor );

Теперь после сборки проекта и запуска “UnrealEngine” у нас в журнале должна появится следующая строчка.

Поздравляю. Вы создали ваш первый “Editor” модуль.

Сделаем так же с “Runtime” модулем и проверим запуск.

#pragma once

#include "CoreMinimal.h"

class FMyModulesExampleModule final :public IModuleInterface
{
public:
	virtual void StartupModule() override;
};

#include "MyModulesExample.h"
#include "Modules/ModuleManager.h"


void FMyModulesExampleModule::StartupModule()
{
	UE_LOG(LogTemp, Warning, TEXT("============ My Runtime module started ============="));
}

// IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, FMyModulesExampleModule, "MyModulesExample" );
IMPLEMENT_PRIMARY_GAME_MODULE( FMyModulesExampleModule, MyModulesExample, "MyModulesExample" );

При успехе компиляции и запуска движка в журнале событий появится следующее.

Вот теперь и точно всё что можно рассказать про создание базовых модулей.

Далее расскажу зачем эти модули вообще нужны для разработки. Думаю что возьму как пример классу “IDetailCustomization“.

UPDATE:

Переключил проект на 5.2. Всё работает так же без ошибок