Всем привет! В предыдущих постах я программировал посудомоечную машину. Теперь решил сделать мини-серию постов о программировании, а точнее, о том, как надо мыслить для того, чтобы программа получилась. Я не буду вдаваться в нюансы написания самого кода. Здесь, скорее, будут размышления на тему, так что быть программистом для чтения не обязательно (и не желательно). Поехали.
Для начала, небольшой ракурс в историю. Первая игровая приставка (в привычном нам понимании) появилась в 1972 году. Разработала её группа военных инженеров во главе с Ральфом Бером -"Отцом видеоигр". Эта самая приставка была впоследствии названа Magnavox Odyssey. Выполнена она была полностью на транзисторах. Можете себе представить? (фото взято с сайта Wikimedia).
Такая приставка умела немного. Однако, важнее всего была суть - возможность генерировать свой управляемый телевизионный канал. До этого подобное не приходило никому в голову. Так вот, примерно спустя 10 лет после этих событий, появились в продажу компактные ЭВМ для массового потребления - ZX Spectrum, IBM PC и другие. Каждый получил возможность изучить компьютеры и обрёл пространство для своих творческих экспериментов. А пятьдесят лет спустя, мы привыкли видеть компьютерные игры уже так:
Конечно, у нас есть новейшие производительные процессоры. Практически каждый школьник теперь может "собрать" свой компьютер (вставив для этого комплектующие в соответствующие для них слоты). Но, когда у кого-либо возникает желание разобраться поподробнее, как это всё устроено, и, может быть, написать свою игру (где будут девушки с большими прелестями, можно грабить корованы и т.д.), тут уже возникают нюансы и, оказывается, не всё так просто в этом мире. Но на помощь нам приходят учебники по программированию. Однако, как известно, учат в школе многих, да немногие в итоге выучиваются.
Кстати говоря, скоро наступает 1 сентября. Вспоминаются мои институтские годы - как мы пришли, и первой парой стояла высшая математика. До сих пор во многих провинциальных вузах программа первого семестра начинается с матриц. Я помню ребят, которые невесть как попали в институт, смотрели с задних парт круглыми глазами и только повторяли "- И чё мы тут вообще забыли?". Кстати говоря, ребята эти в итоге семестр не пережили и были отчислены. Но это уже их проблемы. Однако, даже у тех, кто остался, часто возникали вопросы: зачем? что это мы такое учим?
Понимание приходит не сразу, однако, когда ты сталкиваешься с какой-то задачей, то со временем уже начинает что-то доходить. Так же и здесь. Оказывается, с помощью тригонометрических функций, производных и интегралов, матриц очень удобно осуществлять операции над множествами точек и фигур, что является незаменимым при создании графики в компьютерных играх. Собственно, возможность быстро производить подобные операции и характеризует "крутизну и навороченность" компьютера.
Но давайте приступать. Для начала, чтобы программа получилась, необходимо мышление программиста. Я его понимаю, как две составляющие: некая абстрактная модель и её реализация. Короче говоря, теория и практика. Чтобы было более понятно, о чём я говорю, давайте представим, что мы программируем обычную столовую ложку, как в фильме "Матрица"
Так вот, если думать, как программист, то эту ложку можно разделить на две составляющие.
1. Теория ложек
Какие параметры существуют у ложки? Такие, какие вы ей сами зададите. Вы можете взять тарелку, зачерпывать ей суп, и тем самым объявить тарелку ложкой. Но немногие с вами согласятся, ведь, во-первых, это неудобно. Во-вторых, все привыкли видеть ложку привычной ложкой. Поэтому рассмотрим более детальные требования:
- Ложка имеет хлебало и держало черенок и чашечку. Чашечка, в свою очередь, имеет форму полусферы. Мы можем задать чашечку, например как функцию z^2 = x^2 + y^2
Это, конечно же, весьма грубое сравнение. Но оно позволяет представить, каким образом будет реализована дальнейшая ложка. Аналогично можно математически описать и остальные части. Это - будет наша модель ложки. Опять же, одна из возможных.
2. Практика ложек
Как мы знаем, обычно ложки вырезаются из дерева, либо изготавливаются штамповкой из металла. В нашем случае давайте представим, что мы решили запрограммировать ложку в компьютер. Полученная двумерная проекция на экран при этом вполне может выглядеть так:
И то и это - нормально. Всё дело в представлении ложки. В том, как мы её видим и в том, как мы её задали. Последнее компьютер выполняет, в отличие от людей, весьма чётко. Мы задали какую-то задачу, и получили конкретный результат.
Именно поэтому важна проработка обоих частей программы. Понятное дело, новички, когда только врубаются в основы программирования, изучают язык подобно маленьким детям. Т.е., пытаются повторять и копировать какие-то фрагменты кода, составляя из них программу. Однако, каждый чужой кусочек кода является именно практикой, следствием где-то существующей модели. Именно поэтому программа новичка, порою, выглядит, как на известных мемах:
Но, если не сдаваться и упорно двигаться вперёд, можно прийти к какому-то конкретному результату. Для этого требуется выполнять различные "задачки": на сортировку, на реализацию функций, на описание структур и т.д., пробовать выполнять какие-то кусочки больших и сложных моделей: хвост оленя, чашку ложки и т.п. Собственно, подобным образом осваиваются и другие человеческие науки: для рисования нужно отрабатывать тени и завитки, для игры на музыкальных инструментах - гаммы и аккорды, ну и так далее.
Давайте рассмотрим одну из таких задачек. Вспомним ракурс в историю, который был в начале поста. Первые 3D игры для первых ЭВМ были примерно такие: Battle Tank
Star Wars: Empire strikes back.
Всё это было до Джона Кармака, появления Direct3D, OpenGL и т.д. Примерно в таком ракурсе мы и будем с вами сегодня сегодня работать. Писать будем пока на плюсах, хотя для новичков проще это будет сделать, наверное, на питоне. В качестве подспорья возьмём графическую библиотеку SFML. С 2018 года она уже не поддерживается, но, для иллюстрации сути поста её будет достаточно. Мы хотим получить что-то вроде этого:
Для того, чтобы обдумать реализацию, и, в дальнейшем, это получить, мы должны разобрать "по косточкам" происходящее на экране. Мы видим:
1. GUI (интерфейс с очками и временем). Пока отбросим его.
2. Пространство с астероидами
3. Космический корабль
Также мы знаем, что происходит при нажатии клавиши:
1. Космический корабль наклоняется в сторону
2. Пространство поворачивается вокруг корабля, создавая иллюзию того, что корабль куда-то летит (да, когда вы играете в компьютерную игру, это не игрок поворачивается в пространстве, а пространство вокруг игрока).
Теперь рассмотрим более подробно первую задачу: по нажатии клавиши космический корабль поворачивается. Давайте разберём корабль, по аналогии с ложкой. Он представляет собой проекцию трёхмерного объекта на двумерное, группу точек, соединённых рёбрами (в случае Wireframe моделей - линиями, в остальных - треугольниками, полигонами). Значит, теория корабля следующая:
1. Где-то есть группа точек. Её мы просто задаём, как набор переменных. Произвольно. Но хотя бы один экземпляр её должен существовать.
2. Есть способ изменения этой группы точек по внешним параметрам. Для этого есть матрицы сдвига, масштабирования и поворота в трёхмерном пространстве (материал взят с сайта Wikipedia):
3. Есть способ отображения их на мониторе (хотя это уже больше относится к реализации)
Прежде, чем приступать к реализации 3D варианта (он будет во второй части поста), давайте упростим наш корабль до прямоугольника, и одно измерение уберём. Таким образом, будет проще разобрать задачи на начальном этапе. Да и матрица поворота сильно упрощается и теряет один порядок:
С теорией понятно, давайте перейдём к практике.
1. Развернём IDE, подключим библиотеки, подготовим точку входа.
2. Создадим класс Box2D. Зададим группу точек нашего прямоугольника как двумерный массив - массив пар координат. Пока что с конечным размером.:
float vertices[4][2] = { {-50.f, 50.f}, {-50.f, -50.f}, {50.f, -50.f}, {50.f, 50.f} };
Также добавим переменную center, характеризующую сдвиг центра объекта. Это необходимо для возможности дальнейшего поворота относительно произвольной оси, но пока не нужно.
3. Добавим методы, осуществляющие поворот объекта по заданному углу. Код здесь получился несколько кривой в результате быстрого его портирования с питона, но, в дальнейшем, поправим:
Обратите внимание.
В generateMatrix переменная newMatrix - та самая матрица поворота. Вспоминаем нашу математику. Функция подготовит матрицу поворота на конкретно заданный градус. Именно на неё мы будем перемножать имеющиеся ранее заданные значения точек, чтобы получить новые координаты.
calculatePerVertex - перемножает координаты на матрицу.
recalculateRotation - проходит по всем координатам объекта и выполняет их обработку.
Таким образом, используя матрицу newMatrix, мы можем повернуть любое множество точек, перебрав их каждую по очереди. В двумерном пространстве это будет выглядеть, как движение точки по касательной окружности (в случае положительного угла - по часовой стрелке, и наоборот).
Ну и осталось спроецировать эти точки на экран. Тут есть множество вариантов - можно также создать копию массива точек, соответствующих полученным значениям, если в дальнейшем нам нужно применять к точкам ещё какие-либо операции. В нашем случае это не нужно, поэтому все точки можно просто забить поочерёдно в дисплейную область оперативной памяти посредством средств библиотеки SFML - объекта "Circle" со смещением на координаты, задаваемые вершинами.
Готовый результат при таком способе проекции будет выглядеть так:
При нажатии клавиши фигура поворачивается на заданный угол. На данный момент фигура поворачивается по прерыванию от клавиши, так как игровой таймер ещё не реализован, что будет поправлено в следующих постах, если эта тема понравится.
Обобщим вышесказанное:
Мы имеем некоторую воображаемую модель множества точек. Над которой осуществляем операции, и затем практически отображаем результат этих операций. Именно за это и отвечает математика. Хотя, конечно, это чуть ли не школьный уровень.
Давайте изменим практическую реализацию и соединим наши точки линиями. Для этого мы сообщаем в модель ещё один внешний параметр - порядок соединения этих линий. Он может быть произвольным. Но, как мы знаем, у прямоугольника каждая вершина соединяется лишь с двумя другими под углом 90 градусов. Поэтому порядок соединения будет 1-2-3-4-1.
Центр обозначен красной точкой. Уже становится немного понятно, каким образом осуществляется управление воображаемым космическим кораблём. Далее остается лишь добавить третье измерение, и получить куб. Немного забежим вперёд:
Трёхмерный куб вращается относительно произвольной оси, центр куба не равен линии вращаемой оси (обратите внимание на красную точку). Вершины куба также обозначены окружностями для наглядности. Реализовать такой куб, и, затем, космический корабль мы попробуем во второй части поста.
А для тех, кто дочитал и кто любит компьютерные игры, забежим ещё очень сильно вперёд. В 2009 году Маркус Перссон, изобретатель игры "Minecraft" написал маленький апплет, под названием Minecraft 4k. Вот, как он выглядел:
Переписывание этого апплета также является хорошим упражнением для развития навыков по программированию. Обратите внимание: разворачивается трёхмерный массив кубов, который вращается вокруг игрока, создавая иллюзию того, что игрок осматривается. Оптимизация отсутствует, отрисовываются все грани, однако, также выполняется рейкастинг по 3 осям, и ближайшие грани кубов перекрывают более дальние, создавая такую картинку.
С вами был Kekovsky, на сегодня всё, приятных выходных. Если какие-то из моих постов понравились или были для вас информативными, можете поддержать меня, благодаря стараниям администрации Пикабу у меня теперь так же есть донатная кнопка.
Всем успехов в освоении новых наук!