Введение

В этом уроке мы научимся создавать классы игровых объектов. Научившись создавать свои классы игровых объектов, вы сможете делать игровые объекты любой сложности, отвечающие любым нуждам.

В качестве примера, мы создадим тип игрового объекта, который меняет свой материал при получении повреждения (например, при попадании пули).

Данный урок состоит из двух частей:

Создание класса игрового объекта,
Создание типа игрового объекта.
В первой части пойдет речь непосредственно о создании нового класса игрового объекта. Во второй - о создание типа (.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" как пример. Будет их использовать.

https://c.radikal.ru/c16/1905/68/f712d5b52635.jpg
https://d.radikal.ru/d37/1905/bb/8c4e6d26ee36.jpg

Создание типа

https://a.radikal.ru/a33/1905/68/3cd30f6b5c97.jpg

В появившемся окне выберем тип ресурса: Entity Type. И нажмем кнопку Continue.

https://b.radikal.ru/b11/1905/42/31d3b120dc60.jpg

Теперь укажем имя нового типа: ExampleMagicBall, класс: ExampleMagicObject, созданный нами. После чего нажмем кнопку Next.

https://a.radikal.ru/a11/1905/f5/a51b5556b2b6.jpg

Редактирование типа

Дважды щелкнем на появившемся файле ExampleMagicBall.type, чтобы перейти в режим редактирования.

Теперь наша задача настроить модель (меш), физическую модель и указать "бликующий" материал (свойство BlinkMaterialName).

Начнем с настройки меша. Кликнем правой кнопкой по рабочему окну и в меню Attach Object выберем пункт Mesh.

https://b.radikal.ru/b04/1905/19/7bc599ab1e9b.jpg

В качестве примера, выберем файл Ball.mesh в директории "Data\Types\Dynamic\Ball".

https://b.radikal.ru/b26/1905/57/933815e29956.jpg

Теперь настроим свойство BlinkMaterialName. Выделим игровой объект в списке Objects и нажмем кнопку "..." у параметра BlinkMaterialName.

https://d.radikal.ru/d40/1905/7a/7b902b8ab08c.jpg

Выберем заранее подготовленный материал ExampleMagicBallBlinkMaterial.highMaterial из текущей директории.

https://b.radikal.ru/b32/1905/6c/8752e5a11be9.jpg

Теперь нужно присоединить к нашему типу физическую модель. Параметр PhysicsModel зададим нашей физической моделью - ExampleMagicBall.physics. Чтобы удостовериться, что физическая модель нам подходит, поставим флажок Show Physics в нижней панели редактора объектов. Радиус сферы меша и радиус сферы физической модели должны совпадать.

https://d.radikal.ru/d15/1905/ab/b7d9162f7d30.jpg

Наконец, не забудем поставить свойство AllowEditorCreate в True, чтобы объект можно было создать в редакторе карт.

Теперь осталось сохранить созданный нами тип игрового объекта и проверить его работу.

Проверка работы

Настало время увидеть наш тип в действии. Добавим объект ExampleMagicBall на какую-либо карту. Объект в списке типов можно найти в директории "Types\Dynamic".

https://a.radikal.ru/a12/1905/91/71bb50841357.jpg

Запустим режим симуляции. Возьмемся за Shotgun и протестируем магический шар. Вот так ExampleMagicBall выглядит перед выстрелом:

https://a.radikal.ru/a04/1905/48/0f85bbe2766f.jpg

А вот так сразу после выстрела:

https://a.radikal.ru/a07/1905/4d/071488ae89f9.jpg

Итоги

Наш урок подошел к концу. Мы научились создавать классы игровых объектов и настраивать, основанные на них игровые типы.

Система объектов, включающая в себя классы и типы игровых объектов - это мощнейший инструмент для написания игровой логики в движке NeoAxis.