Часть 5. Простой редактор карты.


Рабочие файлы этого урока в (Архиве .ZIP) и на Github: Редактор карты
Изменения в файлах:
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

Комментарии

  1. Супер! Ждем продолжения и новых разборов других типов игр!

    ОтветитьУдалить

Отправить комментарий