Краткое руководство по началу работы с движком - maxsimilian560/Rinegine GitHub Wiki
Данное руководство подходит для версии 0.2.1, в версях выше изменён синтаксис файлов конфигурации сборки (rgset) и и синтаксис самого движка, об этом после публикации 0.3.0
Внимание! Для работы с движком у вас должна быть видеокарта с поддержкой OpenGL 3.3, иначе окно сборщика и собранной программы будут белыми или в цвет очистки контекста, в версии движка 0.3.0 это планируется исправить.
Для начала надо создать проект. Просто создайте папку и в ней создайте 2 файла, один cpp другой rgset. Названия могут быть любыми, для удобства в статье файл cpp будет назван source (source.cpp), а rgset - config (config.rgset).
Теперь в папке движка ищем программу Rinegine.exe (если вы работаете на 32 битном компьютере используйте Rinegine-x32.exe)
Запускаем, видим (лого, после видим) текст, который гласит, что сюда следует переместить файл rgset. Можно переместить уже сейчас, он его прочитает только после того, как вы нажмёте кнопку сборки. Перетаскиваем, видим написан путь к файлу и появилась кнопка сборки. Собирать ещё рано, нам необходимо заполнить наш конфиг. Синтаксис очень прост, и выглядит вот так:
name = example
source = source.cpp
resource = false
bit = 64
(порядок присвоения не важен)
- name: в него заполняется имя выходного файла, если собираете в 32 бита, то в конец будет приписано -x32.
- source: имя файла с кодом. Пока нельзя собирать большие проекты с большим кол-во cpp файлов
- resource: пока не работает, ставьте на false (или вообще не заполняйте и не объявляйте). В будущем будет отвечать за подключение resource.rc к проекту
- bit: под какую систему собираете. Может быть 32, 64 и all.
На деле настроек больше но писать все сюда не лучшая идея.
Всё, проект подключен и готов к сборке, можем писать код. Пути до проектов, которые были переданы программе сборки, пока не сохраняются, по этому каждый раз после закрытия придётся заново перетаскивать файл конфигурации в программу. После v0.2.1 можно использовать rgcmd, он находится в папке bin. Для удобного вызова лучше занести путь в переменные среды Windows. В последующих версиях я это оптимизирую. После того как вы занесли путь до rgcmd (и перезагрузили консоль) вы можете из консоли вызвать rgcmd. По умолчанию он будет искать файл rgset в папке из которой был вызван. Если найдёт сборка пойдёт автоматически, в ином случае попросит путь.
Обычно я использую ещё один файл source.h для того, чтобы в нём были все функции, заголовки и переменные, чтобы не захламлять cpp, но тут я покажу пример только с одним cpp.
Начнём, для начала нам нужно подключить заголовочный файл движка. Для подключения используйте #include <Rinegine>. Далее создаём функцию int rg_main(). Если определить #define RG_OWN_MAIN то вы можете использовать свою main функцию, но тогда всю инициализацию придётся выполнять в ручную. Так же все аргументы из функции main (если вы всё же используете rg_main) передаются в массив RG_MainArguments, он типа RG_Array<string>. Готово, мы можем собрать наш проект. Получается пустая программа, которая ничего не делает. Исправляем. Для работы модулей надо их подключить. Пишем #define RG_GRAPH обязательно перед include(!). Теперь можем создать окно. Для начала работы нам необходимо создать функцию интерфейса (или локации или логики окна, называйте как хотите, данная функция отвечает за логику происходящего в окне). Сделаем функцию, которая рисует в окне фон в градиенте сверху в низ от синего к зелёному. После v0.2.1 создание объектов изменилось, теперь создание такого выглядит так:
int Menu(int LastLoc){
int NowPlay = -1;
bool play = true;
RG_Object::Background background;
background.set.color1 = {0,0,1,1};
background.set.color2 = {0,1,0,1};
background.set.type = RG_Object::Background::GradientVertical;
background.Create();
while(play){
RG_PollEvents();
if(glfwWindowShouldClose(RG_Window_Standart->win())) {play = false;NowPlay = -1;}
if(RG_KEYS[GLFW_KEY_ESCAPE] == GLFW_PRESS) {play = false;NowPlay = -1;RG_KEYS[GLFW_KEY_ESCAPE] = GLFW_RELEASE;}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
background.draw();
RG_UpdateStates();
}
return NowPlay;
}- LastLoc в него передаётся позиция локации из которой эта локация была запущена, в первые при запуске всегда равен 0 (тоесть самая первая локация)
- NowPlay (название может быть любым) отвечает за то, что будет запущено после завершения функции, -1 это выход из программы (в большинстве случаев). В конце выполнения функции
returnвозвращает данную переменную, которая указывает какую локацию открыть следующим, об этом подробнее чуть позже. - play я думаю не стоит объяснять.
- RG_Object это область имён, в ней содержатся все типы объектов (на момент v0.2.1 их всего 2, Sprite и Backround), подробнее будет в документации.
- RG_PollEvents() обновляет всё (не только события), что связано с окном (окна, камеры, курсора, таймера (если передан))
- RG_KEYS переменная типа int[350], содержит состояния всех клавиш, позиции которых равны позициям из GLFW (например GLFW_KEY_ESCAPE) (их состояния тоже равны тем, что в GLFW)
- RG_UpdateStates обновляет все состояния (меняет буфер glfw местами, состояния изменения размера окна меняет на false, скрол колёсиком ставит на 0).
Далее функция возвращает позицию функции или -1 для выхода.
Дальше нам надо заполнить функцию main. Нам необходимо создать экземпляр класса RG_Functions, назовём его funcs. В теле функции main пишем
RG_Functions funcs;
int rg_main(){
funcs.push_back(Menu);
return RG_Start(funcs);
}RG_Start упрощает инициализацию движка и открытие окна, вы можете изменить настройки окна путём передачи третьим параметром переменную типа RG_Window_Settings (второй параметр - функция, которая выполняется после инициализации окна и движка, используется для загрузки текстур, шрифта и т.п., если вам ничего не из этого нужно передайте вторым параметром функцию RG_MainPrepare)
Теперь можно нажать кнопку собрать (или ввести в консоль VSCode или просто консоль, где удобнее, rgcmd), нажимаем, ждём до изменения надписи на "Done!" (если в rgset вы инициализировали run то не ждём)

У вас не будет иконки у окна как на скриншоте, для её добавления вам нужно у переменной класса RG_Window_Settings изменить параметр PathToIcon до вашей иконки и передать
RG_Startтретьим параметром (вторым можете передатьRG_MainPrepare).
Должен получится данный код:
#define RG_GRAPH
#include <Rinegine>
int Menu(int LastLoc){
int NowPlay = -1;
bool play = true;
RG_Object::Background background;
background.set.color1 = {0,0,1,1};
background.set.color2 = {0,1,0,1};
background.set.type = RG_Object::Background::GradientVertical;
background.Create();
while(play){
RG_PollEvents();
if(glfwWindowShouldClose(RG_Window_Standart->win())) {play = false;NowPlay = -1;}
if(RG_KEYS[GLFW_KEY_ESCAPE] == GLFW_PRESS) {play = false;NowPlay = -1;RG_KEYS[GLFW_KEY_ESCAPE] = GLFW_RELEASE;}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
background.draw();
RG_UpdateStates();
}
return NowPlay;
}
RG_Functions funcs;
int rg_main(){
funcs.push_back(Menu);
return RG_Start(funcs);
}Для создание 2D объектов используется Sprite из RG_Object. Код для создания белого квадрата на весь виртуальный экран выглядит так:
RG_Object::Sprite obj;
obj.Create();Для отрисовки как и у Background используется ::draw()
obj.draw();Но белый квадрат это слишком скучно, можно изменить цвет с помощью color из настроек и тогда код будет выглядеть так:
RG_Object::Sprite obj;
obj.set.color = {1,0,0,1};
obj.Create();Но красный квадрат не сравнится с квадратом с текстурой. Для загрузки текстуры используется система ключей. Код загрузки текстур должен быть в функции main или rg_main и будет выглядеть так:
RG_Atlas_Standart.Init({"img.png"},{"img"});Первое значение это путь до текстуры, второй это её имя или ключ. Чтобы получить текстуру по ключу используйте RG_Atlas_Standart["img"];, тогда вернётся ид текстуры в атласе (не gl(!)). Допустим я загрузил текстуру травы (это развёртка, на 3D объекте будет выглядеть правильно, но пока так) RG_Atlas_Standart.Init({"grass.png"},{"grass"});. Для использовании текстуры в set объекта надо загрузить его ид атласа. Код уже будет выглядеть так:
RG_Object::Sprite obj;
obj.set.textures = RG_Atlas_Standart["grass"];
obj.Create();Вы можете заметить, что объект выглядит маленьким, это связано с тем, что его размер 1:1 к текстуре и масштабу виртуального окна. Для увеличения масштаба текстуры нужно по всем сторонам изменить его масштаб:
RG_Object::Sprite obj;
obj.set.textures = RG_Atlas_Standart["grass"];
obj.set.scale = {30,30,30};//{x,y,z}
obj.Create();И да, объекты всегда находятся в 3D, это никак не сказывается на производительности (разве что на уровне погрешности).
В v0.2.1 был обнаружен баг, из-за которого масштабирование по координате
zтак же изменяет масштабy, а масштабированиеyничего не меняет!
Теперь объекта размером 1:30 к обычной текстуре.
Но что, если его необходимо по двигать в реал тайме? Прям во время исполнения программы? Можно! Для этого в цикле используйте различные методы объекта и обязательно после изменения используйте метод update()! Я буду показывать пример с условием, но условие необязательно.
Допустим у нас цикл
while(play){
RG_PollEvents();
if(glfwWindowShouldClose(RG_Window_Standart->win())) {play = false;NowPlay = -1;}
if(RG_KEYS[GLFW_KEY_ESCAPE] == GLFW_PRESS) {play = false;NowPlay = -1;RG_KEYS[GLFW_KEY_ESCAPE] = GLFW_RELEASE;}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
...
background.draw();
obj.draw();
RG_UpdateStates();
}Где на месте ... будет находится код изменения.
Например нам надо поворачивать объект, для этого есть метод to_rotate(POINT3D<double>). Он поворачивает объект на столько градусов, сколько было передано. Пусть объект постоянно поворачивается по часовой стрелке пока не нажат пробел
if(RG_KEYS[GLFW_KEY_SPACE] != GLFW_PRESS){
obj.to_rotate({0,0,-0.07});
obj.update();
}Такое маленькое значение из-за того, что частота обновления слишком велика (у меня это 1300 кадров в секунду в оконном режиме и 1800 в полноэкранном). Это можно исправить включив у окна вертикальную синхронизацию или (что наиболее желательно) с помощь таймера посчитывать кол-во градусов в секунду, так у вас в не зависимости от частоты кадров скорость поворота будет одинакова.
if(RG_KEYS[GLFW_KEY_SPACE] != GLFW_PRESS){
obj.to_rotate({0,0,(-90.*RG_Timer_Standart.getBias())});
obj.update();
}RG_Timer_Standart это стандартный таймер который используется везде по умолчанию.
Вы так же можете включить вертикальную синхронизацию. Для этого у используемого окна используйте setVsyn(uint lvl) где lvl - сила вертикальной синхронизации (1 - максимальная частота кадров экрана. 2 - половина максимальной частоты экрана, 3 - треть частоты и т.д.).
Перед циклом можете использовать RG_Window_Standart->setVsyn(1);
Так же у объекта можно менять положение с помощью move
if(RG_KEYS[GLFW_KEY_W] == GLFW_PRESS||RG_KEYS[GLFW_KEY_D] == GLFW_PRESS||RG_KEYS[GLFW_KEY_S] == GLFW_PRESS||RG_KEYS[GLFW_KEY_A] == GLFW_PRESS){
if(RG_KEYS[GLFW_KEY_W] == GLFW_PRESS){
obj.move({0,1*RG_Timer_Standart.getBias(),0});
}
if(RG_KEYS[GLFW_KEY_D] == GLFW_PRESS){
obj.move({1*RG_Timer_Standart.getBias(),0,0});
}
if(RG_KEYS[GLFW_KEY_S] == GLFW_PRESS){
obj.move({0,-1*RG_Timer_Standart.getBias(),0});
}
if(RG_KEYS[GLFW_KEY_A] == GLFW_PRESS){
obj.move({-1*RG_Timer_Standart.getBias(),0,0});
}
obj.update();
}В итоге должно получится примерно так:

Вы можете управлять камерой, менять перспективу с 2D на 3D и обратно, двигать, менять зум. При изменении положении, поворота и зума камеры объекты GUI (такие как background и sprite с настройкой GUI) не будут никак затронуты. Будем брать пример выше, так как фон это GUI а объект нет, мы можем двигать объект без изменения его положения двигая камеру. Для этого есть стандартная камера, можно создать свою но тогда её лучше инициализировать как RG_Camera &MyCam = RG_Camera_Standart; иначе что-то может не работать. В принципе методы довольно похожи, для перемещения так же используется move(POINT3D<double>) и в конце обязательно update(). Есть rotate(POINT3D<double>), аналог to_rotate(POINT3D<double>) у объекта. У камеры так же есть zoom(), он изменяет зум камеры. В принципе работа с камерой не отличается от работы с объектом. Вот пример перемещения камеры для иллюзии движения объекта:
if(RG_KEYS[GLFW_KEY_W] == GLFW_PRESS||RG_KEYS[GLFW_KEY_D] == GLFW_PRESS||RG_KEYS[GLFW_KEY_S] == GLFW_PRESS||RG_KEYS[GLFW_KEY_A] == GLFW_PRESS){
if(RG_KEYS[GLFW_KEY_W] == GLFW_PRESS)
RG_Camera_Standart.move({0,-1*RG_Timer_Standart.getBias(),0});
if(RG_KEYS[GLFW_KEY_D] == GLFW_PRESS)
RG_Camera_Standart.move({-1*RG_Timer_Standart.getBias(),0,0});
if(RG_KEYS[GLFW_KEY_S] == GLFW_PRESS)
RG_Camera_Standart.move({0,1*RG_Timer_Standart.getBias(),0});
if(RG_KEYS[GLFW_KEY_A] == GLFW_PRESS)
RG_Camera_Standart.move({1*RG_Timer_Standart.getBias(),0,0});
RG_Camera_Standart.update();
}