Ссылка на репозиторий с кодом. Если вы когда-нибудь работали с игровым движком Ren'Py, то знаете, что вывод спрайта на экран осуществляется следующим образом: character outfit emotion это имя заранее объявленного изображения. Попытка вывести спрайт character emotion без outfit, не приведёт к тому, что спрайт окажется голым. Вместо этого движок будет кидаться в нас traceback'ами. Если принять высокие моральные принципы движка ещё можно, то вот мириться с тем, что для смены эмоции/одежды/аксессуара нам снова нужно прописывать полное имя спрайта, как-то не хочется. Но не стоит отчаиваться, ведь нас выручит layeredimage. layeredimage это мощный инструмент, который позволят декларативно описать изображение, которое состоит из нескольких слоёв и управляется атрибутами. always — изображение которое будет отображаться всегда; group — атрибуты из одной группы, не могут повторяться; attribute — собственно, атрибут. В чём отличие от ручного определения каждого варианта спрайта? Во первых, в удобстве. Во вторых, layeredimage при отображении через show запоминает атрибуты, что позволяет не прописывать полное имя спрайта заново. Само собой, у layeredimage есть ещё очень много крутых фич, но они хороши освещены в документации, так что я не буду здесь подробно останавливаться. Предыдущий пример теперь отработает без ошибок. Теперь встаёт вопрос, а собственно как это всё работает? Система отображения изображений знает о существовании layeredimage и использует для него отдельную логику? Как бы не так. layeredimage(а также модуль live2d для Ren'Py) использует недокументированную фичу, с помощью которой изображение может само обрабатывать атрибуты. Что за атрибуты? Атрибуты это всё то, что идёт после тега. В большинстве случаев, тег это первая часть имени изображения. Но также тег может быть задан и во время показа спрайта через show. И так, что нужно для минимального обработчика атрибутов? Наверное, нужно создать класс, который реализует какой-то интерфейс и реализовывать логику управления атрибутов в нём. Нет. Это вам не Java или C#. Никаких интерфейсов. Да здравствует утиная типизация! Если что-то выглядит как утка, плавает как утка и крякает как утка, это, вероятно, и есть утка. Для того, чтобы отобразить изображение используя переданные пользователем атрибутами, достаточно определить у класса метод _duplicate. В него передаётся специальный объект, который как раз и содержит наши атрибуты. Вот так это выглядит. В методе _duplicate мы взяли атрибуты, а затем отфильтровали слои по ним. Отфильтрованные слои обернули в виджет, который отображает изображения наслаивая их друг на друга. Использовать это можно так: Выглядит классно, но это всё ещё не пригодно к использованию. Доказать это достаточно просто. Тело пропало, а эмоции наслаиваются друг на друга. Первая проблема вызвана тем, что по умолчанию, Ren'Py не запоминает установленные в прошлом атрибуты. Вторая проблема является следствием того, что мы никак не фильтруем атрибуты перед показом. И так, давайте напишем фикс для первой проблемы. Отличие от прошлой реализации одно - появился метод _choose_attributes. Метод _choose_attributes вызывается с новыми атрибутами(required) и атрибутами которые были до вызова(optional). Наша реализация просто складывает эти атрибуты, что решает проблему пропавшего тела. Проверим. Хотя первая проблема решена, но вот вторая никуда не делась. Для её решения, придётся изменить структуру объявления изображения. Теперь каждый слой будет содержать несколько вариантов изображения связанным с атрибутом. Кода много, но мы попытаемся разобраться, что происходит. Метод _choose_attributes теперь проходит по слоям и выясняет, какие из вариантов изображения в каждом слое активны. Если активны несколько вариантов, то это означает, что произошёл конфликт атрибутов и нам необходимо выбросить исключения. Если активен только один вариант, то исключаем из существовавших ранее атрибутов все атрибуты слоя. Это гарантирует нам то, что для одного слоя будет использовано одно изображение. В то же время, логика метода _duplicate меняется не сильно. Проходим по всем слоям и выбираем первый вариант изображения используя атрибуты. В этом методе конфликтов быть не может, так как мы их уже успешно решили в методе _choose_attributes. Настало время тестирования. Все работает, как и ожидалось. Все атрибуты использованные ранее остаются на месте, если они не конфликтуют между собой. Конфликт новых атрибутов выбрасывает исключение. Конечно, текущая реализация очень далека от универсальной. Например, давайте попробуем добавить для нашей Славяны новую позу. Чудо не случилось. Эмоции и позы чётко соотносятся друг с другом и изменив позу, эмоция остаётся прежней. Написать универсальное решение, конечно, можно, но на это уйдёт довольно много времени. Вместо этого, давайте напишем реализацию конкретно для спрайта Слави, а вернее для всех спрайтов из игры "Бесконечное Лето". И так, каждая части спрайта персонажа делятся на 4 типа: Тело(body) Одежда(pioneer, dress, swim) Эмоция(smile, cry, sad) Аксессуар(panama, glasses) Также, каждая часть привязана к позе. Поза определяется при помощи эмоции. Формат имени файла для каждой части имеет следующий вид - {tag}_{body_number}_{image}.png, где: tag - тег персонажа body_number - номер позы image - имя изображения(body, pioneer, smile, glasses) Используя всю вышеперечисленную информацию, мы можем перейти к реализации. Кода много, но он достаточно примитивный. Все неоднозначные моменты, я постарался осветить в комментариях и документации к методам. Результат радует. Поза меняется в зависимости от эмоции. Старые атрибуты сохраняются и заменяются новыми при конфликте. Конфликт новых атрибутов приводит к ошибке. Я постарался рассказать в общих чертах об устройстве того, как Ren'Py работает с атрибутами. Некоторые вещи были мной опущены, так они не предоставляют большой ценности(в частности, речь идёт об методе _list_attributes. Вся информация для статьи была почерпана из исходников Ren'Py, которые находятся в открытом доступе. Стоит помнить, что _duplicate и _choose_attributes не являются документированными фичами, что наводит мысль о том, что эти вещи вполне могут измениться в будущих релизах. Ссылка на репозиторий с кодом. Ren'Py | СНГ — русскоязычное сообщество Ren'Py в Discord.