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

XXIII Международный конкурс научно-исследовательских и творческих работ учащихся
Старт в науке

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

Георгиев И.Г. 1
1МОУ Раменская СОШ №21 с УИОП
Сидорова В.В. 1
1МОУ Раменская СОШ №21 с УИОП
Автор работы награжден дипломом победителя I степени
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

Введение

Язык программирования С++ был официально выпущен в октябре 1985 года, а начал создаваться сотрудником фирмы Bell Laboratories — Бьёрном Страуструпом. .(https://www.sravni.ru/kursy/info/yazykprogrammirovaniya-c/)

Он придумал ряд усовершенствований к языку программирования C, для собственных нужд. Т. е. изначально не планировалось создания языка программирования С++. Ранние версии языка С++, известные под именем «Cи с классами», начали появляться с 1980 года. Язык C, будучи базовым языком системы UNIX, на которой работали компьютеры фирмы Bell, является быстрым, многофункциональным и переносимым. Страуструп добавил к нему возможность работы с классами и объектами, тем самым зародил предпосылки нового, основанного на синтаксисе С, языка программирования. Синтаксис C++ был основан на синтаксисе C, так как Бьёрн Страуструп стремился сохранить совместимость с языком C.

Игровой движок — это программное обеспечение, которое используется для создания и запуска компьютерных игр. Он предоставляет разработчикам набор инструментов, библиотек и функций для работы с графикой, физикой, звуком, искусственным интеллектом и другими аспектами игрового процесса.

Игровые движки разделяются на несколько категорий: 2D (для создания двухмерных игр, программ) и 3D (для создания трехмерных игр, программ). На нашем игровом движке можно создавать как двухмерные, так и трехмерные приложения. Также они подразделяются по тому, на какие платформы можно создать на них приложение: под телефоны, под ПК, под консоли. Также существуют кроссплатформенные движки. На них можно создавать приложения под несколько платформ сразу. На данный момент «Neon Engine» может создавать приложения только под операционную систему Windows, но благодаря тому, что он был создан на библиотеке SFML, в скором времени будет создана возможность делать на нем приложения под телефоны. Игровые движки бывают в виде программного обеспечения, или в виде фреймворков.

Изучая вопрос игрового движка, мы столкнулись с проблемой функциональности игрового движка и отсутствием полностью бесплатного, многофункционального, оптимизированного и удобного игрового движка. И в ходе написания данной работы решили определить, можно ли изучив язык программирования создать игровой движок, который мог бы высчитывать и отрисовывать трехмерные объекты, а также предоставлял удобную работу с ним.

Актульность. В настоящее время изучение языка программирования C++ является актуальным, потому что благодаря С++ работают Яндекс. Поиск и поисковый движок Google. С его помощью создаются беспилотные автомобили, нейронные сети, сервера, видеоигры и даже пишутся музыкальные треки. У С++ есть много плюсов, таких как:

  • Поддержка объектно-ориентированного программирования — в нем предусмотрено представление программы в виде совокупности взаимодействующих объектов, каждый из которых является экземпляром определённого класса.

  • Универсальность — C++ используется для реализации самых различных задач, от написания баз данных, до написания драйверов

  • Производительность — благодаря своей вычислительной мощности язык обеспечивает высокую скорость исполнения кода

  • Отсутствие существенной нагрузки язык не утяжеляет программы, позволяет использовать их даже на старых устройствах. (https://ru.stackoverflow.com/questions/639851/Отличия-в-оптимизации-c-и-c)

Всё выше перечисленное позволяет сформулировать цель исследования: создать игровой движок, используя язык программирования C++ и библиотеку SFML.

Для достижения этой цели должны быть выполнены следующие задачи:

1) По литературным данным выявить, насколько тяжело разрабатывать, оптимизировать и поддерживать игровой движок;

2) Рассказать об устройстве игровых движков и о проекции трехмерных моделей

  1. Написать программу действий игрового движка, провести тестирование программы и сделать выводы;

  2. На основе выводов сделать предложения по улучшению…

Предмет исследования:язык программирования C++

Объект исследования:программасоздания игровой движок «Neon Engine»

Гипотеза:предполагаем, что у нас получится создать игровой движок на языке программирования C++.

Материалы и методы

Материалы. При написании программы и проведения тестирования полученных результатов мы пользовались: Visual Studio 2022, Microsoft Visual C++, SFML, OpenGL.

Методы исследования.

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

Изучения языка программирования началось с Python. Потом был рассмотрен язык JavaScript (легкий и функциональный на котором была написана пошаговая игра Ivan Adventures). В настоящее время изучается язык C#, под игровой движок Unity.

Создание игрового движка началось с изучения матриц, векторов и остальной геометрии, сложности возникали в реализации самых базовых вещей, таких как реализация поворота, перемещения трехмерного объекта, реализация его проекции, загрузки трехмерных моделей из файлов расширения obj (над которой до сих пор продолжается работа по её улучшению).

2. Отладка программы и анализ результатов Метод эмпирического исследования позволил нам сравнить полученные в ходе исследования данные, для коррекции программы и нахождения ошибок.

Первоначально сделанный движок, долгое время показывал ошибки, за счет недостаточного знания о значении такой очень важной команды, как «pragma once», которая следила, чтобы конкретный исходный файл при компиляции подключался строго один раз.

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

Для удобного пользования нашим игровым движком, был создан первоначальный класс программы. Для того, чтобы начать разрабатывать приложение, нужно создать класс, с названием «Main», который будет наследоваться от основного. Основной класс содержит в себе различные методы, которые будут вызываться при различных ситуациях, к примеру когда пользователь передвигает мышь, нажимает клавиши на клавиатуре, или когда запускается программа. Данный класс, помимо этих различных методов, содержит в себе класс рендера программы, который высчитывает и выводит все трехмерные объекты. Двухмерные же объекты являются на самом деле трехмерными, просто в настройках камеры можно выбрать, будет ли она перспективная или ортогональная.

Ход работы

Прохождение этапов создания движка. В нашем игровом движке есть несколько основных этапов: основной класс приложения, математика, классы рендера, игровых объектов. Именно про них мы и поговорим подробнее.

1) Основной класс приложения

Для удобства пользования движком было реализовано множество различных классов. Самым главным, основным из них является класс приложения (Рис. 1).

Рис.1 Основной класс приложения

В данном классе реализуется самый главный метод, который в самом начале запускает виртуальный метод «Start». Следом высчитываются начальное положение всех статических объектов. Позже начинается цикл, который работает пока приложение запущенно. В нем, при каждом действии пользователя вызываются виртуальные методы, выделенные под определенные действия. Следующим этапом всегда вызывается виртуальный метод «Update». За ним следует отрисовка определенной сцены, вывод нужной и цикл начинается с самого начала.

2) Математика приложения

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

Зная, что вектора бывают, двухмерными, трехмерными, четырехмерными и так далее. Двухмерные вектора содержат в себе два числа – x и y. У трехмерного также появляется третье число – z. У четырехмерного – w. Векторы можно складывать, вычитать, умножать, делить (складывая, вычитая, умножая, деля все их элементы между собой). Умножать вектора можно и просто на число (скаляр). Также вектора имеют метод, возвращающий длину вектора, возвращающий скалярное произведение вектора с другим, нормализующий вектор. Матрицы же – это массив чисел в виде таблицы. В нашем игровом движке реализованы только матрицы 2 на 2, 3 на 3, 4 на 4. На рисунке 2 приведен пример одной из матриц (3х3). Изначально, была идея сделать матрицы в виде векторов, то есть вектор 2 на 2 содержал два двухмерных векторов, 3 на 3 – три трехмерных и т.д., но позже, при оптимизации игрового движка, код был переписан на просто массивами.

Рис.2. Класс трехмерного вектора

3) Классы рендера. Обработка точек

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

Чтобы высчитать заданную по точкам фигуру, в начале, при каждом вызове, который происходит каждый цикл работы программы, метода «drawScene», класса «Render», отвечающего за отрисовку и обработку всех точек, мы получим игровой объект в виде класса «Object» (Рис. 3).

Рис.3 Класс игрового объекта

Класс «Object» - таже один из самых важных классов нашего движка. Он хранит в себе помимо разной не сильно полезной, также самую важную информацию – класс «Mesh», хранящий все локальные позиции точек, класс «Transform», в котором в виде вектора указаны 4 параметра: глобальное положение объекта, его поворот и локальный центр координат. Также в классе игрового объекта имеется массив с компонентами. Компоненты каким-либо образом могут влиять на объект, частью которого они являются. Примером компонентов является «Rigidbody», который на данный момент позволяет только задавать скорость и направление объекта, но в будущем в нем также будут реализованы методы, изменяющие объект согласно законам физики, к примеру реализация гравитации и так далее. Благодаря удобной реализации основного класса компонентов их можно легко изменять и исправлять.

Но вот, зная всю информацию о нашей точке, что нужно дальше? Первым делом мы умножаем локальную позицию точки относительно центра, переведенную в четырехмерный вектор, на три матрицы поворота: по оси x, по оси y и по оси z. Очень важно, чтобы мы применяли на точки объекта матрицы поворота, до матрицы перемещения, так как делая это после, они начинают вращаться вокруг центра мира, а не локального центра объекта. Это конечно можно исправить, но при этом код будет более нагруженным, чего ни в коем случае нельзя допускать. В следующую очередь, на полученную после прошлой матрицы точку, мы применяем матрицу размера. Тут тоже важно не применять ее после матрицы перемещения по той же причине. Именно после этого можно высчитывать глобальную позицию точки. Теперь, зная все о позиции точки, давайте сдвинем ее еще и относительно родительского объекта. Теперь, с той точкой, что мы получили после расчета локального поворота, размера и положения, мы проделываем с ней все тоже самое в том же порядке, но только все параметры будет брать у родителя.

Далее расчеты произойдут благодаря камере, которая также является игровым объектом, но с возможностью получать из нее матрицу самой камеры, то есть положение еще и относительно позиции и поворота камеры и матрицу перспективы. Об этих вычислениях мы подробней поговорим в разделе с камерой. Также они и все последующие будут производиться для статических объектов.

Узнав еще больше про итоговое положение точки, давайте уже завершим ее расчет. Теперь, для создания эффекта удаления точки, все ее координаты надо поделить на отрицательное значение координаты w, в которую мы и перенесем значение дальности благодаря камере. Мы же должны поделить именно на отрицательное значение, чтобы у нас была левосторонняя система координат, то есть увеличивая значение z, объект отдалится, а не приблизится (Рис. 4). Далее мы умножаем точку на 0,15 от размеров окна, чтобы увеличить объект, так как иначе он будет казаться очень маленьким, а умножая на какое-либо статичное значение, при маленьком размере окна, объект начнет вылазить за его рамки. И под конец мы добавим к положению точки половину размеров окна, чтобы нулевая координата находилась в его центре

Рис.4. Правосторонняя систем координат.

4. Игровые объекты Работа камеры

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

1) Камерой во всех играх является обычная точка в трехмерном пространстве, которая имеет в себе угол обзора. Также наша трехмерная камера будет иметь в себе две плоскости: дальнюю и ближнюю, размер которых будет зависить от угла обзора, а на их основе мы сможем определять, какие объекты есть в поле зрения камеры, а каких нет.

Теперь стоит понять, как все полученные данные помогут создать матрицы камеры и перспективы на их основе, а главное, как они будут работать. Первая матрица нужна, чтобы при и перемещении или повороте камеры, все объекты относительно нее тоже менялись, ну а работает она довольно просто: она, исходя из положения и направления камеры передвигает и поворачивает игровой мир (Рис.5). Говоря об устройстве матрицы проекции, то она нужна для проекции трехмерных точек на плоскость, то есть на экран. В данном методе мы, во-первых, мы масштабируем значения x и y в соответствии с соотношением сторон окна, во-вторых, высчитываем правильное значение дальности точки, записав его в координату w.

Рис.5. Метод создания матрицы камеры.

Также можно упомянуть про метод отсечения точек фигуры, о плоскости камеры. Чтобы отсечь точку мы должны найти дистанцию точки до плоскости. В случае, если она больше единицы, то точка за границами плоскости, а это значит, что мы ее отсекаем. Подробнее об отсечении мы поговорим в следующем разделе

5. Алгоритм отрисовки

Для отрисовки, как мы ранее говорили, используется метод «drawScene» класса «Render», а сейчас давайте подробнее поговорим про его работу. Отрисовка в нашем движке бывает трех типов: по точкам, когда на месте точки рисуется небольшой круг, по линиям, когда рисуются только контуры фигур (Рис.6), а также полная отрисовка фигур с заливкой. Из основных различий в алгоритмах всех трех видов отрисовки является работа отсекания точек и методом отрисовки через SFML или OpenGL.

Первым делом мы должны проверить, какой тип отрисовки будет использоваться. Далее мы начинаем перебирать все объекты сцены. В случае, если мы отрисовываем только точки, или только линии, то идем дальше, но в случае, когда мы должны нарисовать объект полностью, мы создаем массив, хранящий в себе все данный о всех лицах. Лицо – фигура (чаще всего треугольник), хранящая в себе несколько значений: номера точек, координат их текстуры и их нормалей. Эти значения используются для работы загрузки моделей. А также лицо имеет сами точки, координаты их текстур, и их нормали. Эти же данные используются для сохранения данных лица у статических объектов (объектов, которые не могут изменять свое положение, поворот, размер для оптимизации приложения). Следующим этапом мы обходим все лица фигуры и обрабатываем все их вершины. Для отрисовки только по точкам, мы просто проверяем находиться ли точка внутри плоскостей камеры и если да, то рисуем, а иначе – нет. При полной отрисовке или отрисовке по линиям мы не будем сразу применять на по линиям точки матрицу перспективы, а перед этим обрежим все лишние, заменяя их на места пересечений фигуры с плоскостью, и только лишь потом мы будем делать дальнейшие расчеты. Далее, просчитав полностью все точки лица, при полной обработке мы переберем все лица всех объектов по среднему значению дальности, где в начале будут стоять самые дальние лица, а в конце – самые близкие. Это все для того, чтобы те лица, что дальше не рисовались поверх ближних. В случае же рисования по линиям, нам не надо обрабатывать их таким образом, и поэтому получив готовое лицо мы просто его отрисуем.

Рис.6. Результат отрисовки объектов

Результаты

Анализ проведенной работы позволяет выделить результаты на каждом этапе его проведения:

Этап работы

Что получилось

Сложности, трудности

Выбор языка C++

Понравилось програм-мировать

У меня возникли трудности из-за плохого знания языка, но позже их не стало

Изучение языка C++

Изучал нужные мне детали данного языка и по итогу его выучил

У меня не возникло каких-либо трудностей, так

Выбор идеи для проекта

Проект довольно полезный и функциональный

Изначально плохо понимал алгоритм работы игрового движка

Написание программы

Движок довольно мало весит, многофункцио-нальный и интересный

Изначально плохо знал работу ООП, которое было очень важно для данного проекта, а также не разбирался в матрицах.

Тестирование

Благодаря тестам смогли очень сильно оптимизировать и улучшить движок.

Из-за медленной работы движка мне пришлось полностью его переписать.

Подведение итогов

Создали замечательный движок, разобрались в ООП, углубили знания в математике.

Это был мой первый опыт в создании настолько тяжелого и огромного проекта

Заключение

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

Написали программу действий игрового движка, провели тестирование программы и сделали выводы, такие как:

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

аиболее интересным и запоминающимся при написании работы было создание основной математики;

-трудности возникли из-за плохой оптимизации, плохого знания ООП и C++.

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

Выводы и направления дальнейших исследований

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

Проект не находится в открытом доступе, т.к. не является полностью реализованным. Когда он будет готов, планируется опубликовать его.

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

Список литературы

  1. Джейсон Грегори "Игровой движок. Программирование и внутреннее устройство"

Интернет-источники:

  1. https://www.sravni.ru/kursy/info/yazyk-programmirovaniya-c/

  2. http://cppstudio.com/post/1984/

  3. https://habr.com/ru/articles/599403/

  4. https://www.youtube.com/watch?v=ih20l3pJoeU

Просмотров работы: 48