Задумал я сделать игрушку. День 3.
Запись от Storm23 размещена 28.03.2016 в 02:57
Показов 4525
Комментарии 2
|
День 3. Космос. Предыдущие дни здесь. Итак, вдохновившись PowderToy, захотелось мне сделать что-то больше, красивое и эпичное. И конечно оно должно быть в 3d. Двумерные игры мне не нравятся. А тут еще на хабре прочел заметку про то как парень писал космосим. И выбор был сделан. Пишем космический шутер. Конечно, я мог бы не париться, взять Unity3d и вперед по рельсам. Как вы можете догадаться я таким путем не пошел. Это не интересно. Да к тому же, в играх для меня всегда было проблемой поиск текстур и моделей. А сам делать модели я не умею. Итак, только хардкор, только олд-скул. Никаких DirectX, никаких шейдеров, никаких Unity и XNA. В общем, далее будет типичный crazy programming and wheel invention. В лучших традициях велосипедостроения будем делать нечто вроде 3d движка, но под GDI+, без шейдеров, без использования возможностей видеокарты, на чистом C#, без сторонних библиотек. И посмотрим, что получится. А да, и еще текстуры, готовые модели и звуки - мы тоже использовать не будем. Кстати посмотрим еще и на размер программы в результате. И разумеется, нужно выжать максимальное быстродействие. Иначе не интересно. Начнем с физики. Я не буду подробно останавливаться на физическом движке, который я реализовал. Скажу лишь что он почти такой же как в PowderToy, только трехмерный. И еще, столкновения частиц - основная фишка PowderToy - мне не пригодилась. Вот бывает и так :[] Физика реализована в классе Sandbox, который содержит список частиц, класс Particle. Они довольно скучны внутри, я не буду пока подробно останавливаться. Скажу лишь, что я применил интегрирование Верле, о котором можно более подробно почитать здесь. Это немного повышает быстродействие и дает возможность обрабатывать столкновения. Кстати, далее по тексту я буду давать довольно много ссылок на полезную информацию. Иначе зачем я это все пишу, если вы ничего не узнаете нового для себя? Кроме того, я использую связанный список LinkedList для хранения частиц, вместо обычного List, что повышает быстродействие, и немного упрощает работу с частицами. Итак класс Sandbox содержит набор частиц (я далее буду называть их партиклами, как более устоявшееся название в геймдеве). И соответственно весь движок у нас будет партикловый, то есть основанный на точках - частицах. В Sandbox содержатся также и крупные объекты (SolidObject) но отрисовываются они тоже с помощью частиц. Почему я использую частицы, а не полигоны, как в стандартных движках? Ну потому что под полигоны заточены видеокарты и они отрисовывают их быстро. Но я их не использую. А моделировать и рисовать частицы - проще и быстрее. Кроме того, меньше мороки при просчете z-буфера, заслонения объектов друг-другом и так далее. И да, мы же исходим из PowderToy, а там был песочек. А еще, партикловый движок - это более оригинальное решение. Ведь я хочу что-то совсем нестандартное. Кроме того, партиклы позволяют не использовать текстуры. Кстати это огромный плюс, как по мне. Конечно у партиклов есть и недостатки. Их полно. В первую очередь - малая информативность, отсутствие детализации объектов. Но, надеюсь, мы с этим поборемся. И еще, выбор партикловой модели довольно хорошо ложится на тематику космоса. Здесь нет теней (по крайней мере, у звезд - так точно). И малые объекты находятся на таком большом расстоянии, что они и должны выглядеть как точка. Так, теперь перейдем к графике. Как и в PowderToy я буду использовать промежуточный битмап, в который собственно я и буду рендерить партиклы. А затем сам битмап будет отрисовываться в отдельной панели и в отдельном потоке. Это дает возможность разделить потоки рендеринга и собственно вывода на экран. Кроме того, это дает возможность сделать GUI интерфейс более отзывчивым, с высоким FPS, что очень сильно повышает играбельность шутера. Поскольку вы можете смотреть в разные стороны и стрелять со скоростью 65 FPS, и это не зависит от того с каким FPS обрабатывается физика и рендерятся частицы. Они как раз работают медленнее. Особенно в определенные сложные игровые моменты, когда появляется много частиц - физика и рендер могут лагнуть. Но GUI, мышка и экран будут по прежнему работать очень быстро. Игрок просто заметит, что частицы на экране будут двигаться медленнее, но дергаться кадры не будут. Теперь я немного остановлюсь на математической модели игры в плане графики. Я не буду использовать стандартные методы, основанные на матрицах. Почему? Ну вот так захотелось. Crazy development потому что. Ну на самом деле конечно не просто так я не использую матрицы. Почему матрицы нам почти не пригодятся - станет ясно чуть позже. Итак. У нас есть трехмерный мир частиц-точек. А нам нужно нарисовать их на двумерном экране. В обычной ситуации я бы просто сделал проективную матрицу и умножил на нее все координаты точек. Но вместо этого, я буду переводить точки в сферическую систему координат, с центром в точке, где находится игрок. Ниже приведен рисунок с расположением осей: (прошу прощения за качество рисунков, но мне так быстрее и проще - рисовать их от руки) После того, как точки спроецированы в сферические координаты, у меня есть зенитный и азимутальный углы для каждой точки. А также расстояние до точки. Далее, наша камера игрока будет смотреть только вперед с возможностью немного смотреть вверх, и немного - вниз. То есть из всей сферы мы будем видеть только относительно небольшую полосу вдоль экватора сцены. Если бы мы делали честную проекцию сферы на плоскость экрана, нам бы потребовался синус зенитного угла. Но поскольку мы смотрим только на экватор, то вместо синуса можно взять просто сам зенитный угол, пользуясь тем, что Sin(x)~x при малых x (замечу, что я изначально считаю не зенитный угол, а PI/2 минус зенитный угол, то есть отсчитываю его от горизонта, так удобнее). Итак, область вдоль экватора сферы отображается на плоскость, имеющую размеры 800 пикселов по вертикали и 3600 - по горизонтали. Это наш буферный битмап, в который мы будем рендерить наши частицы. Рисунок ниже показывает идею: Таким образом, азимутальный угол фактически является координатой X на битмапе, а зенитный угол - координатой Y. Оба естественно умножаются на масштабирующий коэффициент. При этом, игроку показывается не весь битмап, а лишь небольшая его часть размером 600x400. Это и есть та область, куда смотрит игрок и которая отображается на экране. Окно просмотра может передвигаться по битмапу, в зависимости от движения мышкой. Таким образом игрок просматривает пространство вокруг себя и немного выше и немного ниже себя. Забавно, я пытаюсь выжать максимальную производительность, но при этом рендеринг у меня происходит всей сцены вокруг игрока, а не только той части, куда он смотрит. Но по-другому - нельзя. Почему - станет понятно далее. Тут еще нужно остановиться немного на игровой механике, которую я буду использовать. Обычно в космических играх корабль может двигаться и вращаться в любых направлениях. Я так делать не буду. Мой игрок сможет передвигаться только в двумерной плоскости. Смотреть и стрелять он может в любом направлении, и вверх и вниз. Но двигаться - только вперед/назад, вправо/влево. Это дает несколько преимуществ. Первое - это проще. Проще делать карту, проще делать остальную механику, проще делать дизайн уровней. Второе - управление кораблем будет намного проще и привычнее, чем управление кораблем в полноценном 3D. Фактически передвижение - стандартное для обычных шутеров: клавиши WS - вперед/назад и AD - стрейф влево/вправо. Ну а кроме того, у меня от постоянного кручения начинает болеть голова :D Еще одно преимущество двумерного передвижения - я смогу так закрыть некоторые объекты от посещения игроком. Например, если я не хочу, что бы игрок влетел в звезду - я просто расположу ее немного ниже плоскости игрока. И тогда он сможет ее видеть, но не сможет в нее влететь. Если же объект должен быть доступен игроку - наоборот, располагаем его в плоскости. Теперь переходим к самому интересному - к картинкам. После всех манипуляций, описанный выше. Я смог получить примерно следующее: Неплохо, но не впечатляет. Совсем. И тут применяем главную фишку рендеринга - размытие. Будем делать размытие картинки, блюр. Причем будем делать его постоянно, в отдельном потоке. И при этом на каждом цикле будем использовать предыдущее размытое изображение. То есть буферный битмап никогда не будет очищаться. Новые положения частиц мы будем просто отрисовывать поверх старого битмапа. Затем битмап будет немного размываться, и снова процесс повторяется. В результате получаем следующее: Намного лучше, да? Размытие дает нам следующее. Первое - виден след от движения частиц, создается иллюзия движения с хвостом, типа комет. Второе - создается гало вокруг крупных объектов типа звезд. Третье - собственно размытие позволяет видеть само тело звезд, а не видеть только отдельные партиклы. Еще несколько картинок: Итак, рисуется красиво. Эпичненько. Симпатично. Это плюс. Но не все так гладко. Самое плохое в таком подходе то, что битмап никогда не очищается. Пока игрок находится на месте, а только крутит головой - все хорошо. Но когда он начинает сам быстро двигаться мимо других объектов, их местоположение на битмапе сдвигается. А поскольку битмап не очищается, то на их старом месте еще довольно долго видно характерное пятно. Это неприятно. И мы будем с этим бороться. Во-первых звезды у нас будут далеко от игрока. Если тело далеко - эффект не заметен. А близко подлетать к звездам игроку не дадим. Да это и в реальности невозможно сделать - там огромная температура. С мелкими и близкими объектами - сложнее. Их относительное движение всегда велико. С кораблями противника я более менее знаю что делать. С планетами - пока нет. Будем думать. Теперь о хорошем. О производительности. Основной лаг системы - в операции размытия битмапа. Ведь он имеет довольно большой размер 3600x800. А это почти 3 млн пикселов. Причем, не забываем, что изображение имеет 4 цветовых канала, то есть получается массив размером 11 мегабайт. Поэтому стояла первоочередная задача как можно быстрее его размывать и желательно с хорошим качеством. Сначала я приведу код, который довольно полезен, хотя я его и не применяю в данном случае. Вот здесь описан быстрый метод гауссовского размытия изображений, но на неизвестном мне языке. А здесь девушка пыталась перевести его на C#. Но ее код был местами неоптимален и с ошибками. Я исправил ошибки и оптимизировал его. Выкладываю результат, может кому пригодится: GaussianBlur
GaussianBlurUnsafe
Приведены два статических класса, один - обычный, другой - unsafe. Небезопасный дает небольшой прирост производительности (процентов на 10-20). Ниже приведены примеры размытия: Первый - исходный квадратик. Второй - размытый box-блюром, и третий - гауссовским блюром. Оба - с радиусом размытия 4. Для этих методов у меня получилось добиться производительности 9-10 fps для гаусса, и около 16 fps - для box. Это для размера изображения 600x400. Но у меня изображение гораздо больше. И производительности этих методов - не хватило. Я написал собственный метод размытия, и максимально оптимизировал его. Выше на картинке он справа. Код: Blur
Поскольку в моем методе размытие происходит только на один пиксел за раз, то для получения результата, аналогичного картинкам выше, нужно вызвать метод несколько раз. Для картинки выше я пробежался по по пикселам 6 раз. Но даже при этом быстродействие моего метода составило 42 fps, против 16 fps для box. Правда, если нужно размывать изображения с большим радиусом размытия, то box и гаусс могут оказаться быстрее. Но мне это не требовалось. Мне вполне достаточно размывать картинку на 1 пиксел на каждом кадре, но зато это должно делаться очень быстро. В результате, размытие буферного битмапа 3600x800 моим методом делается примерно на скорости 45 fps. На самом деле я бы хотел 65 fps, но выше поднять не удалось. Остановился на этом. Кстати, для размытия можно было применять методы из WPF. Но мы же договорились - никакого DirectX :D И да, в середине дня я говорил что вынужден рендерить все пространство вокруг игрока, а не только то, куда он смотрит. Это нужно потому, что нам нужно постоянно делать размытие всего мира вокруг. Иначе, если игрок повернет голову, то он увидит неразмытое изображение, что не годится. Весь проект целиком: Исходник Бинарник Сразу прошу прощения за качество кода. Код местами грязный. Он находится в сыром виде, не вылизан, и является по сути просто полигоном для отработки алгоритмов. Для максимального быстродействия нужно компилировать в Release, Ctrl-F5. Это важно, потому что в Debug производительность упадет раза в два. Управление - WASD. Одновременное нажатие с shift - включает турбо режим, и можно долететь до звезды :) (пока только для тестов, потом будет отключено). По итогам: моделирование физики идет с производительностью 48 fps, рендеринг на битмап около 40 fps, отрисовка 65 fps. День получился насыщенный. И все что нужно я не успел. Поэтому всякие разности переносятся на День Четвертый. Продолжение следует... | |||||||||||||||
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 2
Комментарии
-
На форуме ты далеко не первый, кто пытается сделать игру. Но все, кто пошли твоим путём развития, в итоге сливались. Суть сводится к следующему. Человек очень глубоко закапывается в графику, в красивости отрисовки, вылизывание мельчайших деталей. А когда дело доходит до создания игрового процесса, то всё обламывается, потому что наконец приходит сознание, что красивая графика - это всего лишь малая часть игры, а построение непосредственно игрового процесса - это намного более сложное занятиеЗапись от Evg размещена 28.03.2016 в 09:38
-
Запись от Storm23 размещена 28.03.2016 в 11:58


