
Немного об эмбеде ассетов
Хочу рассказать о своем подходе к эмбеду картинок и звуков.
Не секрет, что большинство девелоперов используют векторную графику и, соответственно, swc-файлы, экспортированные из Flash IDE. Там есть свои подходы, о которых я рассказывать не буду. Но, если вы используете растровую графику — информация, изложенная ниже, может помочь ускорить рутинные операции.Если, конечно, эти бояны еще кто-то не знает. Про swc тоже немного напишу.
Итак, сформулируем проблему.
Картинок может быть много, например, несколько сотен.
Эти картинки мы хотим использовать в игре в качестве спрайтов.
Интернеты подсказывают нам следующее решение:
Еще один недостаток такого прямолинейного подхода — невозможность получить картинку по имени файла.
Например, у нас есть картинки с именами «wall_0.png», «wall_1.png», ..., «wall_18.png» и так далее. Типично хочется пробегать по таким картинкам в цикле и менять только циферку в имени, чего обычный подход не позволяет.
Лично я нашел решение в кодогенерации.
Я генерирую большой файл с вышеприведенными строчками и дополнительно заношу ссылку на каждый экземпляр в словарь с ключом равным имени файла. После этого дополнительно оборачиваю парой функций и все проблемы решены.
Раньше генерировал с помощью локального php, но скилл растет, поэтому переписал недавно на bat.
Пример батника:
Для реального использования нужно поменять имя пэкеджа, класса и относительный путь.
Что, собственно, происходит:
Остальное просто вывод статичного текста, чтобы руками не копипастить.
В итоге генерируется вот такой файл:
Выглядит примерно так:
Но я не добавляю, мне не надо. Но добавить можно, не спорю.
Поэтому про звуки покороче, потому что подход похожий на картинки.
Как обычно происходит работа с музыкантом, по крайне мере у меня:
1. Пишем список звуков.
2. Создаем каталог с пустышками.
3. Пишем динамическую загружалку из файлов.
4. Вставляем вызовы проигрывания звуков.
5. Отправляем музыканту.
6. Он заменяет пустышки на нормальные звуки и отправляет обратно.
7. Заменяем динамическую загружалку на эмбеды.
Все это тоже можно ускорить.
Итак, создали список звуков в текстовом файле:
Кидаем в каталог маленький, но рабочий(!) .mp3-файл. Это будет наша исходная «пустышка».
Теперь для каждого звука надо создать свой файл.
Используем следующий .bat:
Получили заполненный каталог.
Пустышку можно удалить.
Теперь для каждого звука надо нагенерить динамическую загрузку. Подразумевается, что файлы валяются прямо рядом с swf, поэтому никакие пути не нужны.
Генерируем:
Выглядит так:
Дальше можно использовать такой же подход как с картинками и проигрывать звуки через строковые константы, но это опасно, потому что, в отличии от картинок, звуки добавляются обычно в конце игры в самые неожиданные места.
Ошибиться в названии звука легко, а определить пропущенный звук третьего шага двадцать восьмого врага на тринадцатом уровне на максимальной сложности довольно трудно.
Мое решение — перенести проверку на компилятор. Заодно он и подсказывать будет, чтобы не запоминать названия.
Для каждого звука я генерирую маленькую однострочную функцию следующего вида:
Батник (с ним мне пришлось сегодня повозиться, именно он стал причиной написания поста):
Теперь, из любого места можно проиграть звук просто вызвав функцию:
Когда музыкант присылает звуки обратно и настает время делать финальную сборку, добавляем эмбеды:
Батники уже постить не буду, надоело.
SWC.
Чтобы уже добить пост до конца, в 2 словах расскажу про эмбед из swc.
Собственно, при использовании swc самая большая проблема остается открытой — по имени класса нельзя получить экземпляр или ссылку на класс.
Все это решается примерно так же, как с файлами.
Просто запихиваем в словарь ссылки с помощью кодегена.
Расскажу на словах (кода не будет, я так делал всего пару раз и технология не отработана).
Переименовываем .swc в .zip и вытаскиваем catalog.xml
Он выглядит примерно так:
Строчки:
Собственно вот и все.
Вытаскиваем xml, пробегаем по нодам, для каждого нужного нода — генерируем код.
Не секрет, что большинство девелоперов используют векторную графику и, соответственно, swc-файлы, экспортированные из Flash IDE. Там есть свои подходы, о которых я рассказывать не буду. Но, если вы используете растровую графику — информация, изложенная ниже, может помочь ускорить рутинные операции.
Итак, сформулируем проблему.
Картинки.
У нас есть каталог (что-то вроде assets/gfx) с картинками.Картинок может быть много, например, несколько сотен.
Эти картинки мы хотим использовать в игре в качестве спрайтов.
Интернеты подсказывают нам следующее решение:
[Embed(source='../assets/gfx/pic.png')]
private static var pic_class:Class;
private static var pic:Bitmap = new pic();
Ну хорошо, 5-10 картинок можно наколотить руками, но вот когда картинок много — это превращается в pain in the ass.Еще один недостаток такого прямолинейного подхода — невозможность получить картинку по имени файла.
Например, у нас есть картинки с именами «wall_0.png», «wall_1.png», ..., «wall_18.png» и так далее. Типично хочется пробегать по таким картинкам в цикле и менять только циферку в имени, чего обычный подход не позволяет.
Лично я нашел решение в кодогенерации.
Я генерирую большой файл с вышеприведенными строчками и дополнительно заношу ссылку на каждый экземпляр в словарь с ключом равным имени файла. После этого дополнительно оборачиваю парой функций и все проблемы решены.
Раньше генерировал с помощью локального php, но скилл растет, поэтому переписал недавно на bat.
Пример батника:
@echo off
echo package ryz.globals
echo {
echo import flash.display.Bitmap;
echo import flash.display.BitmapData;
echo import flash.utils.Dictionary;
echo public class GUIEmbeds
echo {
@FOR %%i IN (*.png *.jpg) DO ^
echo [Embed(source='../../../assets/gui/%%i')] && ^
echo private static var k_%%~ni:Class;
echo private static var wasInit:Boolean = false;
echo private static var bmps:Dictionary = new Dictionary();
echo public static function Init():void
echo {
echo if (wasInit)
echo {
echo return;
echo }
@FOR %%i IN (*.png *.jpg) DO echo bmps['%%~ni'] = new k_%%~ni().bitmapData;
echo wasInit = true;
echo }
echo public static function Get(s:String):BitmapData
echo {
echo if (!wasInit)
echo {
echo Init();
echo }
echo return (bmps[s] as BitmapData);
echo }
echo public static function Bmp(s:String):Bitmap
echo {
echo return (new Bitmap(Get(s)));
echo }
echo }
echo }
Запускать нужно в каталоге с картинками.Для реального использования нужно поменять имя пэкеджа, класса и относительный путь.
Что, собственно, происходит:
@FOR %%i IN (*.png *.jpg) DO ^
echo [Embed(source='../../../assets/gui/%%i')] && ^
echo private static var k_%%~ni:Class;
Бежим по всем png и jpg, для каждого файла пишем имя файла с расширением ("%%i") и без расширения ("%%~ni"). Во втором случае дополнительно добавляем префикс «k_» (просто для красоты).@FOR %%i IN (*.png *.jpg) DO echo bmps['%%~ni'] = new k_%%~ni().bitmapData;
Вот тут опять бежим по всем файлам и заносим в словарь ссылку на битмапдату.Остальное просто вывод статичного текста, чтобы руками не копипастить.
В итоге генерируется вот такой файл:
package ryz.globals
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.utils.Dictionary;
public class GUIEmbeds
{
[Embed(source='../../../assets/gui/3stars.png')]
private static var k_3stars:Class;
[Embed(source='../../../assets/gui/blue_block.png')]
private static var k_blue_block:Class;
//.............. тут много много эмбедов.........
[Embed(source='../../../assets/gui/unlock_over.png')]
private static var k_unlock_over:Class;
private static var wasInit:Boolean = false;
private static var bmps:Dictionary = new Dictionary();
public static function Init():void
{
if (wasInit)
{
return;
}
bmps['3stars'] = new k_3stars().bitmapData;
bmps['blue_block'] = new k_blue_block().bitmapData;
//.............. тут много много записей в словарь.........
bmps['unlock_over'] = new k_unlock_over().bitmapData;
wasInit = true;
}
public static function Get(s:String):BitmapData
{
if (!wasInit)
{
Init();
}
return (bmps[s] as BitmapData);
}
public static function Bmp(s:String):Bitmap
{
return (new Bitmap(Get(s)));
}
}
}
Ну, здесь все понятно. Эмбедятся картинки, при первом обращении происходит инициализация. Можно просить битмапдату или новую битмапу.Выглядит примерно так:
var tt:BitmapData = GUIEmbeds.Get('ig_top_lside');
Можно добавить какие-то константы, функции, и все такое прочее.Но я не добавляю, мне не надо. Но добавить можно, не спорю.
Звуки.
Я не буду тут расписывать полностью работоспособный менеджер звуков, потому что это тема для отдельной большой статьи.Поэтому про звуки покороче, потому что подход похожий на картинки.
Как обычно происходит работа с музыкантом, по крайне мере у меня:
1. Пишем список звуков.
2. Создаем каталог с пустышками.
3. Пишем динамическую загружалку из файлов.
4. Вставляем вызовы проигрывания звуков.
5. Отправляем музыканту.
6. Он заменяет пустышки на нормальные звуки и отправляет обратно.
7. Заменяем динамическую загружалку на эмбеды.
Все это тоже можно ускорить.
Итак, создали список звуков в текстовом файле:
01_plasma_shoot.mp3
02_plasma_blast.mp3
...
54_land_blast2.mp3
Я обычно нумерую звуки, потому что так проще.Кидаем в каталог маленький, но рабочий(!) .mp3-файл. Это будет наша исходная «пустышка».
Теперь для каждого звука надо создать свой файл.
Используем следующий .bat:
FOR /F %%i IN (muz_list.txt) DO copy empty.mp3 %%i
Для каждой строчки в файле «muz_list.txt» копируем «empty.mp3».Получили заполненный каталог.
Пустышку можно удалить.
Теперь для каждого звука надо нагенерить динамическую загрузку. Подразумевается, что файлы валяются прямо рядом с swf, поэтому никакие пути не нужны.
Генерируем:
FOR %%i IN (*.mp3) DO echo songs['%%~ni']= new Sound(new URLRequest('%%i'));
Вот тут для каждого файла создается запись в словаре.Выглядит так:
songs['01_plasma_shoot']= new Sound(new URLRequest('01_plasma_shoot.mp3'));
songs['02_plasma_blast']= new Sound(new URLRequest('02_plasma_blast.mp3'));
Дальше можно использовать такой же подход как с картинками и проигрывать звуки через строковые константы, но это опасно, потому что, в отличии от картинок, звуки добавляются обычно в конце игры в самые неожиданные места.
Ошибиться в названии звука легко, а определить пропущенный звук третьего шага двадцать восьмого врага на тринадцатом уровне на максимальной сложности довольно трудно.
Мое решение — перенести проверку на компилятор. Заодно он и подсказывать будет, чтобы не запоминать названия.
Для каждого звука я генерирую маленькую однострочную функцию следующего вида:
public static function s_plasma_shoot():void { Play('01_plasma_shoot'); }
public static function s_plasma_blast():void { Play('02_plasma_blast'); }
Где «s_» — префикс, «Play()» — функция, непосредственно проигрывающая звук.Батник (с ним мне пришлось сегодня повозиться, именно он стал причиной написания поста):
setlocal EnableDelayedExpansion
FOR %%i IN (*.mp3) DO set v=%%~ni && set vv=s_!v:~3,-1!():void && echo public static function !vv! { Play('%%~ni'); }
endlocal
Как я уже писал, у меня все файлы звуков пронумерованные, а в функциях я хочу использовать имена без номеров, поэтому батник немного отличается от предыдущих.setlocal EnableDelayedExpansion
Без этой команды изменение переменных в цикле не работает.set v=%%~ni
Копируем значение во временную переменную, потому что сабстринг применять к переменной цикла нельзя.set vv=s_!v:~3,-1!():void
Вот эта команда делает сабстринг в новую переменнуи одновременно добавляет префикс «s_» и постфикс "()void". Постфикс нужен тут, потому что в echo эта переменная выводится с лишним пробелом в конце. Баг, который я так и не смог победить. В данном конкретном случае это неважно, просто я код скопипастил из другого места :)Теперь, из любого места можно проиграть звук просто вызвав функцию:
SoundPlayer.s_plasma_blast();
На мой взгляд очень удобно.Когда музыкант присылает звуки обратно и настает время делать финальную сборку, добавляем эмбеды:
[Embed(source='../assets/mfx96/01_click.mp3')]
private static var k_click:Class;
private static var s_click:Sound = new k_click();
и заменяем URLRequest'ы на присваивания:songs['click'] = s_click;
Батники уже постить не буду, надоело.
SWC.
Чтобы уже добить пост до конца, в 2 словах расскажу про эмбед из swc.
Собственно, при использовании swc самая большая проблема остается открытой — по имени класса нельзя получить экземпляр или ссылку на класс.
Все это решается примерно так же, как с файлами.
Просто запихиваем в словарь ссылки с помощью кодегена.
Расскажу на словах (кода не будет, я так делал всего пару раз и технология не отработана).
Переименовываем .swc в .zip и вытаскиваем catalog.xml
Он выглядит примерно так:
<?xml version="1.0" encoding ="utf-8"?>
<swc xmlns="http://www.adobe.com/flash/swccatalog/9">
<versions>
<swc version="1.2" />
<flash version="11.5" build="d349" platform="WIN" />
</versions>
<features>
<feature-script-deps />
<feature-files />
</features>
<libraries>
<library path="library.swf">
<script name="Dron" mod="1333043131062" >
<def id="Dron" />
<dep id="AS3" type="n" />
<dep id="flash.display:MovieClip" type="i" />
</script>
<script name="DronBulletBlast" mod="1333043131062" >
<def id="DronBulletBlast" />
<dep id="AS3" type="n" />
<dep id="flash.display:MovieClip" type="i" />
</script>
</library>
</libraries>
<files>
</files>
</swc>
Строчки:
<def id="Dron" />
<def id="DronBulletBlast" />
это и есть имена классов.Собственно вот и все.
Вытаскиваем xml, пробегаем по нодам, для каждого нужного нода — генерируем код.
- +35
- ryzed
Комментарии (29)
Но, в любом случае, это решение пригодится на 100%.
А по сути — здорово! Спасибо.
Пойду еще почитаю про батники.
Текстурки картинками — 73.
Остальное в зипах и загружается другим способом.
Есть пара моментов, которые я хотел бы дополнить:
1. Удобно сделать не функции Get(s:String):BitmapData, а статические переменные в классе:
Т.к. в итоге автодополнение подсказывает имена ассетов при использовании.
2. Удобно иметь возможность сгенерить два варианта класса: первый выдает за-embed-енные картинки, а второй динамически загружает картинки при старте флешки, так художник может сам без помощи программиста пробовать новые картинки, да и даже самому удобнее графику тестировать.
3. Удобно по картинкам в одной папке с названиями 01.png, 02.png и т.д. делать объявление массива, а не кучи переменных:
4. У меня еще этот скрипт делает из png два jpeg с цветом и альфаканалом для экономии места.
Код кривой-кривой, но работает.
Под местом в данном контексте все же как-то привычнее понимать область памяти, которая в лучшем случае от этого трюка с «PNG -> 2 x JPG» никак не уменьшится.
Время на сборку всего этого в памяти уходит конечно, но не очень много: меньше секунды в моем случае, и обычно все равно на старте куча всякой инициализации и приходится заставлять игрока подождать, на этом фоне вообще не заметно.
Но если например компилировать флешку для мобилок, то можно переделать скрипт, а исходники игры в части, где используются ассеты не менять…
Чтобы класс не нужно было включать в заголовках импорта, можно импортировать библиотеку «особым» образом:
Ну и на самом деле я привык нормально называть объекты, и использовать нормальное обращение
А за батник — спасибо. А то все при них забывают, всякие питоны и прочие скрипты используют, а батники то еще ого-го и устанавливать ничего дополнительного не надо.
Можно через getDefinitionByName, но как-то мне не понравился такой подход.
Даже не помню почему.
Дополню пост ссылкой: Как перенаправить стандартный вывод в файл?
ээээм… что я делаю не так?
отлично вижу имена всех названных классов, через подключенную swc, могу оверроудить да вообще что угодно могу, это же мовиклипы
Например, тридцать седьмой звук.