Давайте сделаем рогалик (Бродим)
Мы достигли важного момента в жизни нашего Подземелья Судьбы, а именно, добавление кода передвижения нашего персонажа. Мы сможем передвигаться по уровню, открывать двери и даже спуститься по лестнице на новый уровень подземелья и исследовать его. Конечно, на данном этапе, подземелье пустынно, но это уже начинает напоминать игру.
Исправление ошибок
Прежде, чем приступить к рассмотрению кода передвижения персонажа, мы должны вернуться к коду из предыдущей главы и исправить некоторые ошибки. Как я и говорил в предыдущей главе — код передвижения персонажа поможет нам выявить ошибки в процедуре генерации уровней подземелья. Эта книга о разработке реальной игры, и с похожими проблемами вы столкнетесь при разработке своего приложения.
Была найдена небольшая ошибка в процедуре DrawMap. Старый код выглядел следующим образом:
map.bi
'Нарисуем видимую часть карты. For x = 1 To w For y = 1 To h 'Очистим текущее знакоместо (нарисуем черный квадрат). tilecolor = fbBlack PutText acBlock, y, x, tilecolor 'Напечатаем тайл. If _level.lmap(i + x, j + y).visible = True Then 'Получим ID тайла tile = _level.lmap(i + x, j + y).terrid 'Получим ASCII символ тайла mtile = _GetMapSymbol(tile) 'Получим цвет тайла tilecolor = _GetMapSymbolColor(tile) 'Нарисуем маркер предмета. If _level.lmap(i + x, j + y).hasitem = True Then 'Тут обрабатываем предмет. Endif PutText mtile, y, x, tilecolor 'Если на тайле монстр, то нарисуем его If _level.lmap(i + x, j + y).hasmonster = TRUE Then 'Тут обрабатываем монстра. Endif Else 'Не на прямой видимости. If _level.lmap(i + x, j + y).seen = TRUE Then If _level.lmap(i + x, j + y).hasitem = True Then PutText "?", y, x, fbSlateGrayDark Else PutText mtile, y, x, fbSlateGrayDark End If End If End If Next Next
Обратите внимание, куда мы поместили функции GetMapSymbol и
GetMapSymbolColor — в обработку прямой видимости, в результате, если мы не видим
тайл в данный момент, то при его выводе используется переменная mtitle которая
содержит не код текущего тайла, а значение, которые было в нее записано на более
ранних шагах. Исправить это достаточно просто, нужно вынести эти две функции из
оператора If, тогда все будет работать как и ожидалось.
map.bi
'Нарисуем видимую часть карты. For x = 1 To w For y = 1 To h 'Очистим текущее знакоместо (нарисуем черный квадрат). tilecolor = fbBlack PutText acBlock, y, x, tilecolor 'Получим ID тайла tile = _level.lmap(i + x, j + y).terrid 'Получим ASCII символ тайла mtile = _GetMapSymbol(tile) 'Получим цвет тайла tilecolor = _GetMapSymbolColor(tile) 'Выведем тайл. If _level.lmap(i + x, j + y).visible = True Then 'Нарисуем маркер предмета. If _level.lmap(i + x, j + y).hasitem = True Then 'Тут обрабатываем предмет. Endif PutText mtile, y, x, tilecolor 'Если на тайле монстр, то нарисуем его If _level.lmap(i + x, j + y).hasmonster = TRUE Then 'Тут обрабатываем монстра. Endif Else 'Не на прямой видимости. If _level.lmap(i + x, j + y).seen = TRUE Then If _level.lmap(i + x, j + y).hasitem = True Then PutText "?", y, x, fbSlateGrayDark Else PutText mtile, y, x, fbSlateGrayDark End If End If End If Next Next
Передвижение персонажа
Для того, чтобы игрок перемещал персонажа, нам нужно получить ввод с клавиатуры. В Подземелье Судьбы, для перемещения персонажа, мы будем использовать как клавиши со стрелками, так и клавиши на дополнительной цифровой клавиатуре. Цифровая клавиатура должна быть в режиме NumLock, чтобы мы получали числа, а не клавиши управления. Информацию об этом нужно обязательно добавить в файл справки по игре. Мы будем использовать дополнительную клавиатуру для передвижения на 8 сторон света, а клавиши стрелок — для быстрого перемещения по подземелью. Одним из усовершенствований игры, могла бы быть возможность пользователю задать свой набор клавиш для различных действий в игре, но, для простоты, мы не будем это реализовывать в этой версии игры.
Программная логика довольно проста. После того, как игрок вышел из меню, мы генерируем новый уровень подземелья, отображаем его, а затем входим в цикл сбора нажатых клавиш. Если игрок наживает клавиши направления, то мы попытаемся переместить персонажа. Если игрок наживает клавишу «вниз по лестнице», то мы генерируем новый уровень подземелья. Если клавишу «выход», то мы выходим из главного цикла программы.
dod.bas
'Главний цикл игры. If mm <> mmenu.mQuit Then 'Строим первый уровень подземелья level.GenerateDungeonLevel 'Нарисуем главный экран. DrawMainScreen Do ckey = Inkey If ckey <> "" Then 'Получим клавиши направления со стрелок или нумпада. 'Проверим клавишу вверх и 8 If (ckey = key_up) OrElse (ckey = "8") Then mret = MoveChar(north) If mret = TRUE Then DrawMainScreen Endif 'Проверим 9 If ckey = "9" Then mret = MoveChar(neast) If mret = TRUE Then DrawMainScreen Endif 'Проверим клавишу вправо и 6. If (ckey = key_rt) OrElse (ckey = "6") Then mret = MoveChar(east) If mret = TRUE Then DrawMainScreen Endif 'Проверим 3 If ckey = "3" Then mret = MoveChar(seast) If mret = TRUE Then DrawMainScreen Endif 'Проверим вниз и 2. If (ckey = key_dn) OrElse (ckey = "2") Then mret = MoveChar(south) If mret = TRUE Then DrawMainScreen Endif 'Проверим 1 If ckey = "1" Then mret = MoveChar(swest) If mret = TRUE Then DrawMainScreen Endif 'Проверим влево и 4. If (ckey = key_lt) OrElse (ckey = "4") Then mret = MoveChar(west) If mret = TRUE Then DrawMainScreen Endif 'Проверим 7 If ckey = "7" Then mret = MoveChar(nwest) If mret = TRUE Then DrawMainScreen Endif 'Спуск на нижний уровень. If ckey = ">" Then 'Проверим, есть ли лестница. If level.GetTileID(pchar.Locx, pchar.Locy) = tstairdn Then 'Строим новый уровень подземелья. level.GenerateDungeonLevel 'Нарисуем главный экран. DrawMainScreen End If Endif End If Sleep 1 Loop Until ckey = key_esc Endif
Вначале мы проверяем, была ли нажата клавиша. Функция InKey возвращает
строку с символом нажатой клавиши или пустую строку, если буфер клавиатуры пуст.
Если какая либо клавиша была нажата, то, при помощи нескольких команд If — Then,
в зависимости от того, какая клавиша нажата, мы выполняем то или иное
действие.
dod.bas
... 'Проверим клавишу вверх и 8 If (ckey = key_up) OrElse (ckey = "8") Then mret = MoveChar(north) If mret = TRUE Then DrawMainScreen Endif ...
Этот код проверяет, нажата ли клавиша вверх, или клавиша 8 на цифровой
клавиатуре. Все остальные клавиши проверяются аналогичным образом. Если стрелка
вверх или клавиша 8 нажата, то мы вызываем функцию MoveChar, для перемещения
персонажа. Параметр, передаваемый в MoveChar, это одно из восьми направлений
света, в данном случае это северное направление. Функция возвращает «истина»
если персонаж переместился, или «ложь» - если он не может двигаться в указанном
направлении. Если персонаж не двигался, то нам не нужно перерисовывать дисплей,
что сэкономит немного процессорного времени, и, как результат, сделает программу
несколько быстрее. Теперь давайте рассмотрим функцию MoveChar.
commands.bi
'Перемещение персонажа по сторонам свчета. Function MoveChar(comp As compass) As Integer Dim As Integer ret = FALSE, block Dim As vec vc = vec(pchar.Locx, pchar.Locy) 'Создадим вектор. Dim As terrainids tileid vc+= comp 'Проверим на выход за пределы карты. If (vc.vx >= 1) And (vc.vx <= mapw) Then If (vc.vy >= 1) And (vc.vy <= maph) Then 'Проверим на блокирующий перемещение тайл. block = level.IsBlocking(vc.vx, vc.vy) 'Перемещаем персонажа. If block = FALSE Then 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE Else 'Проверим специфические типы местности. 'Получим id местности. tileid = level.GetTileID(vc.vx, vc.vy) Select Case tileid Case tdoorclosed 'Закрытая дверь. ret = OpenDoor(vc.vx, vc.vy) 'Если дверь не открылась — выведем сообщение. If ret = FALSE Then 'тут сообщение выводим. Else 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE Endif Case tstairup 'Возможно перемещение по лестнице вверх. 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE End Select Endif Endif Endif Return ret End Function
Первое, что бросается в глаза, это то, что здесь мы используем объект
вектора, который мы рассмотрим позже, упрощающий нам расчет координат. Оператор
+= для него был перегружен, для того, чтобы автоматически получить новые
координаты персонажа в нужном направлении. Как только мы получаем новые
координаты, нам нужно проверить, вначале, что они лежат в рамках нашего массива
карты, а затем мы проверяем, находится ли в новых координатах на карте
блокирующий передвижение тип местности. Функция IsBlocking это новая функция,
которую мы добавили в наш объект карты уровня.
map.bi
'Возвращает истина или лож, если в координатах x, y блокирующий тип местности. Function levelobj.IsBlocking(x As Integer, y As Integer) As Integer Return _BlockingTile(x, y) End Function
Вы можете видеть, что это просто вызов приватного метода BlockingTile,
который возвращает истина, если по указанным координатам на карте находится
блокирующий тип местности, или ложь, если нет. Мы используем возвращаемое
значение, чтобы определить, может ли персонаж находится на данных координатах
карты. Если это так, то мы просто изменяем текущие координаты персонажа и
возвращаем «истина», чтобы указать на успешность перемещения.
commands.bi
... 'Перемещаем персонажа. If block = FALSE Then 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE ...
Пока все хорошо, но в случае, если проход заблокирован, то нам нужно
проверить несколько исключений: лестницы и закрытые двери.
commands.bi
... Else 'Проверим специфические типы местности. 'Получим id местности. tileid = level.GetTileID(vc.vx, vc.vy) Select Case tileid Case tdoorclosed 'Закрытая дверь. ret = OpenDoor(vc.vx, vc.vy) 'Если дверь не открылась — выведем сообщение. If ret = FALSE Then 'тут сообщение выводим. Else 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE Endif Case tstairup 'Возможно перемещение по лестнице вверх. 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE End Select Endif ...
Для того, чтобы узнать, является ли блокирующая проход местность одним из
наших исключений, мы должны получить идентификатор тайла местности. GetTileID,
это еще одна новая функция, которую мы добавили в объект карты уровня.
map.bi
'Возвращает id типа местности по координатам x, y. Function levelobj.GetTileID(x As Integer, y As Integer) As terrainids Return _level.lmap(x, y).terrid End Function
Эта функция получает доступ к приватному массиву карты уровня и
возвращает тип местности, находящийся по координатам x, y. Мы используем
полученное значение в функции MoveChar, чтобы проверить наши исключения. Есть
два случая которые нас интересуют, это закрытая дверь с идентификатором
tdoorclosed и лестница вверх, с идентификатором tstairup. Мы рассмотрим
обработку закрытых дверей позже, вначале давайте посмотрим на обработку более
простого случая — проверка на обнаружение лестницы вверх.
Хотя лестница вверх у нас находится в списке блокирующих типов местности, для того чтобы блокировать прямую видимость, но мы хотим, чтобы персонаж мог на нее стать, чтобы иметь возможность подняться на предыдущий уровень. Для этого мы просто изменим координаты персонажа и вернем «истина», чтобы указать на то, что персонаж переместился.
Если же мы обнаружили закрытую дверь, то мы хотим, чтобы персонаж ее открыл, если дверь не заперта. Было бы весьма утомительно заставлять игрока, каждый раз, когда он подходит к новой двери, нажимать клавишу «o» для того чтобы ее открыть. Большинство людей просто не будут играть в игру, которая их утомляет. Чтобы облегчить им игру, мы просто откроем дверь автоматически, когда персонаж врезается в нее. Однако, это означает, что мы должны проверить дверь, возможно она заперта или заблокирована и, если дверь не заперта, то только тогда открыть ее и переместить на ее место персонажа. Мы делаем это в функции OpenDoor.
commands.bi
'Открывает закрытую дверь, если не заперта. Function OpenDoor (x As Integer, y As Integer) As Integer Dim As Integer ret = TRUE, doorlocked 'Проверим, заперта или нет. doorlocked = level.IsDoorLocked(x, y) If doorlocked = FALSE Then 'Открываем дверь. level.SetTile x, y, tdooropen Else 'Дверь заперта и не может быть открыта. ret = FALSE End If Return ret End Function
Чтобы проверить, заперта ли дверь, мы используем новую функцию. Которую
добавили в объект карты уровня.
map.bi
'Возвращает True если дверь заперта. Function levelobj.IsDoorLocked(x As Integer,y As Integer) As Integer Return _level.lmap(x, y).doorinfo.locked End Function
Как вы можете видеть. Мы объявили новую структуру в нашем объекте карты,
это doorinfo. Следующий код описывает данную структуру.
map.bi
'Описание типа двери. Type doortype locked As Integer 'Истина, если закрыта. lockdr As Integer 'Уровень сложности взлома замка. dstr As Integer 'Сила двери (для выбивания). End Type
Данная структура содержит информацию о двери. Если дверь заперта, то поле
locked будет установлено в «истина», иначе в «ложь». Поле lockdr указывает на
сложность замка, и будет использоваться, когда персонаж будет пытаться его
взломать. dstr — прочность двери, которую мы будем проверять, когда персонаж
будет пытаться выбить дверь силой. Мы вернемся к этому коду позже, когда будем
реализовывать удары персонажа. Информацию о двери мы добавим в нашу структуру
mapinfo.
map.bi
'Описание ячейки карты Type mapinfotype terrid As terrainids 'тип сестности. hasmonster As Integer 'Монстр в текущей ячейке. hasitem As Integer 'Предмет в текущей ячейке. monidx As Integer 'Индекс в массиве монстров. visible As Integer 'персонаж видит ячейку. seen As Integer 'Персонаж видел ячейку. doorinfo As doortype 'Информация о двери. End Type
Поле doorinfo определено как описанный ранее тип doortype. Как вы уже
видели, mapinfotype, это часть массива карты уровня, содержащегося в структуре
levelinfo.
map.bi
'Информация об уровне подземелья. Type levelinfo numlevel As Integer 'Номер текущего уровня. lmap(1 To mapw, 1 To maph) As mapinfotype 'Массив карты уровня. End Type
Мы хотим поместить информацию о двери в массив карты уровня, чтобы иметь
возможность сохранять и загружать эту информацию просто сохраняя и загружая
данный массив.
Для того, чтобы двери нормально работали. Мы должны заполнить данными поле информации двери при создании карта уровня, но, вначале, мы должны очистить информацию о дверях, в процедуре, где мы очищаем массив карты уровня.
map.bi
'Создает новый уровень подземелья. Sub levelobj.GenerateDungeonLevel() Dim As Integer x, y 'Очистим уровень For x = 1 To mapw For y = 1 To maph 'Set to wall tile _level.lmap(x, y).terrid = twall _level.lmap(x, y).visible = FALSE _level.lmap(x, y).seen = FALSE _level.lmap(x, y).hasmonster = FALSE _level.lmap(x, y).hasitem = FALSE _level.lmap(x, y).doorinfo.locked = FALSE _level.lmap(x, y).doorinfo.lockdr = 0 _level.lmap(x, y).doorinfo.dstr = 0 Next Next _InitGrid _DrawMapToArray End Sub
Каждое поле в doorinfo очищается, как и остальные поля в массиве карты.
Теперь, когда мы добавляем на карту дверь, у нас уже имеется очищенная структура
с информацией о этой двери, которую мы можем заполнять различными данными.
map.bi
'Добавляем на карту двери. Sub levelobj._AddDoorsToRoom(i As Integer) Dim As Integer row, col, dd1, dd2 'Проверим горизонтальные стены. For col = _rooms(i).tl.x To _rooms(i).br.x dd1 = _rooms(i).tl.y dd2 = _rooms(i).br.y 'Если в верхней стене пустое место. If _level.lmap(col, dd1).terrid = tfloor Then 'Add door. _level.lmap(col, dd1).terrid = tdoorclosed _level.lmap(col, dd1).doorinfo.locked = FALSE If _level.lmap(col, dd1).doorinfo.locked = TRUE Then _level.lmap(col, dd1).doorinfo.lockdr = 0 _level.lmap(col, dd1).doorinfo.dstr = 0 End If Endif 'Ксли в нижней стене пустое место. If _level.lmap(col, dd2).terrid = tfloor Then _level.lmap(col, dd2).terrid = tdoorclosed _level.lmap(col, dd2).doorinfo.locked = FALSE If _level.lmap(col, dd2).doorinfo.locked = TRUE Then _level.lmap(col, dd2).doorinfo.lockdr = 0 _level.lmap(col, dd2).doorinfo.dstr = 0 End If End If Next 'Проверим вертикальные стены. For row = _rooms(i).tl.y To _rooms(i).br.y dd1 = _rooms(i).tl.x dd2 = _rooms(i).br.x If _level.lmap(dd1, row).terrid = tfloor Then _level.lmap(dd1, row).terrid = tdoorclosed _level.lmap(dd1, row).doorinfo.locked = FALSE If _level.lmap(dd1, row).doorinfo.locked = TRUE Then _level.lmap(dd1, row).doorinfo.lockdr = 0 _level.lmap(dd1, row).doorinfo.dstr = 0 End If End If 'Проверим правую стену. If _level.lmap(dd2, row).terrid = tfloor Then _level.lmap(dd2, row).terrid = tdoorclosed _level.lmap(dd2, row).doorinfo.locked = FALSE If _level.lmap(dd2, row).doorinfo.locked = TRUE Then _level.lmap(dd2, row).doorinfo.lockdr = 0 _level.lmap(dd2, row).doorinfo.dstr = 0 End If Endif Next End Sub
Когда мы добавляем закрытую дверь, мы также устанавливаем ее состояние в
заблокирована или нет. Сейчас все двери отпираются, но позже, мы добавим код,
делающий некоторые двери запертыми. Тогда нам нужно будет устанавливать значения
для сложности взлома замка и прочности двери. Все эти данные нужны для
возможности открытия двери и заполняются при создании карты уровня, хотя само
открытие осуществляется функцией DoorOpen, описанной в файле commands.bi.
commands.bi
'Открывает дверь, если не заперта. Function OpenDoor (x As Integer, y As Integer) As Integer Dim As Integer ret = TRUE, doorlocked 'проверяем, закрыта дверь или нет. doorlocked = level.IsDoorLocked(x, y) If doorlocked = FALSE Then 'Открываем дверь. level.SetTile x, y, tdooropen Else 'Дверь заперта и не может быть открыта. ret = FALSE End If Return ret End Function
Если функция IsDoorLocked возвращает значение «ложно», то значит дверь не
заперта и мы можем ее открыть. Для этого мы должны установить в массиве карты
уровня тип местности tdooropen, воспользовавшись еще одной новой функцией в
объекте нашего уровня — SetTile.
map.bi
'Установить тип местности для координат x, y. Sub levelobj.SetTile(x As Integer, y As Integer, tileid As terrainids) _level.lmap(x, y).terrid = tileid End Sub
Мы просто устанавливаем значение типа местности, в массиве карты уровня,
на открытую дверь, которая не блокирует область видимости и перемещение.
Функция OpenDoor возвращает «истина» если дверь открыта или «ложь», если дверь заблокирована и открыть ее не получилось. Возвращаясь к функции MoveChar, теперь мы можем рассмотреть весь процесс открытия двери целиком.
commands.bi
'проверяем, закрыта дверь или нет. doorlocked = level.IsDoorLocked(x, y) If doorlocked = FALSE Then 'Открываем дверь. level.SetTile x, y, tdooropen Else 'Дверь заперта и не может быть открыта. ret = FALSE End If Return ret
Рассмотрим теперь вариант, когда на своем пути мы встретили закрытую
дверь. Если OpenDoor возвращает «ложь», то мы выводим сообщение, что дверь не
открылась и не перемещаем персонажа. Функция MoveChar вернет ложь в программу
вызвавшую ее. Если дверь можно открыть, то мы перемещаем персонажа и возвращаем
«истина», чтобы сообщить об успешности перемещения.
Как вы можете видеть, добавляя новые возможности в игру, мы постоянно возвращаемся и дополняем/изменяем уже написанный ранее код. Очень важно, чтобы во время этого процесса мы сразу же проверяли внесенные изменения, чтобы не внести ошибку в уже работающий код. Может показаться расточительным — постоянно переписывать уже написанный код, но на самом деле это гарантирует что программа работает правильно и. Если мы обнаружим ошибку, то точно будем знать, что она находится в части кода, который мы только что добавили, и ее гораздо легче будет найти и исправить.
Если бы мы сели и закодировали все функции в один присест, а потом выяснилось, что-то не работает правильно, с чего мы начали искать проблемы? Это может быть в любой из дюжины функций, которые были реализованы, каждый из которых может быть много строк кода. В результате поиск и исправление ошибок окажется долгим и трудоемким процессом. В конечном счете, итеративный процесс кодирования и перекодирования работает быстрее и надежнее, а также, приводит к рабочему коду с намного меньшим количеством расстройств.
Мы не совсем закончили с кодом в функции MoveChar. Нам необходим рассмотреть еще один файл с исходным кодом векторного объекта, который мы использовали в функции передвижения персонажа. Чтобы вы вспомнили, посмотрите на часть кода из MoveChar:
commands.bi
... vc+= comp 'Проверим на выход за пределы карты. If (vc.vx >= 1) And (vc.vx <= mapw) Then If (vc.vy >= 1) And (vc.vy <= maph) Then 'Проверим на блокирующий перемещение тайл. block = level.IsBlocking(vc.vx, vc.vy) 'Перемещаем персонажа. If block = FALSE Then 'Установим новые координаты персонажа. pchar.Locx = vc.vx pchar.Locy = vc.vy ret = TRUE ...
Чтобы получить в векторе новые координаты персонажа, мы просто добавляем
к объекту вектора направление, используя перегруженный оператор + =, а затем
получаем х и у координаты, обращаясь к свойствам объекта. Чтобы разобраться, как
это работает, давайте посмотрим на код объекта вектор.
vec.bi
'Направление на 8 сторон света Enum compass north neast east seast south swest west nwest End Enum 'Описание Типа для 2D вектора. Type vec Private: _x As Integer _y As Integer _dirmatrix(north To nwest) As mcoord = {(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)} Public: Declare Constructor () Declare Constructor (x As Integer, y As Integer) Declare Property vx (x As Integer) Declare Property vx () As Integer Declare Property vy (y As Integer) Declare Property vy () As Integer Declare Operator += (cd As compass) Declare Sub ClearVec() End Type 'Пустой конструктор, создает нулевой вектор. Constructor vec () _x = 0 _y = 0 End Constructor 'Конструктор с инициализацией значений. Constructor vec (x As Integer, y As Integer) _x = x _y = y End Constructor 'Свойства для установки и получения значений x и y координат. Property vec.vx (x As Integer) _x = x End Property Property vec.vx () As Integer Return _x End Property Property vec.vy (y As Integer) _y = y End Property Property vec.vy () As Integer Return _y End Property 'Обновим значения x и y координат, используя направление из compass. Operator vec.+= (cd As compass) If (cd >= north) And (cd <= nwest) Then _x += _dirmatrix(cd).x _y += _dirmatrix(cd).y End If End Operator 'Установить вектор в 0. Sub vec.Clearvec () _x = 0 _y = 0 End Sub
Сперва мы определяем перечисление compass в котором указываем восемь
сторон света, для передвижения каждого актера в игре. Также, мы будем
использовать эти направления, чтобы определить ячейки карты вокруг нашего
персонажа, например, когда персонаж будет в режиме поиска.
Тип вектор содержит две координаты, x и y в приватной секции, а также массив dirmatrix, в котором описаны смещения этих координат для каждой стороны света, перечисленной в compass. Этот массив используется в перегруженном операторе +=.
vec.bi
... 'Обновим значения x и y координат, используя направление из compass. Operator vec.+= (cd As compass) If (cd >= north) And (cd <= nwest) Then _x += _dirmatrix(cd).x _y += _dirmatrix(cd).y End If End Operator ...
Параметр cd, это одно из направлений из compass. Мы проверяем, чтобы
убедиться, что направление является допустимым, а затем добавляем смещение из
dirmatrix к текущем значениям координат х и у вектора. Предполагается, что
вектор был инициализирован координатами текущего местоположение персонажа.
Давайте рассмотрим как это работает. В массиве dirmatrix северному направлению соответствует значение (0,-1), где 0, это смешение по координате x, а -1 — смещение по координате y. Каждое значение из матрицы массива добавляется к соответствующей координате вектора. К x мы добавляем 0 и она остается неизменной, к y мы добавляем -1, что уменьшит ее на единицу. В результате мы получим новые координаты в векторе, просто добавив к нему направление по компасу, как мы это видели в функции MoveChar: vc+= comp. Перегрузка оператора += позволяет нам использовать направление как и любую другую переменную или цифру в программе. Это значительно упрощает получение новых координат вектора и показывает всю силу объектов в процессе облегчения программирования.
Конструктор без параметров необходим для задания значение координат вектора по умолчанию. Он устанавливает координаты x и y в нулевое значение. Значения координат вектора можно менять при помощи свойств vx и vy. Нам это понадобиться, если мы будем использовать объект вектора в цикле, и необходимо будет менять его координаты. Мы увидим это в действии, когда реализуем функции поиска.
Подпрограмма ClearVec используется для задания нулевых координат вектора и необходима, если нам понадобится, по какой либо причине, очистить вектор. Для многих объектов инициализация выделяет для них какие либо ресурсы, поэтому всегда полезно иметь метод, который освобождает эти ресурсы и делает объект вновь готовым к инициализации.
Объект вектора достаточно прост, но облегчает написание кода. Поскольку мы будем расширять возможности нашей программы, мы будем его использовать снова и снова.
Возвращаясь к нашему основному коду в dod.bas, следующая команда, которую нам нужно рассмотреть, это перемещение персонажа вниз по лестнице, для спуска на следующий уровень подземелья.
dod.bas
... 'Проверим, есть ли лестница. If level.GetTileID(pchar.Locx, pchar.Locy) = tstairdn Then 'Строим новый уровень подземелья. level.GenerateDungeonLevel 'Нарисуем главный экран. DrawMainScreen End If ...
Здесь мы используем функцию GetTileID, чтобы убедиться, что персонаж
стоит на лестнице вниз, мы же не хотим, чтобы персонаж мог просачиваться на
нижний уровень сквозь пол! Если персонаж стоит на лестнице вниз, то мы
генерируем новый уровень и отображаем его, начиная с начальной комнаты. Здесь мы
добавили довольно много кода, чтобы заставить персонажа двигаться, но это только
начало! В скором времени мы добавим значительно больше.
Теперь наш персонаж может передвигаться и исследовать подземелья, но основной экран выглядит довольно пустым. Мы должны исправить это. В следующей главе мы придадим основному экрану нашей игры надлежащий вид.
Перевод на русский: Fantik
содержание | назад | вперед