Создание редактора квестов и диалогов для Unreal engine: Часть 2 технические аспекты

image

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

Во-первых, чтобы плагин подключить к вашему проекту нужно перенести папку StoryGraphPlugin из папки plugins в папку «директоря вашего проекта»\plugins. Возможно вам придется пере компилировать игру если версии Unreal engine не совпадут.

Но даже после того как вы создадите ассет StoryGraph вам нужно будет произвести некоторые манипуляции. Все сюжетные объекты которые вы размещаете на карте должы быть производными от определенных объектов вот их список:

1) ACharecter_StoryGraph — базовый класс для персонажей имеет блюпринт методы:
ChangeState — Метод изменяет состояние персонажа в данном случае с живого на мертвый, то есть в блюпринте вам нужно создать функцию которая при смерте персонаже будет переключать его состояние, и тогда StoryGraph поймет что персонаж умер.
OpenDialog () — Блюпринт-метод который нужно вызвать чтобы начать диалог с этим персонажем.
GetObjectName () — Блюпринт-метод возвращающий имя персонажа которое определено в StoryGraph, если вы разместили на уровне несколько объектов StoryGraph и в каждом из них имя данного персонажа разное то функция вернет имя из первого StoryGraph.
GetMessegeFromStoryGraph () — Блюпринт-событие происходит когда когда в StoryGraph активируется нода Send Message, которая передает сообщение данному персонажу.

2) APlaceTrigger_StoryGraph — Базовый класс для триггера имеет блюпринт методы:
GetPlaceTriggerType () — Нужен чтобы узнать в каком режиме работает триггер.
ChangeState () — Аналогичен методу из ACharecter_StoryGraph только состояния другие (UnActive, Active). Его нужно использовать в режиме работы тригера UnInteractive.
Activate () — Этод метод не является аналогией предыдущего, потому-что он какраз не работает в режиме UnInteractive, а работает в двух других. В режиме Interactive он активирует триггер, а в режиме AdvanceInteractive он открывает диалоговое окно взаимодкйствия с триггером.
Как и предыдущий класс данный имеет блюпринт-методы GetObjectName () и GetMessegeFromStoryGraph ().

3) AInventoryItem_StoryGraph — Базовый класс для сюжетного предмета инвентаря. Кроме выше упомянутых GetObjectName () и GetMessegeFromStoryGraph () данный класс имеет метод
PickUp () — Который перемещает предмет в инвентарь персонажа.

4) AOtherActor_StoryGraph — Класс для объектов не принадлежащих к вышеперечисленным категориям. Не имеет своих методов только GetObjectName () и GetMessegeFromStoryGraph ().

5) ALevelScriptActor_StoryGraph — Базовый класс для LevelBluprint имеет одно Блюпринт-событие
GetMessegeFromStoryGraph () — Которое вызывается при активации ноды Send message to level blueprint

Кроме объектов на карте StoryGraph взаимодействует с элементами интерфейса вот их список:

1) AHUD_StoryGraph — Базовый класс для вашего HUD. Имеет методы:
EndGame () — Блюпринт событие которое вызывается при срабатывание ноды Game Over.
PrintQuestPhaseOnScreen () — Блюпринт событие которое вызывается при срабатывание ноды Print quest phase on screen.
OpenDialogEvent () — Блюпринт событие вызывается когда один из персонажей вызывает OpenDialog ().
OpenPlaceTriggerMessagesEvent () — Тоже самое, но для триггера.
ChangeLocalization () — Меняет язык в игре.
GetCurrentLocalization () — Возвращает текущий язык.

2) UGameScreen_StoryGraphWidget — Базовый класс для виджета располагающемся на игровом экране. Этот виджет выводит на экран сообщения для игрока, а также так называемый DefaultAnswer этот ответ игрок видит если у персонажа

3) UJurnal_StoryGraphWidget — Виджет выводит на экран активные в данный момент квесты.

4) URadar_StoryGraphWidget — Радар его нужно разместить в виджете GameScreen и он будет выводить цели

5) UDialog_StoryGraphWidget — Выводит на экран окно диалога или окно взаимодействия с триггером.

6) UInventory_StoryGraphWidget — Виджет выводит на экран инвентарь в котором находятся сюжетные предметы.

После этого у вас все заработает как надо.

Возникшие проблемы


Теперь расскажу про проблемы которые мне пришлось решить чтобы написать этот плагин.

Первой проблемой было то что уровень в Unreal Engine это закрытая система все указатели объектов которые находятся на уровне должны указывать на обекты на уровне, а объекты вне уровня не могут иметь указатели указывающие на объекты на уровне. И это проблема по скольку объект StoryGraph находится вне уровня, но его объекты нужно как то привезать к объектам уровня. К счастью в Unreal engine есть так называемые ленивые указатели (TAssetPtr). Ленивые они потому что могут ссылаться на объекты которые в данный момент не загружены, а также при помощи них можно ссылаться на объекты уровня из объектов которые находятся за его пределами.

Второй проблемой проблемой для меня оказался тот факт что объект граф (UEdGraph) нельзя использовать в игре. То есть игра с компилируется и даже упакуется, но при попытке запустить эту игру она просто вылетит с ошибкой. Поэтому ноды я просто укладываю в массив, а на графе я размещаю «объект переходник» UProxyNodeBase который просто перенапрвляет моим нодам вызовы методов при редактировании в редакторе. А сам этот объект находится в модуле эдитора поэтому при упаковке игры он не учитывается.

Ну и когда я уже написал плагин я попробовал скомпилировать его в режиме Development и получи вот что:

Ошибки
UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void * __cdecl operator new(unsigned __int64)" (??2@YAPEAX_K@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void * __cdecl operator new(unsigned __int64,struct std::nothrow_t const &)" (??2@YAPEAX_KAEBUnothrow_t@std@@@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPEAX@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void __cdecl operator delete(void *,struct std::nothrow_t const &)" (??3@YAXPEAXAEBUnothrow_t@std@@@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void * __cdecl operator new[](unsigned __int64)" (??_U@YAPEAX_K@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void * __cdecl operator new[](unsigned __int64,struct std::nothrow_t const &)" (??_U@YAPEAX_KAEBUnothrow_t@std@@@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void __cdecl operator delete[](void *)" (??_V@YAXPEAX@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void __cdecl operator delete[](void *,struct std::nothrow_t const &)" (??_V@YAXPEAXAEBUnothrow_t@std@@@Z) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "void __cdecl UELinkerFixupCheat(void)" (?UELinkerFixupCheat@@YAXXZ) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "bool const GIsDebugGame" (?GIsDebugGame@@3_NB) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "class FFixedUObjectArray * & GObjectArrayForDebugVisualizers" (?GObjectArrayForDebugVisualizers@@3AEAPEAVFFixedUObjectArray@@EA) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "bool GIsGameAgnosticExe" (?GIsGameAgnosticExe@@3_NA) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "wchar_t * GInternalGameName" (?GInternalGameName@@3PA_WA) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "wchar_t const * const GForeignEngineDir" (?GForeignEngineDir@@3PEB_WEB) already defined in StoryTestProgect.cpp.obj
1>UE4-StoryGraphPluginRuntime.lib(Module.StoryGraphPluginRuntime.cpp.obj) : error LNK2005: "struct FNameEntry * * * GFNameTableForDebuggerVisualizers_MT" (?GFNameTableForDebuggerVisualizers_MT@@3PEAPEAPEAUFNameEntry@@EA) already defined in StoryTestProgect.cpp.obj


Я долго не понимал откуда эти ошибки взялись, оказалось все просто надо было заменить в StoryGraphPluginRuntime.cpp

Вот это:

IMPLEMENT_PRIMARY_GAME_MODULE(FStoryGraphPluginRuntime, StoryGraphPluginRuntime, "StoryGraphPluginRuntime");

На вот это:
IMPLEMENT_MODULE(FStoryGraphPluginRuntime, StoryGraphPluginRuntime);

И все заработало. Но после этого я захотел скомпилировать плагин в режиме Shipping и тут опять полезли ошибки я к сожалению их не сохранил, но суть их сводилась к тому что я не поставил; причем компилятор указывал на файлы самого движка. В общем опять оказклось все просто. Для вывода лога надо обявить новую категорию делается это так:
DECLARE_LOG_CATEGORY_EXTERN(StoryGraphPluginRuntime, All, All)
DEFINE_LOG_CATEGORY(StoryGraphPluginRuntime)

Но в режиме Development и Development Editor все работает без точки с запятой, а вот в режиме Shipping нужно писать так:

DECLARE_LOG_CATEGORY_EXTERN(StoryGraphPluginRuntime, All, All);
DEFINE_LOG_CATEGORY(StoryGraphPluginRuntime);

Причем все усугубляет то факт что компилятор при ошибке ссылается не на эти строки, а вобще на библиотеку движка.

На этом все

→ Вот исходники
→ Cсылка на демо

А также ссылка на прошлую статью: Создание редактора квестов и диалогов для Unreal engine: Часть 1 описание плагина

Комментарии (2)

  • 25 декабря 2016 в 14:24

    0

    Не уверен, но наверное плагин подходит для типичных реализаций диалогов вида выбрал ответ1, получил результат1, выбрал ответ2, получил результат2, и т.д.

    А как насчет реализации вида:
    -выбрал ответы 1,2,3 или 1,3,4, или 1,5 получил результат 1
    -выбрал ответы 2,3 или 2,5 получил результат 2
    и т.д., т.е. более похожие на поведение в жизни, ведь в реальности на результат зачастую влияет не одно действие, а цепочка событий

    Не то чтобы видел большие проблемы в реализации этого, но применяется ли такое где нибудь на практике?

    • 25 декабря 2016 в 15:40 (комментарий был изменён)

      0

      В разных ветвях диалога можно активировать объекты DialogTrigger. В главном графе вы выставляете ноды GetObjectState таким образом чтобы например одна ветвь активировалась если сработал 1,3,5 триггер, а тригеры 2 и 4 не активировались и так далее.
      Представьте что фаза квеста это шаг, тогда каждый последующий шаг совершается только если все ноды между двумя фазами заняли соответствующие значения

© Habrahabr.ru