Создаём простой объект на Shot
В этой статье поговорим о том, как добавить в игру простейший объект, используя встроенный язык программирования Shot.
Использовать будем уже существующую кодобазу от игры Comrades, которая, в свою очередь, основана на игровом коде от Quake II, но портирована на Shot и вместо множества разрозненных функций теперь представляет собой симпатичные классы-объекты.
Для начала создадим самый простой объект, который ничего не делает, а просто показывает в игре модель без коллизии. Пусть это будет модель подбираемого предмета 'Power Cube' (но вы конечно можете взять и любую другую).
1. Подготовка к работе
Первым делом мы должны настроить наши исходники для того, чтобы их можно было скомпилировать. Они находятся в XashNT_SDK\src_user
Если у вас уже имеется бета-версия XashNT_SDK, то внутри проектов пути настроены правильным образом и вам остается только ассоциировать в Windows расширения файлов .hsproj с компилятором-линковщиком языка Shot, который расположен по адресу XashNT_SDK\bin\hshot32.exe.
После ассоциирования, файлы .hsproj будут компилироваться по двойному клику на них. Результат автоматически помещается в comrades/client.program для клиента и ../../comrades/server.program для сервера, соответственно.
Проще всего будет вставить новый объект в конец уже имеющегося файла проекта, который служит для такого рода объектов - XashNT_SDK\src_user\ComradesServer\misc.shot
2. Немного теории
Организация кодовой базы Comrades похожа на таковую в бизнес-логике Half-Life. Что необходимо знать:
- Все игровые объекты наследуются от CBaseEntity и их производных, в зависимости от того, какой функционал вам нужен.
Так, если нужна анимированная модель, то наследоваться нужно от CBaseAnimating. Если NPC, то наследование идёт от CBaseMonster.
Для платформы-давилки, разумнее всего выбрать CBaseToggle.
В нашем простейшем случае будет достаточно CBaseEntity.
Важно!
Наследование от того или иного класса не налагает жестких ограничений, поскольку основной функционал предоставляется всё же самим игровым движком. Эти специализированные классы в основном предоставляют удобные обертки для работы с движковыми функциями и различные вспомогательные функции.
При желании вы можете скопировать их в собственный класс, например чтобы получить уникальное сочетание свойств объекта, вроде "монстр-дверь" или "спрайт-поезд". - Для видимых объектов мы должны предварительно закэшировать нашу модель с помощью функции PRECACHE_MODEL, а затем сделать её доступной в игре с помощью функции SetModel. В этом случае, модель будет корректно отображаться сразу при старте игры.
- Объекты с видимой моделью должны содержать как минимум две функции-члена класса для правильной работы. Это
void Precache(void);
и
void Spawn(void); - Чтобы мы могли установить наш новый объект на уровень в редакторе нам недостаточно просто создать класс. Как минимум потому, что неиспользованный класс будет удалён компилятором в рамках оптимизации. Мы должны выполнить инстанцирование и связать наш класс с именем объекта (entity, описывается в FGD-файле), которое и будет добавлено на карту.
На один и тот же класс может ссылаться неограниченное кол-во имён объектов, а вот использование одних и тех же имён инстанцирования с разными классами приведёт к ошибке компиляции.
Типовое связывание объекта с инстансом выглядит так:
LINK_ENTITY_TO_CLASS(misc_banner, CBanner);
где misc_banner это имя объекта, который будет установлен в редакторе на игровой уровень, а CBanner - имя класса, который связывается с этим объектом.
4. Создаём свой первый объект
Итак, теперь мы можем приступить к созданию нового объекта.
Выше мы уже условились, что будем использовать модель от 'Power Cube', а писать код в конце файла XashNT_SDK\src_user\ComradesServer\misc.shot
Добавляем в конец файла:
struct CRotationCube : public CBaseEntity
{
string model_path = "models/items/keys/power.model";
void Precache() { PRECACHE_MODEL(model_path);}
void Spawn() {
Precache();
SetModel(model_path);
}
};
LINK_ENTITY_TO_CLASS(misc_rotation_cube, CRotationCube);Здесь вы видите необходимые функции Spawn и Precache.
Precache загружает модель, причём во время старта игры вам следует вызвать её напрямую из функции Spawn, а во время загрузки сохранённой игры бэкенд вызовет данную функцию автоматически.
Аргумент обоих функций - это относительный (от XashNT_SDK/Comrades/) путь к нашей модели. Вы можете заменить его на встроенную переменную m_model, а путь указывать в редакторе уровней, в настройках нашего объекта в поле "model".
Макроподстановка LINK_ENTITY_TO_CLASS связывает наш класс объекта с именем инстанса misc_rotation_cube.
Компилируем игровой код двойным щелчком по XashNT_SDK\src_user\ComradesServer\server.hsproj
Открываем FGD-файл Джека (XashNT_SDK\J.A.C.K\comrades\quake2_comrades.fgd) и добавляем в конец его объект:
@PointClass base(Keys) studio("models/items/keys/power.model") =
misc_rotation_cube : "Test Entity\n\nRotating cube model." []Собираем тестовый уровень, размещаем на нем info_player_start, light_environment и misc_rotation_cube. Компилируем и запускаем из Джека.
5. Добавляем вращение объекта
Теперь немного усложним наш код - сделаем так, чтобы объект мог вращаться по произвольным осям. Следует отметить, что это будет чисто визуальное вращение без коллизии.
В кодовой базе Comrades есть несколько типов движения объектов:
- MOVETYPE_NONE - объект неподвижен
- MOVETYPE_NOCLIP - объект игнорирует коллизии
- MOVETYPE_PUSH - используется для дверей, поездов и т.п.
- MOVETYPE_STOP - используется для дверей, поездов если объект может быть заблокирован игроком
- MOVETYPE_FOLLOW - используется для жесткого скрепления двух объектов между собой, например для оружия от первого лица и игрока.
- MOVETYPE_WALK - специальный тип для ходьбы игрока
- MOVETYPE_STEP - наземные монстры
- MOVETYPE_FLY - летающие монстры
- MOVETYPE_TOSS - предметы, которые можно выбросить, аптечки или оружие
- MOVETYPE_FLYMISSILE - используется для ракет
- MOVETYPE_BOUNCE - используется для гранат
Как видно, лучше всего для наших целей подойдет MOVETYPE_NOCLIP.
Включить его можно добавив в Spawn() строчку:
m_movetype = MOVETYPE_NOCLIP;
Однако само по себе переключение на данный тип физической симуляции ещё не сделает объект подвижным, ведь мы же не задали ускорение вращения. Давайте это сделаем:
m_avelocity = vec3(30.0f, 30.0f, 30.0f);
Переменная m_avelocity имеет векторный тип и позволяет задавать вращение раздельно по трём осям: PITCH, YAW, ROLL. В данном случае, мы задаём вращение по всем трём осям одновременно со скоростью 30 градусов в секунду. Иными словами за одну секунду объект повернётся на 1/12 часть полного оборота, одновременно по всем осям.
Для того чтобы выбрать какую-то одну ось, достаточно обнулить необходимые аргументы конструктора vec3, к примеру:
m_avelocity = vec3(30.0f, 0.0f, 0.0f);
Это даст нам вращение только по оси PITCH.
Добавим две вышеприведённые строчки прямо в функцию Spawn:
void CRotationCube :: Spawn( void )
{
Precache();
SetModel( "models/items/keys/power.model" );
m_movetype = MOVETYPE_NOCLIP;
m_avelocity = vec3( 30.0f, 30.0f, 30.0f );
}Компилируем игровой код двойным щелчком по XashNT_SDK\src_user\ComradesServer\server.hsproj
Карту пересобирать не нужно, поэтому запускаем XashNT_SDK\comrades.exe, жмем Escape для выхода в консоль, выполняем:
map имя-карты
и видим что наш объект теперь вращается по всем осям:
Добавим возможность настраивать скорость вращения по всем трём осям из редактора уровней:
В наш класс CRotationCube добавим 3 строки в самое начало:
DECLARE_PROP_KEYFIELD_READONLY(float, m_speed_x); DECLARE_PROP_KEYFIELD_READONLY(float, m_speed_y); DECLARE_PROP_KEYFIELD_READONLY(float, m_speed_z);
Этот макрос создаст члены класса:
float m_speed_x;
float m_speed_y;
float m_speed_z;
Передадим их в конструктора вектора ускорения:
m_avelocity = vec3(m_speed_x, m_speed_y, m_speed_z);
struct CRotationCube : public CBaseEntity
{
DECLARE_PROP_KEYFIELD_READONLY(float, speed_x);
DECLARE_PROP_KEYFIELD_READONLY(float, speed_y);
DECLARE_PROP_KEYFIELD_READONLY(float, speed_z);
string model_path = "models/items/keys/power.model";
void Precache() { PRECACHE_MODEL(model_path);}
void Spawn() {
Precache();
SetModel(model_path);
m_movetype = MOVETYPE_NOCLIP;
m_avelocity = vec3(speed_x, speed_y, speed_z);
}
};Теперь добавим эти поля в quake2_comrades.fgd:
@PointClass base() studio("models/items/keys/power.model") =
misc_rotation_cube : "Test Entity\n\nRotating cube model."
[
m_speed_x(float) : "Rotating speed X" : 0
m_speed_y(float) : "Rotating speed Y" : 0
m_speed_z(float) : "Rotating speed Z" : 0
]имя-поля(тип) : "Имя в редакторе" : значение-по-умолчанию
Перезапустим Джек и откроем свойства объекта:
Теперь можно настраивать скорость вращения модели куба по всем осям из редактора.