Preloader своими руками

В статье речь пойдёт о создании preloader-а для flash игры.

В данном контексте имеется ввиду прелоадер, который отображает процесс загрузки самого flash-приложения (т.к. есть ещё одно понятие прелоадера — при загрузке конкретного внутри игрового контента).

Рассмотрим 2 вида прелоадеров и реализуем их во FlashDevelop.

Что такое preloader и как он работает?

Изначально логика загрузки flash приложения (флешки) сделана следующим образом: сначала грузится первый кадр, а затем грузится всё остальное и при этом первый кадр может получать информацию об остальной загрузке флешки.

Таким образом мы можем отображать игроку процесс загрузки. И логично можно догадаться, что чем первый кадр меньше(легче), тем лучше, т.к. если первый кадр будет размером почти со всю флешку(это возможно, если в прелоадере использовать весь контент, который используется во всей игре), то игрок будет видеть чёрный экран пока вся флешка не загрузится.

Когда мы создаём во FlashDevelop (далее FD) новый проект as3 с прелоадером, то конструктор создаёт 2 класса Main и Preloader. При этом точка входа в программу — это класс Main, при этом отсюда уже начинается наша игра и казалосьбы флешка сразу тянет за собой весь контент и ни о каком маленьком первом кадре речи быть не может и вообще как ко всему этому причастен класс Preloader… Но перед классом Main мы замечаем строчку [Frame(factoryClass=»Preloader»)].

Это метатег, который говорит компилятору, что класс Preloader должен быть загружен первым, а т.к. он наследован от MovieClip, а MC состоит из кадров, то вот мы и нашли тот самый первый кадр, который загрузится впереди всего. При этом весь код, который мы напишем в нём, будет функционировать и мы сможем узнавать информацию о процессе загрузки всего приложения.

Теперь запилим парочку разных прелоадеров для закрепления материала.

Прелоадер состоящий из кадров

Этот вид прелоадера представляет из себя заранее заготовленный MovieClip. Где каждый шаг прогресса прелоадера — это определённый кадр заготовленного клипа. Например, делаем клип с 11-ю кадрами, где каждый кадр отображает каждые 10% загрузки: 0%, 10% и т.д. до 100%. В таком прелоадере при 5% реальной загрузки надо будет показывать 0%, при 10% — 10%, при 29% — 20% и т.д.

Создаём новый основной проект «AS3 Project with preloader» в FD. Создаём новый проект во Flash CS#, сохраняем его в папку lib основного проекта под именем Preloader.fla, настраиваем публикацию для компиляции swc библиотеки и подключаем её в FD (об этом я писал в предыдущей статье).

Во Flash создаём MovieClip с 11 кадрами, отображающий прогресс загрузки и кнопку «Play» на первом кадре, которую в коде мы будет выдёргивать из этого клипа и ложить в список отображения класса прелоадера.

Затем пишем код. Я создал специальный класс для прелоадера StepPreloader и использую его в Preloader.as.

Код класса StepPreloader:

    package
    {
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.text.TextField;
     
    /**
    * @author Monax
    */
    public class StepPreloader extends Sprite
    {
    private var _mc: MovieClip; // основной клип с кадрами отображения процесса
    private var _btnPlay: SimpleButton; // кнопка play
    private var _bytesTotal: Number = 0; // всего байт
    private var _bytesLoaded: Number = 0; // байт загружено
    private var _loadPercent: Number; // процент загрузки
    private var _tfLoadPercent: TextField; // тексты отображающие процесс загрузки
    private var _tfBytes: TextField;
    // обработчик события нажатия кнопки Play
    public var onBtnPlayClick: Function;
     
    public function StepPreloader(aMC: MovieClip, aBtnPlay: SimpleButton)
    {
    super();
    _mc = aMC;
    _mc.gotoAndStop(1);
    _btnPlay = aBtnPlay;
    _btnPlay.addEventListener(MouseEvent.CLICK, onBtnPlayClickHandler);
    addChild(_mc);
    addChild(_btnPlay);
    }
     
    // Обновление прелоадера
    private function refresh():void
    {
    // высчитываем процент загрузки
    _loadPercent = 100 * _bytesLoaded / _bytesTotal;
    if (_loadPercent < 0) _loadPercent = 0;
    if (_loadPercent > 100) _loadPercent = 100;
    // переходим на кадр соотвествующий текущему проценту загрузки
    _mc.gotoAndStop(Math.floor(_mc.totalFrames / 100 * _loadPercent) + 1);
    // отображаем текст процента, если поле доступно
    if (_tfLoadPercent) _tfLoadPercent.text = "Loading: " + String(int(_loadPercent)) + "%";
    // отображаем байты
    if (_tfBytes) _tfBytes.text = "(" + String(int(_bytesLoaded / 1000)) + "/" + String(int(_bytesTotal / 1000)) + " KBytes)";
    }
     
     
    private function onBtnPlayClickHandler(e:MouseEvent):void
    {
    if (onBtnPlayClick != null) onBtnPlayClick.apply();
    }
     
     
    public function get btnPlayVisible():Boolean { return _btnPlay.visible; }
    public function set btnPlayVisible(value:Boolean):void { _btnPlay.visible = value; }
     
    public function get bytesTotal():Number { return _bytesTotal; }
    public function set bytesTotal(value:Number):void {
    _bytesTotal = value;
    refresh();
    }
     
    public function get bytesLoaded():Number { return _bytesLoaded; }
    public function set bytesLoaded(value:Number):void {
    _bytesLoaded = value;
    refresh();
    }
     
     
    public function setTFLoadPercent(aTF: TextField):void
    {
    _tfLoadPercent = aTF;
    }
     
    public function setTFBytes(aTF: TextField):void
    {
    _tfBytes = aTF;
    }
     
    public function free():void
    {
    onBtnPlayClick = null;
    removeChild(_mc);
    removeChild(_btnPlay);
    _mc = null;    
    _btnPlay = null;
    }
     
    }
     
    }

Код класса Preloader:

package
    {
    import flash.display.DisplayObject;
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.MouseEvent;
    import flash.events.ProgressEvent;
    import flash.text.TextField;
    import flash.utils.getDefinitionByName;
     
    /**
    * @author Monax
    */
    public class Preloader extends MovieClip
    {
    private var _preloader: StepPreloader;
     
    public function Preloader()
    {
    if (stage) {
    stage.scaleMode = StageScaleMode.NO_SCALE;
    stage.align = StageAlign.TOP_LEFT;
    }
    addEventListener(Event.ENTER_FRAME, checkFrame);
    loaderInfo.addEventListener(ProgressEvent.PROGRESS, progress);
    loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioError);
     
    // создаём основной клип
    var mcPreloader: Preloader_mc = new Preloader_mc();
    // берём с основного клипа кнопку по имени
    var btnPlay: SimpleButton = mcPreloader.getChildByName("btnPlay") as SimpleButton;
    // создаём класс пошагового прелоадера
    _preloader = new StepPreloader(mcPreloader, btnPlay);
    _preloader.setTFLoadPercent(mcPreloader.getChildByName("tfPerc") as TextField);
    _preloader.setTFBytes(mcPreloader.getChildByName("tfBytes") as TextField);
    _preloader.x = 320;
    _preloader.y = 240;
    _preloader.btnPlayVisible = false;
    // прописываем обработчик нажатия кнопки play
    _preloader.onBtnPlayClick = playClick;
    //
    addChild(_preloader);
    }
     
    private function ioError(e:IOErrorEvent):void
    {
    trace(e.text);
    }
     
    private function progress(e:ProgressEvent):void
    {
    // TODO update loader
    if (_preloader.bytesTotal == 0) _preloader.bytesTotal = e.bytesTotal;
    _preloader.bytesLoaded = e.bytesLoaded;
    }
     
    private function checkFrame(e:Event):void
    {
    if (currentFrame == totalFrames) {
    stop();
    loadingFinished();
    }
    }
     
    private function loadingFinished():void
    {
    removeEventListener(Event.ENTER_FRAME, checkFrame);
    loaderInfo.removeEventListener(ProgressEvent.PROGRESS, progress);
    loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, ioError);
    // показываем кнопку
    _preloader.btnPlayVisible = true;
    }
     
    private function playClick():void {
    _preloader.free();
    removeChild(_preloader);
    _preloader = null;
    startup();
    }
     
    private function startup():void
    {
    var mainClass:Class = getDefinitionByName("Main") as Class;
    addChild(new mainClass() as DisplayObject);
    }
     
    }
     
    }

А в коде файла Main.as я реализовал имитацию данного прелоадера (для наглядности) с повторением после нажатия кнопки Play.

Важное замечание! Все остальные ресурсы для игры должны содержаться в отдельной swc библиотеки и не использоваться в прелоадере, иначе прелоадер потянет за собой всю флешку и смысла от него не будет.

В результате получился вот такой прелоадер:

Скачать исходники можно прям отсюда: MyPreloader.rar.

Плавный прелоадер с маской

Данный прелоадер отображает процесс загрузки каким-либо плавным действием-анимацией. В моём случае плавная полоска загрузки.

Вот так это выглядит (нажимайте кнопку Play для повтора):

Способ реализации

Для такого прелоадера нам необходимо сделать полоску прогресса отдельным клипом.

Затем создать фон, текст и кнопку (всё кроме полоски) в новом клипе и так же разместить в нём клип полоски.

prev_4

По списку библиотеки так же можно видеть, что я встроил шрифт и кнопка лежит отдельным символом в библиотеке.

Как встраивать шрифты в библиотеку? Когда выбрали для текстового поля какой-нибудь шрифт из системы, нажимаем кнопку embed рядом с выбором, как показано на рисунке:

Встраивание шрифта во flash
Встраивание шрифта во flash

Затем, в открывшемся окне, вводим имя которое будет отображаться в библиотеке и отмечаем галочкой символы, которые нам потребуются, например All (все символы):

font_embeding2

Вот и всё, после нажатия Ok шрифт появится в библиотеке.

Итак продолжаем. Далее назначаем имена для всех элементов (в клипе mcPreloader), которые нам понадобиться распознать программно: клип полоски, текст, кнопка.

Для отображения всего прелоадера я создал отдельный класс в котором создаётся экземпляр основного клипа прелоадера и распознаются на нём все нужные элементы. Самая основная часть — маска полоски прогресса, создаётся программно. В комментариях кода всё должно быть понятно.

Код класса SmoothPreloader:

package
{
import flash.display.Graphics;
import flash.display.MovieClip;
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;

/**
* @author Monax
*/
public class SmoothPreloader extends Sprite
{
private var _preFon: Preloader_mc; // основной клип
private var _preBar: MovieClip; // клип полоски прогресса
private var _barMask: Sprite; // маска для полоски
private var _btnPlay: SimpleButton;
private var _tfPercent: TextField;
//
private var _bytesTotal: int = 0;
private var _bytesLoaded: int = 0;
// событие нажатия кнопки Play
public var onBtnPlayClick: Function;

public function SmoothPreloader()
{
super();
// создаём основной клип прелоадера
_preFon = new Preloader_mc();
// находим на полоску прогресса
_preBar = _preFon.getChildByName("bar") as MovieClip;
// создаём маску для полоски
_barMask = new Sprite();
_preFon.addChild(_barMask);
// размещаем маску в тех же координатах что и полоска
_barMask.x = _preBar.x;
_barMask.y = _preBar.y;
// рисуем на маске прямоугольник высотой и шириной как у полоски прогресса
var gr: Graphics = _barMask.graphics;
gr.beginFill(0);
gr.drawRect(0, 0, _preBar.width, _preBar.height);
gr.endFill();
// и выставляем масштаб по ширине в 0, в прогрессе мы будем увеличивать масштаб до 1(100% загрузки - масштаб = 1)
_barMask.scaleX = 0;
// присваиваем маску полоске
_preBar.mask = _barMask;

// распознаём кнопку плей и создаём ей обработчик клика
_btnPlay = _preFon.getChildByName("btnPlay") as SimpleButton;
_btnPlay.addEventListener(MouseEvent.CLICK, function(e: MouseEvent):void {
if (onBtnPlayClick != null) onBtnPlayClick.apply();
});
_btnPlay.visible = false;

// распознаём текст отображения процентов
_tfPercent = _preFon.getChildByName("tfLoading") as TextField;

bytesTotal = 0;
bytesLoaded = 0;

addChild(_preFon);
}

public function get bytesTotal():int { return _bytesTotal; }
public function set bytesTotal(value:int):void { _bytesTotal = value; }

public function get bytesLoaded():int { return _bytesLoaded; }
public function set bytesLoaded(value:int):void {
_bytesLoaded = value;
if (_bytesTotal != 0) {
_barMask.scaleX = _bytesLoaded / _bytesTotal;
_tfPercent.text = "Loading: " + int(100 * _bytesLoaded / _bytesTotal) + "%";
}
else {
_barMask.scaleX = 0;
_tfPercent.text = "Loading: 0%";
}
}

public function get btnPlayVisible():Boolean { return _btnPlay.visible; }
public function set btnPlayVisible(aVal: Boolean):void { _btnPlay.visible = aVal;    }

public function free():void
{
removeChild(_preFon);
_preFon = null;
}

}

}

Сразу видно удобство данного варианта, для меня например таковым является, что вся заготовка распологается на одном кадре клипа.

Если вы обратили внимание, что код вышел покомпактнее чем в первом примере, то это лишь от того, что я немножко пооптимальнее написал.

Класс Preloader использует этот класс по назначению, а в классе Main я так же реализовал имитацию процесса загрузки для наглядности.

Таким образом, чтобы использовать прелоадер по назначению, нужно просто убрать имитацию из класса Main.

Архив с исходниками плавного прелоадера: SmoothPreloader.

Пишите ваши замечания и предложения в комментариях, а так же про свой опыт создания красивых прелоадеров!

До новых встреч!