Введение

В этом уроке мы рассмотрим процесс создания нового игрового типа (Game Type). Урок будет состоять из двух частей:

Добавления типа в список игровых типов, чтобы он был доступен в редакторе карт,
Создания игрового окна.
Для этого урока мы создадим простой игровой тип и сделаем для него класс игрового окна с интерфейсом пользователя.

Добавление типа в список

Чтобы наш игровой тип появился в списке игровых типов карты, необходимо отредактировать файл GameMap.cs проекта GameEntities.

Список игровых типов находится в перечислении GameTypes.

Код:
public enum GameTypes
{
	None,
	Action,
	RTS,
	TPSArcade,
	TurretDemo,
	JigsawPuzzleGame,
 
	//Put here your game type.
	NewGameType
}

Добавляем новый тип после комментария "Put here your game type."

После чего компилируем проект и запускаем редактор карт. Новый игровой тип появится в списке.

https://a.radikal.ru/a28/1905/e8/c2caa013af90.jpg

Создание игрового окна

Создание простого игрового окна

Начнем создание игрового окна с пустого класса. Все классы игровых окон в NeoAxis Engine унаследованы от базового класса GameWindow.

Откроем проект Game и добавим к нему новый файл NewGameTypeWindow.cs. Скопируем туда следующий код:

Код:
using System;
using System.Collections.Generic;
using System.Text;
using Engine;
using Engine.UISystem;
using Engine.MathEx;
using Engine.EntitySystem;
using Engine.MapSystem;
 
namespace Game
{
    class NewGameTypeWindow : GameWindow
    {
    }
}

Чтобы закончить наш простейший класс игрового окна, в него необходимо добавить реализацию абстрактного метода OnGetCameraTransform. Этот метод сообщает движку о положении камеры. Мы будем передавать данные о положении камеры, которая сохранилась в карте после редактора карт.

Код:
protected override void OnGetCameraTransform(out Vec3 position, out Vec3 forward,
    out Vec3 up, ref Degree cameraFov)
{
    //Позиция камеры
    position = Map.Instance.EditorCameraPosition;
 
    //Направление взгляда камеры
    forward = Map.Instance.EditorCameraDirection.GetVector();
 
    //Угол обзора камеры
    cameraFov = Map.Instance.Fov;
 
    //Вертикальное направление
    up = Vec3.ZAxis;
}

На этом простейший класс готов. Теперь нам нужно дать движку возможность использовать наш класс игрового окна.

Все в том же проекте Game откроем файл GameEngineApp.cs. В нем найдем метод CreateGameWindowByGameType. Как видим, в нем создаются игровые окна для стандартных игровых типов NeoAxis Engine.

Код:
GameWindow CreateGameWindowByGameType( GameMap.GameTypes gameType )
{
    switch( gameType )
    {
    case GameMap.GameTypes.Action:
    case GameMap.GameTypes.TPSArcade:
        return new ActionGameWindow();
 
    case GameMap.GameTypes.RTS:
        return new RTSGameWindow();
 
    case GameMap.GameTypes.TurretDemo:
        return new TurretDemoGameWindow();
 
    case GameMap.GameTypes.JigsawPuzzleGame:
        return new JigsawPuzzleGameWindow();
 
    //Here it is necessary to add a your specific game mode.
    case GameMap.GameTypes.NewGameType:
        return new NewGameTypeWindow();
    }
 
    return null;
}

Добавляем обработку нашего игрового типа после комментария "Here it is necessary to add a your specific game mode.".

Скомпилируем наш проект.

Приступим к тестированию нашего игрового типа. Для демонстрации нам понадобится карта. В качестве примера можно взять WindowAppExample.map. Выставим в ее настройках параметр GameType (свиток GameMap в настройках карты) в значение NewGameType.

https://c.radikal.ru/c21/1905/13/ef8dd1192823.jpg

После этого запустим карту в режиме симуляции.

https://d.radikal.ru/d38/1905/00/5101bafc0743.jpg

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

Расширение класса игрового окна
Начнем с того, что воспользуемся некоторой информацией о нашей карте - WindowAppExample.map. На этой карте расположено несколько камер, почему бы нам не понаблюдать за игровым миром через одну из них?

Перепишем метод OnGetCameraTransform. Мы обратимся к камере через ее имя, получив тем самым доступ к информации о ее трансформации. Также, на случай, если игровой тип будет использоваться на другой карте, где нет камеры с таким именем, мы оставим прежний способ получения данных о трансформации - из сохраненного в карте положении карты редактора.

Код:
protected override void OnGetCameraTransform(out Vec3 position, out Vec3 forward,
    out Vec3 up, ref Degree cameraFov)
{
    //Получаем доступ к камере
    MapCamera mapCamera = Entities.Instance.GetByName("MapCamera_0") as MapCamera;
 
    //Если камера найдена
    if (mapCamera != null)
    {
        position = mapCamera.Position;
        forward = mapCamera.Rotation * new Vec3(1, 0, 0);
        cameraFov = mapCamera.Fov;
    }
    //Иначе получаем трансформацию камеры редактора
    else
    {
        position = Map.Instance.EditorCameraPosition;
        forward = Map.Instance.EditorCameraDirection.GetVector();
        cameraFov = Map.Instance.Fov;
    }
 
    //Вертикальное направление
    up = Vec3.ZAxis;
}

Теперь перейдем к интерактивности. Добавим к нашем игровому типу меню, состоящее из одной кнопки: Create Box. Пусть по ее нажатию на карту падает ящик.

Итак, для начала нам понадобится пользовательский интерфейс (GUI). Для простоты он состоит из единственной кнопки Create Box.

https://b.radikal.ru/b18/1905/39/09d3149f3f62.jpg

Перед нами встают две задачи:

Прикрепить к игровому окну пользовательский интерфейс,
Написать метод, который бы добавлял на карту ящик при нажатии на кнопку.
Сначала определим переменную, осуществляющую доступ к пользовательскому интерфейсу. После этого добавим реализацию метода OnAttach. Он вызывается при создании игрового окна. В нем мы создадим пользовательский интерфейс (загрузим из файла) и добавим его к окну. Кроме того, мы назначим кнопке CreateBox реакцию на нажатие. Вот так будет выглядеть код:

Код:
//Переменная для доступа к пользовательскому интерфейсу игрового окна
Control hudControl;
 
protected override void OnAttach()
{
    //Вызов родительского метода
    base.OnAttach();
 
    //Загрузка пользовательского интерфейса
    hudControl = ControlDeclarationManager.Instance.CreateControl("Gui\\NewGameTypeHUD.gui");
    //Добавление GUI к окну
    Controls.Add(hudControl);
 
    //Добавляем реакцию на нажатие кнопки
    ((Button)hudControl.Controls["CreateBox"]).Click += CreateBoxButton_Click;
}

Для завершения нашего игрового класса недостает функции добавления ящика на карту. Вот код этой функции:

Код:
void CreateBoxButton_Click( Button sender )
{
    //Если имеется доступ к карте
    if (Map.Instance != null)
    {
        //Создаем новый игровой объект - ящик
        MapObject box = (MapObject)Entities.Instance.Create("Box", Map.Instance);
 
        //Задаем позицию ящика
        box.Position = new Vec3(1.6f, 18.0f, 10.0f);
 
        //Завершаем инициализацию ящика
        box.PostCreate();
    }
}

На этом класс игрового окна закончен, осталось только скомпилировать наш проект.

Для того, чтобы увидеть игровой тип в действии запустим карту WindowAppExample.map в режиме симуляции (параметр GameType у карты должен быть выставлен в значение NewGameType).

Чтобы протестировать пользовательский интерфейс нашего игрового типа, нажмем на кнопку Create Box, после чего на карту должен упасть ящик.

https://a.radikal.ru/a40/1905/43/8a2b277d1d61.jpg

Заключение

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

ActionGameWindow - Игровое окно для реализации игры или других проектов, где нужно управлять персонажем от первого лица или третьего лица.
TurretDemoGameWindow - Пример надстройки над ActionGameWindow, для реализации карты TurretDemo.
RTSGameWindow - Пример реализации стратегии в реальном времени. Смотрите карту RTSDemo для примера.
JigsawPuzzleGameWindow - Пример реализации настольной игры собирания пазла. Поддержка сети для нескольних игроков.