В процессе допиливания игры не смог удержаться от рефакторинга, хотя всё уже было налажено. Некоторые моменты меня раздражали чисто эстетически, и потому решил переделать.
Мудрые принципы Flash-приложений
В эпоху расцвета Flash появлялись весьма увесистые приложения, которые долго загружались.
Flash начинает работать сразу же, как только загружен первый кадр приложения, а всё остальное подгружается по мере готовности.
Поэтому толковые разработчики в первом кадре делали всё по минимуму, чтобы загрузилось только самое необходимое и уже начало работать. А чтобы пользователь не скучал, на экран выводился индикатор прогресса. Но для этого нужна графика, которая тоже должна сначала подгрузиться, либо же рисовать процедурно средствами самого Flash. Самое простое – растущая полоска прогресса.
Когда я сделал воду в игре, она оказалась отличным кандидатом на индикатор прогресса: во-первых, сразу задаёт сеттинг игры, во-вторых, выглядит нескучно, в-третьих, рисуется процедурно и не требует загрузки ресурсов.
Я сделал, чтобы уровень воды поднимался снизу вверх. Честно говоря, ресурсы подгружались так быстро, что пришлось прогресс искусственно замедлить :)
Архитектура, которую заслужил
Вода на экране, будучи индикатором прогресса, бесшовно становилась игрой. Никакой смены экранов. Вот вода заполнила экран, и тут же в этой воде можно начинать игру. Это решение до сих пор меня радует.
Так как вода была центральным элементом игры начиная прямо с загрузки, я решил внедрить её на самый верх программной архитектуры как жёстко прошитый компонент. Он активировался самым первым. Потом игра могла загружать или выгружать различные модули, но вода оставалась всегда.
Я ожидал проблем от такого решения, но был к этому готов и совершенно целенаправленно выбрал этот вариант, просто потому что так хотелось.
Войти в одну воду дважды
Одна из проблем, которую пришлось решать, это помещение объектов в воду. Дело в том, что вода в сцене рисуется самой первой, а потом добавляются другие объекты. Но вода состоит из двух слоёв. Один для фона и другой, полупрозрачный, для переднего плана. Чтобы поместить объект в воду, надо вставить его между этими двумя слоями.
Но так как вода была отдельным компонентом, который свои оба слоя сам рисовал перед всеми остальными объектами, пришлось настраивать довольно сложное взаимодействие со сценой.
В результате компонент воды находился на сцене и прикидывался обычным графическим объектом, но отрисовывал он сам себя. А чтобы поместить объект в воду, нужно было помещать его не на сцену, а непосредственно в компонент воды.
Это порождало пару жёстких зависимостей, но ими можно было пренебречь, я делал всё абсолютно осознанно, и они не разрастались. За исключением воды, остальное работало штатно. На тот момент мне так нравилось. Сейчас я другой человек, а не тот, которым был. Теперь мне нравится по-другому. Поэтому в одну воду нельзя войти дважды.
Рефакторинг
Первым делом я решил отвязать графическое представление воды от её компонента. Компонент должен просто рассчитывать параметры волн, но не рисовать их.
Попутно переделал рендеринг сцены. Сцена была устроена так:
Она имела единый метод отрисовки, который получал от всех объектов на сцене их картинки через метод getGraphics() и отрисовывал эти картинки на экране, включая прозрачность, масштабирование и поворот. Как помним, сцена не умела рисовать воду, поэтому компонент воды размещал на сцене два своих слоя по отдельности как обычные DisplayObject, но когда у них запрашивался метод getGraphics(), они пользовались этим, чтобы нарисовать самих себя, а вместо картинки возвращали null, чтобы сцена ничего не рисовала.
В принципе ни о чём не жалею. Но теперь сделал так:
Появился компонент Renderer, который занимается своим прямым делом – рисованием. Метод draw() есть теперь в каждом объекте сцены, но объект не рисует сам себя. Вместо этого в метод передаётся ссылка на рендерер, и объект может попросить у рендерера "нарисуй меня", вызвав метод renderer.drawImage(), к примеру. Рендерер всегда знает, как рисовать то, о чём его попросят, в том числе и воду.
Выгоды очевидны. Вся работа с графикой теперь находится ТОЛЬКО в рендерере, который всегда можно заменить на другой. Например, на OpenGL. Нет ограничений на то, какую именно графику может содержать объект. Хоть картинку, хоть процедурную. Всё, что может быть нарисовано – нарисует рендерер, надо только его попросить.
Теперь водный компонент сократился до набора параметров волн, а его два слоя графики стали обычными DisplayObject наравне с остальными на сцене.
Кроме того, есть объекты, которые никогда не бывают полупрозрачными, отмасштабированными или повёрнутыми. На деле таких объектов большинство и их можно нарисовать по упрощённой схеме. Но метод рисования в сцене был общий для всех и поэтому должен был учитывать и масштабирование и поворот независимо от того, требовалось это или нет.
Сейчас каждый объект может рисовать себя так, как ему оптимальнее, запрашивая соответствующий метод у рендерера.
Вся подборка: