Пошаговая RPG в Excel (Начало)

Вам бывает скучно на работе? Часто сидите два-три дня без единого намёка на трудовую деятельность и пытаетесь ее имитировать, а руководство так и ждет ошибки с вашей стороны?

Теперь этого можно будет избежать, ведь в разработке находится пошаговая RPG в стиле Dragon Quest на NES для офисного приложения Excel.

Пошаговая RPG в Excel (Начало)

Конечно, идея не нова. В Excel и раньше делали разнообразные игры (даже полноценный шутер). Но если вы читали мои предыдущие статьи, то поймёте, что создавать троллейбусы из батонов белого (или чёрного) хлеба — мое небольшое хобби.

Перейдём же к делу

На данный момент готова альфа-версия графического движка и редактор карт. С вероятностью в 99% они будут дорабатываться в процессе разработки.

Сперва немного расскажу о некоторых технических характеристиках.

1. Скорее всего в игре будет использоваться событийная модель, а не циклическая, ведь для пошаговой стратегии цикл не так уж и важен. Это будет зависеть от того, как быстро Excel справится с рендером одного кадра. Минус такого подхода заключается в том, что написать какой-нибудь платформер на этой основе не получится. В любом случае я попробую оба варианта.

2. Все текстуры имеют размер 16*16 пикселей. Палитра состоит всего из 56 цветов (стандартный размер палитры Excel). В качестве основы я взял палитру NES.

3. Текстуры я рисую в программе Aseprite, а в Excel из. bmp перевожу с помощью небольшого, написанного на VBA софта, который нашел в интернете.

4. Бэкграунд состоит из тайлов, спрайты же привязаны к системе координат.

Графический движок

Карта уровня представляет собой двумерный массив с кодовым обозначением тайла в формате «XXXY», где XXX — номер текстуры по порядку, Y — значение, указывающее на то, можно ли пройти сквозь тайл. Вторая функция пока что не реализована.

Спрайты хранятся в отдельном массиве в формате: координата X, координата Y, порядковый номер спрайта, тип спрайта и тэг. Два последних значения пока не используются.

Пошаговая RPG в Excel (Начало)

Для создания графического движка сперва необходимо инициализировать различные переменные. Часть из них хранится на отдельном листе, а в коде я сделал их публичными (знаю, что так нельзя):

- высота и ширина игрового экрана;

- диапазон игрового экрана;

- массив игрового экрана для рендеринга;

- размер одной текстуры;

- координаты камеры;

- координаты игрока;

- номер уровня;

- карта тайлов и координаты спрайтов;

- массив спрайтов, тайлов и спрайтов игрока.

Пошаговая RPG в Excel (Начало)

Все текстуры хранятся на отдельном листе с отображением индексов цветов палитры.

Пошаговая RPG в Excel (Начало)

Так как это только первая версия движка, нажатие на «Новую игру» тут же запускает метод fillWithTextures (процесс создания массива цифровых значений цвета).

'##########ЯДРО ГРАФИЧЕСКОГО ДВИЖКА########## Sub fillWithTextures() Dim cntRow As Integer, cntCol As Integer, pixOffsetX As Double, pixOffsetY As Double, _ mapBlockX As Integer, mapBlockY As Integer, texXOffset As Integer, texYOffset As Integer, _ texNumber As Integer, arrBlockTex() As Variant Dim cntSprites As Integer, spriteOffsetX As Integer, spriteOffsetY As Integer, _ spriteNumber As Integer ReDim arrRender(main.screenH, main.screenW) For cntRow = 1 To main.screenH For cntCol = 1 To main.screenW 'Считаем смещение пикселя экрана относительно координат камеры pixOffsetX = main.cameraX + cntCol pixOffsetY = main.cameraY + cntRow 'считаем спрайты For cntSprites = 1 To UBound(arrMapSprites(), 1) 'считаем смещение спрайта относительно пикселя spriteOffsetX = pixOffsetX - arrMapSprites(cntSprites, 1) spriteOffsetY = pixOffsetY - arrMapSprites(cntSprites, 2) 'если пиксель содержит спрайт If spriteOffsetX >= 0 And spriteOffsetY >= 0 And spriteOffsetX + pixOffsetX < pixOffsetX + main.blockSize _ And spriteOffsetY + pixOffsetY < pixOffsetY + main.blockSize Then 'если элемент спрайта не пуст If arrSprites((spriteOffsetY + blockSize * arrMapSprites(cntSprites, 3)) + 1, spriteOffsetX + 1) <> 0 Then arrRender(cntRow, cntCol) = arrSprites((spriteOffsetY + blockSize * arrMapSprites(cntSprites, 3)) + 1, spriteOffsetX + 1) End If End If Next 'рисуем тайлы If (pixOffsetX > 0 And pixOffsetX < main.mapWidth) And _ (pixOffsetY > 0 And pixOffsetY < main.mapHeight) Then 'расчет тайла, в который входит пиксель mapBlockX = WorksheetFunction.RoundUp(pixOffsetX / main.blockSize, 0) mapBlockY = WorksheetFunction.RoundUp(pixOffsetY / main.blockSize, 0) 'определяем номер текстуры texNumber = arrMapTiles(mapBlockY, mapBlockX) 'Определение цвета текстуры для пикселя texXOffset = getTexOffset(pixOffsetX) + 1 texYOffset = getTexOffset(pixOffsetY) + 1 If arrMapTiles(mapBlockY, mapBlockX) <> "" And arrTiles(texNumber * _ main.blockSize + texYOffset, texXOffset) <> "" And arrRender(cntRow, cntCol) = "" Then arrRender(cntRow, cntCol) = arrTiles(texNumber * main.blockSize + texYOffset, texXOffset) End If End If Next Next End Sub

Первым делом происходит поиск спрайтов, которые нужно отрисовать. Для этого программа проверяет каждый «пиксель» игрового экрана, считает смещение этого пикселя относительно стартовых координат камеры, а также смещение относительно всех спрайтов на уровне. Если смещение «пикселя» относительно спрайта по каждой оси равно от 0 до 15 (так как размер текстуры 16*16), берётся индекс нужного цвета из массива спрайтов.

Вторым пунктом программа на основе смещения относительно координат камеры высчитывает позицию «пикселей» на карте тайлов. Когда нужный тайл найден, программа с помощью функции getTextOffset возвращает индекс цвета пикселя из массива тайлов.

'Возвращает координату текстуры Function getTexOffset(dCoordinate As Double) As Integer getTexOffset = (dCoordinate - 1) Mod main.blockSize End Function

Почему сперва происходит проверка спрайтов, а затем тайлов?

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

P.S. На следующий день я понял, что проверка условия «закрашенности» спрайтами должна производиться в начале. Тогда это повлияет на производительность. Привет, оптимизация.

Вторая проблема — это проверка спрайтов для каждого пикселя экрана. Предположим, что на уровне находится 40 спрайтов. При размере экрана 96*64 = 6144 пикселей количество итераций цикла достигает 6144 * 40 = 245760. Если пойти другим путём и проверять спрайты не для каждого пикселя, а по условию нахождения в поле зрения камеры, то количество итераций не превысит 40*16*16 = 10240. Эта проблема решается быстро.

Создание игрока и рендеринг изображения

Для того, чтобы поместить спрайт игрока в рендер-массив нужно посчитать смещение спрайта относительно координат камеры.

Sub renderPlayer(imagePose As Integer) Dim offsetX As Double, offsetY As Double, cntRow As Integer, _ cntCol As Integer 'Считаем смещение относительно координат камеры offsetX = main.playerX - main.cameraX offsetY = main.playerY - main.cameraY 'Если игрок не находится за пределами камеры If offsetX >= 0 And offsetY >= 0 And _ offsetX < main.cameraX + main.screenW And _ offsetY < main.cameraY + main.screenH Then For cntRow = 0 To main.blockSize - 1 For cntCol = 0 To main.blockSize - 1 'Если пиксель текстуры заполнен If main.arrPlayerSpr((cntRow + 1) + (imagePose * main.blockSize), cntCol + 1) <> 0 Then 'Если пиксель не заходит за пределы камеры If cntRow + main.playerY <= main.screenH + main.cameraY And cntCol + main.playerX <= main.screenW + main.cameraX Then arrRender(offsetY + cntRow, offsetX + cntCol) = main.arrPlayerSpr((cntRow + 1) + (imagePose * main.blockSize), cntCol + 1) End If End If Next Next End If End Sub

Аргумент imagePose будет использоваться для имитации поворота игрока при движении.

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

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

Вот, что получается в итоге:

Пошаговая RPG в Excel (Начало)

На этом можно пока закончить. Надеюсь, что из ваших глаз не пошла кровь от «лучшего в мире» языка программирования и попыток написать что-то осмысленное.

В любом случае жду критику и советы. Всегда интересно послушать, что скажут люди.

Немного саморекламы

Я создал паблик ВКонтакте, куда буду выкладывать свои мысли, алгоритмы, код, ссылки на эти статьи и конечно мемасики:) Если вам интересно наблюдать за разработкой игр и разных странных вещей, добро пожаловать.

9494
31 комментарий