Gratt
Модостроитель
- Регистрация
- 14 Ноя 2014
- Сообщения
- 3.301
- Благодарности
- 4.636
- Баллы
- 625
Русский English
* В этом туторе будут использоваться препроцессорные команды для раздельной компиляции кроссплатформенного кода. Если это для Вас слишком сложно, добавлю внизу поста пример кода без них. Однако настоятельно рекомендую вникнуть в реализацию с использованием препроцессора.
Для начала работы возьмем предыдущий плагин. Тестировать работу будем на Gothic II: NoTR, а затем распишем код для остальных.
Установим конфигурацию проекта на G2A Release.
Подготовка рабочего пространства
Для удобства создадим новый исходный файл под каталогом Plugin:
Каждый интерфейс движка обернут под пространства имен (Gothic_I_Classic, Gothic_I_Addon, Gothic_II_Classic, Gothic_II_Addon соответственно). Поскольку мы пишем тестовый код под Gothic II NoTR, то определим пространство Gothic_II_Addon.
В итоге получим файл примерно с такой начинкой:
C++:#include <UnionAfx.h> namespace Gothic_II_Addon { }
Однако если мы захотим изменить конфигурацию проекта под другой движок, компилятор выбьет ошибку, что код внутри Gothic_II_Addon ему непонятен.
Поверх пространства допишем команду #ifdef __G2A. Благодаря этому компилятор сможет исключать из построения весь лишний код, когда тот будет не нужен.
Эта конструкция будет базовой для всех кроссплатформенных проектов:
C++:#include <UnionAfx.h> #ifdef __G2A namespace Gothic_II_Addon { } #endif
Добавление циклической функции
Самый простой способ вызывать функции из разных файлов исходного кода - ключевое слово extern. В конце нашего файла создадим функцию Loop, а в Application.cpp объявим ее через extern. Вызовем функцию из предопределенной функции Game_Loop.
C++:// Example.cpp #include <UnionAfx.h> #ifdef __G2A namespace Gothic_II_Addon { } #endif void Loop() { }
C++:// Application.cpp extern void Loop(); void Game_Loop() { Loop(); }
Вывод текста на экран
В Example.cpp в Gothic_II_Addon добавим функцию вывода текста. Текст, как можно догадаться, будет выводиться циклически вызовом из функции Loop.
Вывод будем производить на вьюпорт screen.
screen - это экземпляр zCView (можно посмотреть в файлах zView.h). Он имеет метод Print для вывода текста по координатам, PrintCX выравнивающий текст по горизонтали, PrintCY по вертикали и PrintCXY рисующий текст в центре экрана. Возьмем последнюю.
Example: screen->PrintCXY( "Hello" );
В качестве текста возьмем информацию об имени персонажа в фокусе игрока.
Игрок определяется глобальной переменной player класса oCNpc.
Класс имеет два подходящих метода GetFocusVob - берет любую сущность в фокусе и GetFocusNpc - берет конкретно нпс. Она нам и нужна.
Example: oCNpc* focusNpc = playerGetFocusNpc();
Далее необходимо получить имя npc. Вспоминаем скрипты - нпс имеет массив имен из 5 строк. Нам нужна первая. Воспользуемся методом GetName указав индекс строки 0.
Example: zSTRING npcName = focusNpc ->GetName( 0 )
Теперь собираем все воедино. Не забываем учесть, что npc не всегда присутствует в фокусе, по этому вывод необходимо производить когда указатель на focusNpc не равен null. Поэтому все операции вписываем под блокif( focusNpc )
Код на данном этапе должен иметь такой вид:
C++:#include <UnionAfx.h> #ifdef __G2A namespace Gothic_II_Addon { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } #endif void Loop() { }
Расширение кода на все движки:
Копируем код, меняя в нем пространство имен и определение препроцессора:
В функции Loop добавляем код вызова функций PrintScreen.
Чтобы вызывать только те функции, которые относятся к соответствующему движку - обратимся к экземпляру Union и запросим текущую версию движка методом GetEngineVersion.
TEngineVersion engineVersion = Union.GetEngineVersion();
Поставим проверки на соответствие и вызовем функции.
C++:if( engineVersion == Engine_G1 ) Gothic_I_Classic::PrintScreen();
Но как говорилось ранее, собирая проект для одного движка - остальные будут выдавать ошибку:
Поэтому также обернем вызовы под #ifdef, исключив из построения ненужные вызовы:
C++:void Loop() { TEngineVersion engineVersion = Union.GetEngineVersion(); #ifdef __G1 if( engineVersion == Engine_G1 ) Gothic_I_Classic::PrintScreen(); #endif #ifdef __G1A if( engineVersion == Engine_G1A ) Gothic_I_Addon::PrintScreen(); #endif #ifdef __G2 if( engineVersion == Engine_G2 ) Gothic_II_Classic::PrintScreen(); #endif #ifdef __G2A if( engineVersion == Engine_G2A ) Gothic_II_Addon::PrintScreen(); #endif }
Тестовая компиляция и запуск
Компилируем плагин в конфигурации G2A Release. Проект должен быстро собраться, поскольку отключены 3 движка. Запускаем плагин, заходим в игру и проверяем, чтобы при наведении фокуса на npc выводилось его имя.
Финальная компиляция и запуск:
Включаем конфигурацию сборки Release. Замечаем, что стали активными все блоки, обернутые ранее под команду #ifdef. Это значит, что в игру вступили все движки и они готовы к работе в кроссплатформенном режиме. Компилируем проект, это займет значительно больше времени, чем при тестовом запуске.
Результаты:
Gothic I Classic
Gothic I Addon
Gothic II Classic
Gothic II Addon
Код без препроцессорных команд:
C++:namespace Gothic_I_Classic { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } namespace Gothic_I_Addon { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } namespace Gothic_II_Classic { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } namespace Gothic_II_Addon { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } void Loop() { TEngineVersion engineVersion = Union.GetEngineVersion(); if( engineVersion == Engine_G1 ) Gothic_I_Classic::PrintScreen(); if( engineVersion == Engine_G1A ) Gothic_I_Addon::PrintScreen(); if( engineVersion == Engine_G2 ) Gothic_II_Classic::PrintScreen(); if( engineVersion == Engine_G2A ) Gothic_II_Addon::PrintScreen(); }
* This tutorial will use preprocessor instructions to separately compile cross-platform code. If this is too complicated for you, I’ll add an example code without them at the bottom of the post. However, I strongly recommend that you delve into the implementation using a preprocessor.
To get started, take the previous plugin. We will test the work on Gothic II: NoTR, and then we will write the code for the rest.
Set the project configuration to G2A Release.
Workspace preparation
For convenience, create a new source file under the directory Plugin:
Each engine interface is wrapped under namespaces (Gothic_I_Classic, Gothic_I_Addon, Gothic_II_Classic, Gothic_II_Addon ). Because we are writing test code for Gothic II NoTR, we define the space Gothic_II_Addon.
As a result, we get a file with something like this:
C++:#include <UnionAfx.h> namespace Gothic_II_Addon { }
However, if we want to change the configuration of the project for a different engine, the compiler will throw out an error that the code inside Gothic_II_Addon is unknown to it.
We’ll add a command over namespace #ifdef __G2A. Due to it, the compiler will be able to exclude all unnecessary code from construction when it will not be needed.
This code will be the base for all cross-platform projects:
C++:#include <UnionAfx.h> #ifdef __G2A namespace Gothic_II_Addon { } #endif
Adding a loop Function
The easiest way to call functions from different source code files is to use the keyword extern. At the end of our file, create a function Loop, and in Application.cpp define it via extern. Call the function from a predefined function Game_Loop.
C++:// Example.cpp #include <UnionAfx.h> #ifdef __G2A namespace Gothic_II_Addon { } #endif void Loop() { }
C++:// Application.cpp extern void Loop(); void Game_Loop() { Loop(); }
Display text
In Example.cpp in Gothic_II_Addon let's add text output function. Текст, как можно догадаться, будет the text, as you might guess, will be displayed cyclically by calling from the function Loop.
The output will be produced via the viewport.screen.
screen - is an instance of zCView (you can see it in the file zView.h). It has the method Print for printing text with coordinates, PrintCX with text horizontal align, PrintCY with vertical align and PrintCXY text in the screen center. Let's use the last one.
Example: screen->PrintCXY( "Hello" );
As the text, we take information about the name of the character in the focus of the player.
The player is defined in global variable player of the class oCNpc.
The class has two methods GetFocusVob - any object in focus and GetFocusNpc - npc in focus
Example: oCNpc* focusNpc = playerGetFocusNpc();
Next, you need to get the name npc. NPC has an array of names of 5 lines. We need the first one. We use the method GetName with index 0.
Example: zSTRING npcName = focusNpc ->GetName( 0 )
Now we are putting it all together. Do not forget to take into account that npc is not always present in focus, therefore, the conclusion must be made when the pointer to focusNpc no equal null. Therefore, we write all operations under the blockif( focusNpc )
The code at this stage should look like this:
C++:#include <UnionAfx.h> #ifdef __G2A namespace Gothic_II_Addon { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } #endif void Loop() { }
Code extension to all the engines
We copy the code, changing the namespace and the definition of the preprocessor in it:
In function Loop add function call codePrintScreen.
To call only those functions that relate to the corresponding engine, we use the instance of Union and request the current version of the engine using the method GetEngineVersion.
TEngineVersion engineVersion = Union.GetEngineVersion();
We put the checks for compliance and call the function.
C++:if( engineVersion == Engine_G1 ) Gothic_I_Classic::PrintScreen();
But as mentioned earlier, when collecting a project for one engine, the rest will give an error:
Therefore, we also wrap the calls under #ifdef, eliminating unnecessary calls from the construction:
C++:void Loop() { TEngineVersion engineVersion = Union.GetEngineVersion(); #ifdef __G1 if( engineVersion == Engine_G1 ) Gothic_I_Classic::PrintScreen(); #endif #ifdef __G1A if( engineVersion == Engine_G1A ) Gothic_I_Addon::PrintScreen(); #endif #ifdef __G2 if( engineVersion == Engine_G2 ) Gothic_II_Classic::PrintScreen(); #endif #ifdef __G2A if( engineVersion == Engine_G2A ) Gothic_II_Addon::PrintScreen(); #endif }
Test compilation and launch
We compile the plugin in the G2A Release configuration. The project should be quickly assembled, since 3 engines are disabled. We launch the plugin, go into the game and check that when focusing on npc its name is displayed.
Final compilation and launch:
Enable build configuration Release. We notice that all blocks wrapped previously under the command have become active #ifdef. This means that all the engines have entered the game and they are ready to work in cross-platform mode. We compile the project, it will take much longer than with a test run.
Results:
Gothic I Classic
Gothic I Addon
Gothic II Classic
Gothic II Addon
Code without preprocessor commands:
C++:namespace Gothic_I_Classic { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } namespace Gothic_I_Addon { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } namespace Gothic_II_Classic { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } namespace Gothic_II_Addon { void PrintScreen() { oCNpc* focusNpc = player->GetFocusNpc(); if( focusNpc ) { zSTRING npcName = focusNpc->GetName( 0 ); screen->PrintCXY( npcName ); } } } void Loop() { TEngineVersion engineVersion = Union.GetEngineVersion(); if( engineVersion == Engine_G1 ) Gothic_I_Classic::PrintScreen(); if( engineVersion == Engine_G1A ) Gothic_I_Addon::PrintScreen(); if( engineVersion == Engine_G2 ) Gothic_II_Classic::PrintScreen(); if( engineVersion == Engine_G2A ) Gothic_II_Addon::PrintScreen(); }
Последнее редактирование модератором: