Кодинг в UDK

(Материалы по UDK)

Модератор: Buxyr

Аватара пользователя
KrisGames

Универсал Коммандо Лидер
Командор
Сообщения: 1140
Рег. Ср апр 28, 2010 10:20 am
Награды: 3
Репутация: 428
Вклад в проект: 75
Откуда: Иваново
Благодарил (а): 22 раза
Поблагодарили: 62 раза

Кодинг в UDK

Сообщение KrisGames » Ср янв 11, 2012 12:11 pm

Конфигурация системы Unreal Engine довольно мощная и зачастую требует сохранения многих переменных.
Эта статья представляет собой метод создания системы сохранения игр для RPG, созданных на UDK

Система сохранения игры будет использовать систему хранения пользовательского интерфейса (UI DataStore System) и директивы PerObjectConfig. Предполагается, что читатель знаком с конфигурационными файлами UDK и синтаксисом C#.

Sapitu
Sapitu (Savegames Are Possible In The UDK/Возможность сохранения игры в UDK) реализует простую систему для сохранения игр жанра RPG. Он обладает способностью сохранять персонажа и его инвентарь.
(Вы можете скачать полный исходный код этого примера в конце статьи)

Sapitu делится на 4 класса. Программа также содержит 2 дополнительных класса: SapituGame и SapituPC , используемые только для игр с внедренной системой Sapitu.

Четыре класса Sapitu заключаются в следующем:
  • Sapitu Это основной класс, который управляет различными частями системы сохранения игр.
  • SapituCharacter Этот класс хранит информацию о персонаже.
  • SapituInventory Этот класс предмета инвентаря, который может принадлежать к SapituCharacter
  • SapituItem Этот класс используется в качестве базы данных для создания записи SapituInventory. Экземпляры этого класса предназначены только для чтения в рамках системы.

Класс Sapitu
Этот класс определяет какой-либо метод для управления, сохранения и загрузки новых символов. Он также содержит методы для создания новых предметов инвентаря и сохраняет ссылку на них в базы данных.

База данных
Метод loadItemDB инициализирует базу данных SapituItem и является частью системы хранения пользовательского интерфейса в Unreal Engine. Эта система также используется для получения информации о типах игры, карты, и оружии в UDK.

Метод getItem предоставляет средство для поиска фактических SapituItem (например, в текущей базе данных имя объекта (свойство Object.name). Это имя (точнее, идентификатор) является лишь программным именем (что-то вроде равкода в Варкрафте).

База данных используется в системе инвентаризации, чтобы найти статичные предметы инвентаря (точнее ссылку на них).

Персонаж
Класс Sapitu является опорной точкой при сохранении и загрузке SapituCharacter.
Метод getCharacters возвращает список всех зарегистрированных на персонаже идентификаторов. Так же, как идентификатор элементов базы данных он относится к свойству Object.name (Еще раз напомню, это не удобное читаемое в игре имя, а внутренний идентификатор!). Этот идентификатор необходим во время загрузки или сохранения персонажей. На ИД есть одно важное ограничение - он не должен содержать пробелов, только буквы в диапозоне [Aa-Zz] и цифры. Могу посоветовать давать им понятные имена, чтобы потом не запутаться.
Загрузка и создание символов, на самом деле похожие процедуры. Обе они используют следующий код:
myChar = new(none, "MyCharId") class'SapituCharacter';

Второй параметр, собственно, определяет название создаваемого/загружаемого объекта.
Для того чтобы провести четкое различие между загрузкой и созданием новых символов в Sapitu мы сначала проверяем, существует ли данный идентификатор, а затем, соответственно, принимаем или возвращаем значение. Если объяснять понятней, то при загрузке мы принимаем при существующем ИД, а при сохранении возвращаем при отсутствующем ИД)
Загрузка персонажа еще не закончена, это только начало. Нам также необходимо инициализировать его инвентарь.
SapituCharacter хранит список идентификаторов для SapityInventory items (SapituCharacter.inventoryRecord ).

Инвентарь. Создание предметов
Предметы инвентаря создаются специальным образом. createInventory – это способ создать новый элемент инвентаря на основе данных SapituItem. Он создает уникальный идентификатор для инвентаря, а затем выполняет специальную конструкцию new(none, inventoryId) class'SapituInventory' . После этого будут загружаться различные переменные, которые были не определены SapituItem (например, как уровень, вес и стоимость).

Класс SapituCharacter
Этот класс определяется как class SapituCharacter extends Object config(Sapitu) perobjectconfig;
Это означает, что некоторые из переменных в классе будут сохранены в файле конфигурации UTSapitu.ini , и что для каждого объекта будет создан отдельный раздел. Последние является результатом работы директивы класса perobjectconfig. Имя объекта имеет важное значение при сохранении, например, потому что оно определяет название раздела.

Этот класс хранит различные конфиги переменных для имени персонажа (уже понятного для человека имени, а не ИД), здоровья и т.д.
Инвентарь персонажа сохраняется иным способом. Добавление и удаление предметов инвентаря должно быть сделано с помощью методов addInventory и removeInventory. Этот способ позволяет обновить массивы inventory и inventoryRecords. Массив inventory не может быть изменен непосредственно. Класс Sapitu запрещает это, но это особый метод. :)
Вы не можете принимать значения из SaveConfig, потому что инвентарь не сохранится должным образом. Метод save перебирает массив инвентаря и принимает значения SaveConfig. Таким образом, все, что относится к персонажу сохраняется правильно.
Класс SapituInventory
Этот класс является фактически самим инвентарем. Он основан на классе SapituItem. Работа с этим классом такая же, как и с SapituCharacter

SapituItem
В отличие от классов SapituCharacter и SapituInventory этот класс является подклассом UTUIResourceDataProvider . Он не объявляется, как config , потому что в родительском классе уже определена его конфигурация (объявлялось при помощи PerObjectconfig).

Также обратите внимание на bSearchAllInis=true в разделе defaultproperties. Эта переменная является частью системы хранения пользовательского интерфейса. Она определяет, что система хранения должна пройти через все файлы конфигурации, чтобы найти объекты.

Загрузка SapituItems не должна быть сделана на основе ранее упомянутой конструкции new(none,id). Чтобы получить все объявленные SapituItem, вы должны использовать следующий код:
class'UTUIDataStore_MenuItems'.static.GetAllResourceDataProviders(class'SapituItem', ProviderList);
SapituItems объявляются вручную, путем добавления строки вроде этой:
[Sword1 SapituItem]
DisplayName = короткий меч Видимое имя
level=(min=1,max=10) Уровень
weight=(base=10,levelMult=0.5) Вес
value=(base=5,levelMult=1) Значение
См. файл UTGame\Config\SapituItemDB.ini для просмотра дополнительных примеров

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

Игра с Sapitu
Чтобы играть с этой системой, просто компилируйте исходник и начните игру со следующим параметром:
udk examplemap?game=sapitu.sapitugame
Класс SapituPC определяет команды, вводимые в консоли:
· createChar <имя>
· saveChar
· loadChar <Id чара>
· printChars
· showChar
· createRandomItem
· createItem <baseItem>
· pickupItem <Id предмета>
· pickupAll
· findItems
· listItemTypes
Пишем ток без <> :)

Заметим, что система, представленная здесь это лишь один из нескольких способов создать систему сохранения игры.
К статье прикреплен файл: Sapitu-source.zip (17 КБ)

Переменные, используемые машинным кодом в файле конфигурации, как правило, имеют простой заголовок раздела. Например, первый раздел, который появляется в конфиг файле DefaultEngnine.ini называется просто . Тем не менее, любая переменная, использующая код UnrealScript как правило, имеет полное имя класса, которое соответствует формату [(package).(classname)]. Так, например, раздел [Engine.Engine] файла DefaultEngine.ini указывает класс движка. Есть некоторые исключения из этого правила, например, раздел [WinDrv.WindowsClient], который представляет собой секцию, ключевые значения которой наследуют C++.

Также имейте в виду, что компилятор кода UnrealScript (UCC - UnrealScript code compiler) использует конфигурационный файл для определения существующих пакетов. В отличие от игрового движка, UCC использует лишь небольшую часть параметров (в основном раздел [Editor.EditorEngine] файла DefaultEngine.ini) для их загрузки.
При первом запуске движка многие файлы конфигурации просто-напросто отсутствуют. Загружается тот конфиг-файл, который использует имя текущего проекта. Например, DefaultEditor.ini переведен на ExampleEditor.ini проекта ExampleGame.

Пользовательский ввод конфигурации может быть использован для назначения клавиш «по умолчанию» при помощи таких функций, как Exec-команды.
Работа с конфигурационными файлами
О формате
Разделы
Файлы конфигурации состоят из разделов, включающих в себя пару «ключ-значение»
Это выглядит примерно так
[Razdel]
Key = Value
Специальные символы
Знак Описание
+ Добавляет строку, если свойство не существует. Полезно для привязки (очень хороший пример – DefaultInput.ini)
- Удаляет строку (нужно указывать точное ее название).
. Добавляет новое свойство.
! Удаляет свойство, не нужно вводить значение, достаточно названия свойства.

Вот пример работы со свойствами:
[Engine.PlayerInput]
Bindings=(Name="Q",Command="Foo")
.Bindings=(Name="Q",Command="Bar")
.Bindings=(Name="Q",Command="Foo")

Комментарии
Большинство людей, похоже, подумали, что точка с запятой обозначает комментарии в файлах конфигурации, но вот пример FConfigFile::ProcessInputFileContents
Из этого могу сказать следующее. Технически любой символ может представлять роль разделителя «ключ-значение». Тобиш это не обязательно «=».
Находящаяся в начале строки точка с запятой работает как комментарий, но на самом деле им не является. Комментарии должны обособляться запятой.
; Это комментарий
, Так что это??
Создание конфигурационных файлов
При создании нового файла конфигурации, который основан на другом конфиге, вы должны включить его в раздел [Configuration].
Например, если файл конфигурации будет в зависимости от конфига Engine.ini, вы должны вписать в начале файла следующее:
[Configuration]
BasedOn=..\Engine\Config\BaseEngine.ini
Чтобы внести дополнения в существующую пару «ключ-значение», которая унаследована от базовой конфигурации, используйте специальный знак «+», а именно:
[Core.System]
+Extensions=mymap
Для удаления такой пары, используем «-»:
[Core.System]
-Extensions=umap
Не нужно указывать + или - если ваш конфиг ни от чего не наследуется.
Сохранение конфига объекта
Unreal Engine имеет возможность сохранять конфигурацию объекта в любой конфигурационный файл.
Объявление имени конфига
Объявление класса содержит имя нового файла конфигурации. Например, класс объявляет, что его переменные будут сохранены в файл конфигурации User.ini. Это будет выглядеть примерно так:
class MyController extends Controller""
config(user);
Если вы не объявите имя для конфига, движок просто сохранит содержимое в файл конфигурации по умолчанию. Вот демонстративный код:
class TestConfig extends Actor
config;
var config int X;

function postbeginplay()
{
X=5;
SaveConfig();
}
Когда актор TestConfig будет создан, он примет значение X и сохранит его конфигурацию. К файлу MYGAME.ini добавится следующее:
[UDN.TestConfig]
X=5
В следующий раз при создании актора TestConfig значение по умолчанию для X будет равняться 5. Даже если вы удалите функцию PostBeginPlay() из актора, X = 5.
Если объявить имя нового файла конфигурации, то файл будет создан. Приведенный ниже пример показывает, что объявляется новый конфигурационный файл:
class TestConfig extends Actor
config(UDN);
Переменные конфигов и их наследование
Переменные конфигов наследуются подклассами. Однако, если SaveConfig() вызывается для младшего класса TestConfig , значение X сохраняется для младшего класса. Пример:
class TestConfigChild extends TestConfig;

function postbeginplay()
{
X=15;
SaveConfig();
}
Когда актор TestConfigChild создан, конфиг для этого объекта сохраняется:
[UDN.TestConfigChild]
X=15

Динамическая и статическая конфигурации
Программисты используют 2 разных метода для сохранения конфигов объекта в конфигурационный файл: динамический и статический. В принципе, это просто означает, что выполняется сохранение переменной, если вы вызываете SaveConfig() на объект. Вызов StaticSaveConfig() по переменной класса будет записывать значение объекта по умолчанию в файл конфигурации.
Следующий фрагмент сохраняет значение по умолчанию для X:
class'TestConfigChild'.default.X = 30;
class'TestConfigChild'.static.StaticSaveConfig();
Значение по умолчанию X записывается в файл конфигурации.
[UDN.TestConfigChild]
X=30
Доступные конфигурационные файлы
Файлы конфигурации находятся в каталоге Config вашего проекта.
Вот список конфигурационных файлов, доступных при использовании стандартного проекта Unreal Engine:
· DefaultEditor
· DefaultEditorKeybindings
· DefaultEditorUserSettings
· DefaultEngine
· DefaultGame
· DefaultInput
· DefaultUI

UnrealScript
К одному классу UnrealScript можно прикрепить только один DLL-файл. Библиотеки могут быть загружены из каталога Binaries\Win32\UserCode.

Импортированная функции объявляется как функция UnrealScript с использованием директивы dllimport.
Когда импортированная из DLL функция полностью объявляется, становится невозможным создать подкласс для класса DLLBind.

Например:
class TestDLLPlayerController extends PlayerController
DLLBind(TestDLL);
dllimport final function CallDLL1(out string s);
dllimport final function vector CallDLL2(float x, float y, float z);
dllimport final function bool CallDLL3(string s, int i[2], out float f, out vector v);

Когда класс TestDLLPlayerController загружен, он начинает вызывать Binaries\Win32\UserCode\TestDLL.dll. Если DLL не может быть вызвана, в логах появится соответствующая заметка. Функция попытается найти позицию для аттача DLL (их 3 - CallDLL1, CallDLL2 и CallDLL3). По завершению эта функция может быть вызвана как и все обычные функции UnrealScript. Если не удастся приаттачить, то эта функция попросту не будет работать.

DLL
Функции могут быть реализованы в DLL при помощи C++ следующим образом:
extern "C"
{
struct FVector
{
float x,y,z;
};

__declspec(dllexport) void CallDLL1(wchar_t* s)
{
MessageBox(0, s, L"Inside the DLL: CallDLL1", MB_OK);
// возвращаем строковый параметр
int len = wcslen(s);
for(int i=0; i<len>>1;i++)
{
wchar_t temp = s__;
s__ = s[len-i-1];
s[len-i-1] = temp;
}
}

__declspec(dllexport) FVector* CallDLL2(float x, float y, float z)
{
static FVector result; // объявление в памяти и возврат функции.
result.x = x;
result.y = y;
result.z = z;
return &result;
}

__declspec(dllexport) bool CallDLL3(wchar_t* s, int i[2], float* f, FVector* V)
{
wchar_t temp[1024];
swprintf_s(temp, 1024, L"string: %s, i: {%d,%d}, float: %f, V was (%f,%f,%f)", s, i[0], i[1], *f, V->x, V->y, V->z);
V->x = (float)i[0];
V->y = (float)i[1];
V->z = (*f);
return (MessageBox(0, temp, L"Inside the DLL: CallDLL3", MB_OKCANCEL) == IDOK);
}
}

Выгрузка при помощи API
Когда класс объекта класса DLLBind (ток не путайтесь, один класс относится к объекту, второй к библиотеке) уничтожен, DLL будет выгружена при помощи FreeLibrary API. Если быть точнее, то это произойдет когда будет удалена последняя ссылка на класс объекта. Затем вручную мы должны очистить нашу библиотеку функцией DllMain().

Поддерживаемые типы параметров
Поддерживаются следующие типы параметров:
Тип в UnrealScript Тип в Си Комментарий
int value int value (32-bit) Передает значение value
int value[..] int* value (32-bit) Передает ссылку reference
out int value int* value (32-bit) Передает ссылку reference
float value float value Передает значение value
float value[..] float* value Передает ссылку reference
out float value float* value Передает ссылку reference
byte value unsigned char value Передает значение value
byte value[..] unsigned char* value Передает ссылку reference
out byte value unsigned char* value Передает ссылку reference
string value wchar_t* value Передает ссылку reference
out string value wchar_t* value Передает ссылку reference (смотрите предупреждение ниже!)
struct foo struct foo* value Передает ссылку reference. DLL должна объявить структуру в соответствии со структурой UnrealScript
out struct foo struct foo* value Передает ссылку reference
Поддерживаются следующие типы возврата значения:
Тип в UnrealScript Тип в Cи Комментарий
Int Int (32-bit) Непосредственно возвращает значение
float float value Непосредственно возвращает значение
byte unsigned char Непосредственно возвращает значение
bool unsigned int (32-bit) Непосредственно возвращает значение (отличное от нуля будет равно true)
string wchar_t * указатель значения strcpy'd в строке UnrealScript. См. предупреждение ниже
struct foo struct foo* структура данных memcpy'd в структуре UnrealScript. См. предупреждение ниже

Ограничения
· Поддерживаются только строки Unicode (UTF-16). DLL необходимо компилировать с Юникодом.
· Для 32-разрядной версии UDK доступны только 32-разрядные библиотеки DLL. Аналогично для 64-разрядной версии.
· Библиотеки могут быть загружены только из каталога Binaries\Win32\UserCode.
· Под Win32, поддерживается только STDCALL (это вызов конвенций).

Предупреждения
· При изменении строки из параметра, новая строка должна быть короче или же равная по длине предыдущему значению строки. В противном случае возможна перезапись данных в памяти или же вовсе крах системы.
· При возврате структур или строки, DLL возвращает указатель и Unreal копирует значение обратно в UnrealScript. Однако DLL отвечает за распределение памяти для хранения данных указателя. Например, она может вернуть указатель на статическую переменную, как это делается в CallDLL2. Возврат указателя на переменную, объявленную в стеке приведет к фатал еррору, потому что стек разрушается, когда DLL возвращает функцию.
· В UnrealScript структуры выравниваются на 4 байта, поэтому желательно использовать опцию / Zp4 при компилировании вашего DLL.
· Можно объявить структуру, содержащую тип данных Unreal. Однако DLL этого не поймет. Так что делать так не рекомендую.
Собственно в конце статьи, прикрепляю пример реализации вызова функции из DLL в UDK.

Использование Актор-компонентов
ActorComponent и PrimitiveComponent
ActorComponent это класс объекта, который может быть установлен в мире.
PrimitiveComponent это тот ActorComponent, который отвечает за рендеринг или столкновение объектов.
Добавление компонентов
Пример:
class Actor extends Object;

var const array<ActorComponent> Components;

defaultproperties
{
Begin Object Class=SpriteComponent Name=Sprite
Sprite=S_Actor
HiddenGame=True
End Object
Components.Add(Sprite)
}
Изменение компонентов
Пример:
class Trigger extends Actor;

defaultproperties
{
Begin Object Name=Sprite
Sprite=S_Trigger
End Object
}
Удаление компонентов
Пример:
class StaticMeshActor extends Actor;

defaultproperties
{
Components.Remove(Sprite)

}
Прототип компонента Sprite по умолчанию не ссылается на свойства класса StaticMeshActor
Шаблоны компонентов
Шаблоны используются для объявления по умолчанию компонентов актор-класса. Шаблон это именованный объект, который содержит стандартные параметры для компонента стандартной ссылки свойства класса (Вот такое будет краткое определение).
Шаблоны наследуются подклассами класса, в котором они были объявлены. В каждый таком подклассе можно изменить набор индивидуальных свойств, унаследованных от шаблона.
Создание шаблона
Шаблон компонента, объявленный по умолчанию, будут выглядеть так:
Begin Object Class=SpriteComponent Name=Sprite
Sprite=Texture2D'EngineResources.S_Actor'
HiddenGame=True
AlwaysLoadOnClient=False
AlwaysLoadOnServer=False
End Object
Естественно это с учетом свойств класса
Установка и подключение шаблона
Пример:
Begin Object Class=SpriteComponent Name=Sprite
// Здесь присваиваются значения
End Object
Components.Add(Sprite)
Изменение свойств шаблона, унаследованных от старшего класса
Как я уже говорил, шаблон наследуется подклассом класса шаблона. Этот пример наследует щаблон актора Sprite и изменяет только спрайт:
Begin Object Name=Sprite
Sprite=Texture2D'EngineResources.S_SkyLight'
End Object
Динамически созданные компоненты
Для динамического создания компонента используется новый оператор UnrealScript - AttachComponent.
Работают с ним так.
local StaticMeshComponent NewComponent;
NewComponent = new(self) class'StaticMeshComponent';
// Здесь присваиваем статик меш.
AttachComponent(NewComponent);
Для снятие аттача используется метод DetachComponent. Когда на компонент ничего не ссылается он уничтожается.
Общие параметры PrimitiveComponent
Тип Имя Описание
Boolean AlwaysLoadOnClient Если AlwaysLoadOnClient = False, примитив имеет HiddenGame = True и CollideActors = False, примитив не будет загружен в игру.
Boolean AlwaysLoadOnServer Если AlwaysLoadOnServer = False, примитив имеет CollideActors = False, примитив не будет загружен на сервере.
Boolean BlockActors Если PrimitiveComponent имеет строку collision enabled, BlockActors определяет столкновения примитивов и будет блокировать других участников.
Boolean CastShadow Истинно, если PrimitiveComponent имеет тень, иначе – ложно
Boolean CollideActors Если один из акторов bCollideActors или CollideActors является ложным, примитив не будет просчитывать столкновения.
Boolean HiddenEditor Ложно, если PrimitiveComponent должен быть вынесен в редактор, иначе - истинно.
Boolean HiddenGame Ложно, если PrimitiveComponent должен быть вынесен в игре, иначе истинно

Встроенные типы компонентов
· PrimitiveComponent
· ArrowComponent
· CameraConeComponent
· CylinderComponent
· DrawFrustumComponent
· MeshComponent
o StaticMeshComponent
o SkeletalMeshComponent
o ParticleSystemComponent
o SpriteComponent
· LightComponent
· DirectionalLightComponent
· PointLightComponent
· SpotLightComponent
· SkyLightComponent
· AudioComponent
· HeightFogComponent
· SceneCaptureComponent
· SceneCapture2DComponent
· SceneCaptureCubeMapComponent
· SceneCaptureParaboloidComponent
· SceneCaptureReflectComponent

Sapitu-source.zip
(17.97 КБ) 88 скачиваний

DLLBind_Primer.zip
(9.09 КБ) 83 скачивания


ICQ : 470-451-451
Mail: KrisGames@yandex.ru

Вернуться в «Конструктор UDK»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 0 гостей

 

 

cron