Часть 5. Простой редактор карты.
Изменения в файлах:
models.py → Добавлен класс GameWorld, изменен GameObject
roguelike.py → Добавлена возможность запоминать положение объектов через вывод их в JSON формате в консоль.
Более подробное описание урока Далее...
Краткое описание изменений.
Код игры переписан, для большего соответствия стандарту оформления кода Python - PEP8, добавлен класс GameWorld и объект этого класса World, в котором хранятся различные свойства игрового мира. В GameObject добавлен словарь obj_props, в который можно поместить любое количество произвольных свойств объекта, чтобы не добавлять их каждый раз в программный код класса. Сделан режим Редактирования, который включается/выключается кнопкой [e] на клавиатуре, он позволяет двигать любые объекты и при отключении той же кнопкой, в консоль выводится строка в JSON-формате с описанием всех объектов игры. Она позволяет сохранить положение объектов при следующем запуске игры, таким образом реализован простой редактор карты. В дальнейших уроках будет возможность загрузить карту из .json файла, созданного редактором карт TileD.Также добавлено отображение рамок объектов кнопкой [r].
Подробное описание:
Ранее при запуске игры объекты располагались на карте случайным образом, теперь их положения и свойства считываются в JSON-формате из переменной game_objects_json:assets_dict = {} ### словарь с именем asset-картинки и соответствующим ей списком картинок
game_objects_json = "[{\"posX\": 281, \"posY\": 162, \"obj_props\": {\"ObjectName\": \"big bag\", \"CanMove\": \"True\", \"asset_index\": 8, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 249, \"posY\": 172, \"obj_props\": {\"ObjectName\": \"barrel\", \"asset_index\": 4, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 324, \"posY\": 190, \"obj_props\": {\"ObjectName\": \"open chest\", \"asset_index\": 1, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 234, \"posY\": 192, \"obj_props\": {\"ObjectName\": \"small bag\", \"CanMove\": \"True\", \"asset_index\": 6, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 116, \"posY\": 64, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_index\": 1, \"asset_name\": \"assets/trees1.png\"}, \"height\": 200, \"width\": 228}, {\"posX\": 312, \"posY\": 70, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_index\": 0, \"asset_name\": \"assets/trees1.png\"}, \"height\": 200, \"width\": 228}, {\"posX\": 242, \"posY\": 231, \"obj_props\": {\"ObjectName\": \"chest\", \"asset_index\": 2, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 329, \"posY\": 240, \"obj_props\": {\"ObjectName\": \"box\", \"asset_index\": 0, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 87, \"posY\": 93, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_index\": 5, \"asset_name\": \"assets/trees1.png\"}, \"height\": 200, \"width\": 228}, {\"posX\": 315, \"posY\": 264, \"obj_props\": {\"ObjectName\": \"bag\", \"CanMove\": \"True\", \"asset_index\": 7, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 255, \"posY\": 264, \"obj_props\": {\"ObjectName\": \"open barrel\", \"asset_index\": 5, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 284, \"posY\": 271, \"obj_props\": {\"ObjectName\": \"open chest\", \"asset_index\": 3, \"asset_name\": \"assets/Items1.png\"}, \"height\": 64, \"width\": 32}, {\"posX\": 323, \"posY\": 125, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_index\": 4, \"asset_name\": \"assets/trees1.png\"}, \"height\": 200, \"width\": 228}]"
list_from_json = json.loads(game_objects_json) ### загружаем json-описание всех объектов
for obj in list_from_json: ### для каждого из объектов
asset_name = obj['obj_props']['asset_name'] ### имя файла asset-картинки
asset_index = obj['obj_props']['asset_index'] ### индекс картинки в asset-e, которая соответствует данному объекту
if asset_name not in assets_dict: ### если такого имени файла нет в словаре asset-ов, добавляем, если уже есть, пропускаем добавление
assets_dict[asset_name] = loadSpritesheet(asset_name, int(obj['width']/gameScale), int(obj['height']/gameScale), xScale=gameScale, yScale=gameScale)
cur_sprite = assets_dict[asset_name][asset_index] ### картинка текущего объекта - берем из asset-а по индексу
gameObjectList.append(models.GameObject([obj['posX'],obj['posY']], cur_sprite, obj['obj_props'])) ### добавляем в список всех игровых объектов
Имена объектов("Сундук", "Ящик" и пр.) изменены на английские, для удобочитаемости JSON-строки. В самой строке можно увидеть дополнительное свойство "CanMove", которое дописано только объектам-мешочкам(bag, small bag и big bag). Благодаря этому свойству, мешочки можно двигать, не включая режим Редактирования.
При нажатии кнопки [e] на клавиатуре, включается World.edit_mode - режим Редактирования, чтобы каждый можно было двигать мышкой.
В обычном режиме игры, двигаются только те объекты, у которых в наборе свойств obj_props есть элемент "CanMove", равный 'True'.
При отключении режима Редактирования повторным нажатием [e], в консоль выводится JSON-строка, с описанием всех объектов и их свойств в таком виде:
"[{\"height\": 64, \"posY\": 162, \"obj_props\": {\"ObjectName\": \"big bag\", \"asset_name\": \"assets/Items1.png\", \"CanMove\": \"True\", \"asset_index\": 8}, \"posX\": 281, \"width\": 32}, {\"height\": 64, \"posY\": 172, \"obj_props\": {\"ObjectName\": \"barrel\", \"asset_name\": \"assets/Items1.png\", \"asset_index\": 4}, \"posX\": 249, \"width\": 32}, {\"height\": 64, \"posY\": 190, \"obj_props\": {\"ObjectName\": \"open chest\", \"asset_name\": \"assets/Items1.png\", \"asset_index\": 1}, \"posX\": 324, \"width\": 32}, {\"height\": 64, \"posY\": 192, \"obj_props\": {\"ObjectName\": \"small bag\", \"asset_name\": \"assets/Items1.png\", \"CanMove\": \"True\", \"asset_index\": 6}, \"posX\": 234, \"width\": 32}, {\"height\": 200, \"posY\": 64, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_name\": \"assets/trees1.png\", \"asset_index\": 1}, \"posX\": 116, \"width\": 228}, {\"height\": 200, \"posY\": 70, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_name\": \"assets/trees1.png\", \"asset_index\": 0}, \"posX\": 312, \"width\": 228}, {\"height\": 64, \"posY\": 233, \"obj_props\": {\"ObjectName\": \"chest\", \"asset_name\": \"assets/Items1.png\", \"asset_index\": 2}, \"posX\": 240, \"width\": 32}, {\"height\": 64, \"posY\": 240, \"obj_props\": {\"ObjectName\": \"box\", \"asset_name\": \"assets/Items1.png\", \"asset_index\": 0}, \"posX\": 329, \"width\": 32}, {\"height\": 200, \"posY\": 93, \"obj_props\": {\"ObjectName\": \"Tree\", \"asset_name\": \"assets/trees1.png\", \"asset_index\": 5}, \"posX\": 87, \"width\": 228}, {\"height\": 64, \"posY\": 264, \"obj_props\": {\"ObjectName\": \"bag\", \"asset_name\": \"assets/Items1.png\", \"CanMove\": \"True\", \"asset_index\": 7}, \"posX\": 315, \"width\": 32}, {\"height\": 64, \"posY\": 264, \"obj_props\": {\"ObjectName\": \"open barrel\", \"asset_name\": \"assets/Items1.png\", \"asset_index\": 5}, \"posX\": 255, \"width\": 32}, {\"height\": 64, \"posY\": 271, \"obj_props\": {\"ObjectName\": \"open chest\", \"asset_name\": \"assets/Items1.png\", \"asset_index\": 3}, \"posX\": 284, \"width\": 32}]"Эту строку можно скопировать из консоли и вставить прямо в код скрипта roguelike.py в переменную game_objects_json, в результате положение объектов и их свойства будут сохранены при следующем запуске игры.
Строка import json в начале roguelike.py подключает библиотеку с функциями вывода JSON-строки:
all_objects_dict = [] ###список со всеми объектами
for i in range(len(gameObjectList) - 1): ### Для всех оюъектов в игре
cur_obj = gameObjectList[i] ###текущий объект
new_obj_dict = {'obj_props' : cur_obj.obj_props, ### все свойства объекта заносим во вложенный словарь
'posX' : cur_obj.pos[0], ### Позиция X
'posY' : cur_obj.pos[1], ### Позиция Y
'width' : cur_obj.img_rect.w, ### Ширина картинки
'height' : cur_obj.img_rect.h ### Высота картинки
}
all_objects_dict.append(new_obj_dict) ### добавляем новый объект с описанием в список всех объектов
json_dump = json.dumps(all_objects_dict) ### выгружаем описание всех объектов игры в json-формате
print(json.dumps(json_dump)) ### выводим полученный json в консоль
Включение/выключение режимов редактирования и подсветки прямоугольных рамок объектов делается проверкой события нажатия клавиш KEYUP:
elif event.type == pygame.KEYUP:
if event.key == pygame.K_e:
Проверяет нажатие кнопки [e],elif event.key == pygame.K_r:
Проверяет нажатие [r]В режиме Редактирования, также, появляется синяя рамка вокруг игрока:
if World.edit_mode != None: ### Если режим EDIT
player_rect = pygame.Rect(player_pos[0], player_pos[1], player.rect[2], player.rect[3]) ### Расчитываем прямоугольник вокруг игрока
pygame.draw.rect(screen, (0,0,255), player_rect, 2) ### рисуем рамку игрока
Объект World нового класса GameWorld хранит состояние мира, в том числе включен ли режим Редактирования и Подсветки рамок объектов.
Белой рамкой подсвечивается весь объект, не учитывая прозрачных областей, эта рамка высчитывается при создании объекта в скрипте models.py. Зеленая рамка у объектов - это прямоугольник для обнаружения столкновений, она понадобится в дальнейшем, для построения пути в pathfinding.
Теперь мы можем двигать объекты и сохранять их состояние в JSON-строке:
Вопросы, замечания и обсуждение в комментариях к богу и в Discord: https://discord.gg/hqx6Kg9
Супер! Ждем продолжения и новых разборов других типов игр!
ОтветитьУдалить