Введение
В этом уроке мы научимся создавать классы игровых объектов. Научившись создавать свои классы игровых объектов, вы сможете делать игровые объекты любой сложности, отвечающие любым нуждам.
В качестве примера, мы создадим тип игрового объекта, который меняет свой материал при получении повреждения (например, при попадании пули).
Данный урок состоит из двух частей:
Создание класса игрового объекта,
Создание типа игрового объекта.
В первой части пойдет речь непосредственно о создании нового класса игрового объекта. Во второй - о создание типа (.type файл), основанном на новом классе.
Кстати, создаваемыей нами объект уже присутствует в SDK. Вы можете просто читать урок и не терять времени на написание кода с нуля. Исходный код находится в файле ExampleMagicObject.cs проекта GameEntities. Файлы описания типа расположены в папке Bin\Data\Types\Dynamic\ExampleMagicBall.
Создание класса игрового объекта
Создание классов
Приступим к созданию нового игрового класса. Запустим sln-файл из директории "Game\Src". Откроем проект GameEntities. Добавим к нему новый файл ExampleMagicObject.cs. Скопируем туда следующий код:
using System; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Drawing.Design; using Engine.MathEx; using Engine.Renderer; using Engine.MapSystem; using Engine.PhysicsSystem; namespace GameEntities { public class ExampleMagicObjectType : DynamicType { } public class ExampleMagicObject : Dynamic { ExampleMagicObjectType _type = null; public new ExampleMagicObjectType Type { get { return _type; } } } }
Как видим, в коде представлено два класса:
ExampleMagicObjectType - это класс, который будет отвечать за представление типа объекта в редакторе ресурсов. В этом классе обычно задаются свойства, которые будут настраиваться в редакторе типов объектов.
ExampleMagicObject - это класс самого объекта, экземпляры которого будут созданы в игре. Один экземпляр, для каждого объекта на карте.
Наши классы, наследуются, соответственно, от классов DynamicType и Dynamic. Класс Dynamic реализует динамические объекты и лежит в основе многих других классов. Нам он подходит, т.к. у него есть метод OnDamage - реакция на повреждение объекта.
Обратим внимание на следующую строчку:
ExampleMagicObjectType _type = null; public new ExampleMagicObjectType Type { get { return _type; } }
Она обеспечивает связь между типом объекта и игровым кодом, т.е. связывает два, созданных нами класса. Подобный код является обязательным для любого класса игрового объекта.
Добавление свойств для типа
Добавим атрибут к нашему игровому типу, который можно будет редактировать в редактор игровых объектов.
public class ExampleMagicObjectType : DynamicType { [FieldSerialize] string blinkMaterialName; /// <summary> /// Gets or sets the name of a replacing material. /// </summary> [Editor( typeof( EditorMaterialUITypeEditor ), typeof( UITypeEditor ) )] public string BlinkMaterialName { get { return blinkMaterialName; } set { blinkMaterialName = value; } } }
Атрибут BlinkMaterialName содержит имя материала, на который будет заменяться материал объекта при выстреле.
Обратим внимание на следующую строчку.
[Editor( typeof( EditorMaterialUITypeEditor ), typeof( UITypeEditor ) )]
За счет нее достигается возможность выбора материала по нажатию кнопки "..." у того или иного параметра. Тип EditorMaterialUITypeEditor указывает, что пользователь должен будет выбрать файл именно с материалом.
Переменная blinkMaterialName хранит имя материала, а [FieldSerialize] указывает на то, что этот атрибут будет записан в файл при сохранении игрового типа.
MeshObject blinkMeshObject; string originalMaterialName;
blinkMeshObject будет хранить в себе трехмерную модель объекта, а originalMaterialName его исходный материал.
Следующий шаг. Добавим к нашему классу обработку метода OnPostCreate, вызываемый после создания объекта.
/// <summary>Overridden from <see cref="Engine.EntitySystem.Entity.OnPostCreate(Boolean)"/>.</summary> protected override void OnPostCreate( bool loaded ) { base.OnPostCreate( loaded ); //To find the first attached mesh foreach( MapObjectAttachedObject attachedObject in AttachedObjects ) { MapObjectAttachedMesh attachedMesh = attachedObject as MapObjectAttachedMesh; if( attachedMesh != null ) { blinkMeshObject = attachedMesh.MeshObject; break; } } //To save the original name of a material if( blinkMeshObject != null ) originalMaterialName = blinkMeshObject.SubObjects[ 0 ].MaterialName; }
Первая строчка вызывает родительский метод OnPostCreate:
base.OnPostCreate( loaded );
Затем среди прикрепленных объектов отыскивается меш и записывается в переменную blinkMeshObject:
foreach( MapObjectAttachedObject attachedObject in AttachedObjects ) { MapObjectAttachedMesh attachedMesh = attachedObject as MapObjectAttachedMesh; if( attachedMesh != null ) { blinkMeshObject = attachedMesh.MeshObject; break; } }
Наконец, в переменную originalMaterialName записывается имя исходного материала меша:
if( blinkMeshObject != null ) originalMaterialName = blinkMeshObject.SubObjects[ 0 ].MaterialName;
Настало время написать функцию, которая бы меняла материал объекта при его повреждении. За повреждение объекта отвечает метод OnDamage. Добавим его к нашему классу:
//This method is called when the entity receives damages protected override void OnDamage( MapObject prejudicial, Vec3 pos, Shape shape, float damage, bool allowMoveDamageToParent ) { base.OnDamage( prejudicial, pos, shape, damage, allowMoveDamageToParent ); if( blinkMeshObject != null ) { //To change a material if( blinkMeshObject.SubObjects[ 0 ].MaterialName == originalMaterialName ) blinkMeshObject.SetMaterialNameForAllSubObjects( Type.BlinkMaterialName ); else blinkMeshObject.SetMaterialNameForAllSubObjects( originalMaterialName ); } }
Здесь сначала вызывается родительский метод OnDamage:
base.OnDamage( prejudicial, pos, shape, damage, allowMoveDamageToParent );
Затем меняется материал объекта. Если стоит исходный материал, то меняется на blinkMaterialName. Если используется blinkMaterialName, то меняется на исходный:
if( blinkMeshObject.SubObjects[ 0 ].MaterialName == originalMaterialName ) blinkMeshObject.SetMaterialNameForAllSubObjects( Type.BlinkMaterialName ); else blinkMeshObject.SetMaterialNameForAllSubObjects( originalMaterialName );
На этом написание кода для нашего объекта закончено.
Создание типа игрового объекта
Подготовка ресурсов
Игровые типы создаются и настраиваются в редакторе типов объектов (часть редактора ресурсов).
Запустим редактор ресурсов. Пусть наш тип располагается в директории "Data\Types\Dynamic\ExampleMagicBall".
Перед тем как создать тип, подготовим другие необходимые ресурсы. Нам понадобятся:
Меш (в нашем примере используется обыкновенная сфера),
Материал, который будет применяться к мешу после повреждений,
Физическая модель (для нашего примера опять же подойдет сфера).
Кстати, данные ресурсы уже присутствуют в папке "Data\Types\Dynamic\ExampleMagicBall" как пример. Будет их использовать.
Создание типа
В появившемся окне выберем тип ресурса: Entity Type. И нажмем кнопку Continue.
Теперь укажем имя нового типа: ExampleMagicBall, класс: ExampleMagicObject, созданный нами. После чего нажмем кнопку Next.
Редактирование типа
Дважды щелкнем на появившемся файле ExampleMagicBall.type, чтобы перейти в режим редактирования.
Теперь наша задача настроить модель (меш), физическую модель и указать "бликующий" материал (свойство BlinkMaterialName).
Начнем с настройки меша. Кликнем правой кнопкой по рабочему окну и в меню Attach Object выберем пункт Mesh.
В качестве примера, выберем файл Ball.mesh в директории "Data\Types\Dynamic\Ball".
Теперь настроим свойство BlinkMaterialName. Выделим игровой объект в списке Objects и нажмем кнопку "..." у параметра BlinkMaterialName.
Выберем заранее подготовленный материал ExampleMagicBallBlinkMaterial.highMaterial из текущей директории.
Теперь нужно присоединить к нашему типу физическую модель. Параметр PhysicsModel зададим нашей физической моделью - ExampleMagicBall.physics. Чтобы удостовериться, что физическая модель нам подходит, поставим флажок Show Physics в нижней панели редактора объектов. Радиус сферы меша и радиус сферы физической модели должны совпадать.
Наконец, не забудем поставить свойство AllowEditorCreate в True, чтобы объект можно было создать в редакторе карт.
Теперь осталось сохранить созданный нами тип игрового объекта и проверить его работу.
Проверка работы
Настало время увидеть наш тип в действии. Добавим объект ExampleMagicBall на какую-либо карту. Объект в списке типов можно найти в директории "Types\Dynamic".
Запустим режим симуляции. Возьмемся за Shotgun и протестируем магический шар. Вот так ExampleMagicBall выглядит перед выстрелом:
А вот так сразу после выстрела:
Итоги
Наш урок подошел к концу. Мы научились создавать классы игровых объектов и настраивать, основанные на них игровые типы.
Система объектов, включающая в себя классы и типы игровых объектов - это мощнейший инструмент для написания игровой логики в движке NeoAxis.