В процессе работы над арканоидом, стало становиться понятным, чего именно не хватает Marmalade Framework-у, разработанному ранее, в процессе создания значительно более простой игры. Выяснилось, что не хватает ему практически всего (именно поэтому я не смог его как либо использовать, работая над арканоидом).
Теперь, когда я готов начать обобщать полученный опыт, я приступаю к работе над следующей версией Framework-а. Как и прежде, цель этой разработки — облегчение выполнения рутинных операций при разработке 2D-игр с использованием инструментальной среды Marmalade, но теперь, основной упор будет сделан на декларативность. В идеале, в коде не должно оставаться никакой конкретики в отношении используемых ресурсов, позиционирования объектов на экране, локализации и т.п.
В этой статье не будет кода (именно поэтому я не помещаю ее в хабы разработки под iOS и Android). Сегодня, по совету crmMaster, я займусь проектированием.
Сердцем нашего проекта будет JSON-описание. Вообще говоря, с тем парсером, который мы интегрировали в проект, мы могли-бы использовать YAML, но JSON мне нравится больше.
Итак, первое, что будет делать приложение после старта — это загрузка описания из файла main.json. Ниже мы рассмотрим, как будет выглядеть содержимое этого файла.
{ load: state.json,
...
}
Начнем с того, что не может быть и речи о том чтобы хранить все описание в одном файле. Различные описания могут использоваться в различных целях и выглядеть совершенно разнородно. Понятно, например, что описание взаимодействия сцен будет иметь мало общего с конфигурацией собственно игровой сцены. Вполне логично разнести эти описания в разные файлы.
Используя директиву load мы сможем добавить файл в очередь загрузок. Я специально не стал использовать слово include, поскольку оно подразумевает включение некоего ресурса непосредственно в той точке где находится по описанию. В нашем случае, файлы указанные директивой load, в корневой секции описания, будут загружены после завершения загрузки main.json. Также можно будет использовать динамическую загрузку описаний, о которой я расскажу позже.
Файл state.json, упомянутый нами в примере, обеспечит нашему приложению персистентность. Он будет содержать последовательность присвоений переменным, содержащим основные настройки игры (таким как собственно пользовательские настройки, достигнутый в процессе игры уровень и т.п.). Откуда возьмутся эти переменные, я также расскажу ниже. Сейчас важно понимать, что отсутствие этого файла при загрузке — это вполне штатная ситуация, не ведущая к ошибке выполнения. Такое возможно, например, при первом запуске приложения или после удаления игровых настроек в Android.
templates: { some_template: { ...
}
}
...
template: some_template,
...
Эта конструкция позволит нам эффективно бороться с копипастом. Используя ее мы сможем определить повторяющиеся именованные шаблоны, чтобы впоследствии использовать их в любом месте описания. Единственным условием такого использования является то, шаблон может быть использован только если он определен ранее в описании (парсер будет однопроходным).
Шаблоны можно будет определять не только в корневом, но и во вложенных разделах. Для чего могут понадобиться подобные локальные шаблоны — отдельная тема. Я собираюсь затронуть ее когда мы перейдем к реализации загрузчика.
display: { width: 720,
orientation: portrait
}
В этом разделе мы будем определять экранные настройки. Очень важным параметром здесь является значение width. Все последующие описания размеров и позиций будут заданы таким образом как-будто они используются на экране заданного размера. При отображении на конкретном устройстве, с другим размером экрана, они будут пересчитаны пропорционально.
Можно задать значения width и height, но, как правило будет задаваться только одна из них. В этом случае, все размеры и позиции будут пересчитываться так, чтобы пиксел оставался квадратным при любом соотношении сторон. Также, договоримся, что отрицательные размеры позиций будут означать координаты от правой и нижней сторон экрана, в сторону уменьшения.
Ширина экрана может быть переопределена в любой момент (например в другом файле описания), после чего, все позиции и размеры будут перерассчитываться относительно новой величины.
devices: [ { default: Y,
width: 720,
height: 1280,
path: 1280
}
]
В этом разделе мы описываем соотношения сторон, для которых имеются ресурсы не требующие масштабирования. Строка path будет автоматически добавятся в начало имени любого графического ресурса. Задачей Framework-а будет выбор из имеющегося набора ресурсов того, который обеспечит наименьшие искажения при отображении.
langs: [ { id: 16,
path: ru,
default: Y
}
]
Аналогичным образом описываются доступные локализации. Значение id должно соответствовать используемому в Maramalade. Строка path добавляется в имя любого локализованного ресурса (графического или звукового). Framework должен будет определить локализацию, исходя из текущих настроек устройства и приложения.
resources: { logo: { localized: Y,
file: backgrounds/logo.png
},
ball: { x: 0,
y: 0,
width: 10,
height: 10,
left_margin: 3,
right_margin: 3,
top_margin: 3,
bottom_margin: 3,
file: game/atlas.png
}
}
Таким образом мы будем описывать графические ресурсы. Обязательная настройка file определяет имя файла, в котором содержится ресурс. Настройки x, y используется при работе с текстурными атласами. Они, также как настройки width и height должны соответствовать размеру экрана, определенному в разделе display.
Настройки xxx_margin будут использоваться для изображений с прозрачными фрагментами на краях. Например, стреляющая платформа в арканоиде отрисовывается с двумя пушками по бокам. Фрагмент между пушками — прозрачный и в него может заходить шарик. Используя top_margin, мы можем определить его высоту.
В следующей части я планирую рассказать о разделах scopes и regions, определяющих блоки, из которых будет строиться приложение.
Исходные тексты Framework-а будут выкладываться на GitHub, по мере разработки.
Автор: GlukKazan