02 февраля 2013

Flash, ответы - 1

Как и обещал выкладываю первую тройку ответов. Напомню, что в качестве эксперимента я решил собирать короткие вопросы по флеш и отвечать на них  в своем журнале.
Надо признаться давненько я не рассказывал о программировании. Все дело в том, что эта часть разработки игр становится для меня рутиной, и преподносит мне все меньше сюрпризов. А сам я углубляюсь в геймдизайн, ливелдизайн, дитрибуцию и прочие сопутствующие моменты.Но сегодня я решил отвлечься, написать немного текста и порадовать журнал новым сообщением. Великих тайн я раскрыть не смогу, ведь сам не обладаю знанием, поэтому рассказ пойдет о простых вещах, об основах так сказать. Итак, сегодня поговорим про flash.
Ожидайте в выпуске:
  • как полностью удалить объект?
  • загрузка звукового файла
  • хочу удалить объект из MovieClip'а со всех кадров!
Как полностью удалить объект?
Дело в том, что в Delphi, на котором я программировал бОльшую часть жизни, удаление объекта происходило просто MyObject.Free()... После вызова этой строчки я мог быть уверен, что оперативная память очистилась от ненужного более объекта.
В as3 такого нет. Поэтому при первом знакомстве моя дотошная натура беспокоилась: "мы же заххх-ламииии-м всю память, моя прелессссть". Погуглив, стало ясно, что as3 оснащен сборщиком мусора, который иногда зачищает память от объектов, на которые никто не ссылается. Например, мы создали мувик и положили на сцену:

var MyClip: MovieClip = new MovieClip;
stage.addChild(MyClip);

Как теперь избавиться от нашего экземпляра MovieClip?
Сначала убираем его со сцены, и теперь stage перестанет ссылаться на наш клип:

stage.removeChild(MyClip);

Но кто-то еще держит ссылку на этот объект. Кто? Конечно, это наша переменная MyClip, являясь по сути ссылкой на сам клип. Зануляем его и дело с концом:

MyClip = null;

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

Загрузка звукового файла
Чтобы подбор звуков для игры не стал мучительным круговоротом "подправил звук -> скомпилировался -> подправил звук -> скомпилировался ->..." я загружаю звуки во время игры. То есть ударили нашего персонажа по голове, и в этот момент я загружаю звук с диска и отправляю на проигрывание. Ударили второй раз - снова гружу с диска. Поэтому, если между ударами заменить файл со звуком, то во второй раз проиграется уже новый звук. Таким образом подгон кромкости, тональности и остального проходит довольно быстро. Когда игра готова, я убираю код с подгрузкой из файла, а вместо этого вшиваю финальные звуки в итоговый файл игры.
Загрузить звук очень просто:

var request: URLRequest = new URLRequest("sounds/" + aName + ".mp3");
var sound: Sound = new Sound;
sound.load(request);
sound.play(); // запускаем загруженный звук

Замечательно, теперь наш звук будет грузиться с диска и сразу проигрываться!

Хочу удалить объект из MovieClip'а со всех кадров!
Итак, самый сложный вопрос на сегодня я оставил напоследок. Чтобы было понятно о чем речь, расскажу такую историю.
К примеру, есть у меня мувик (MovieClip) с анимацией движения человечка с мечом. Так как в игре планируются разные мечи, то в этом мувике вместо меча лежит синий квадратик, и я надеюсь его подменить нужным мечом, которым экипирован человечек. Таким образом я хочу сэкономить на анимациях - делаю одну, а потом просто меняю синий квадратик на меч. При проигрывании анимации я сначала удалю квадратик, и в его позицию помещу меч, с углом поворота все того же убер-квадратика.
И вот теперь встает первая задача - я хочу удалить синий квадратик из мувика! Ведь мне нужно его заменить на меч, а сам квадратик мне и даром не нужен...
Итак, допустим у нас есть объект A в первых двух кадрах мувика B. Делаем так:

B.gotoAndStop(1);
B.removeChild(A);
B.gotoAndStop(2);

И наблюдаем замечательную картину - объект A пропал и из второго кадра тоже! Отлично! То, что нужно? Почти... Давайте после всего этого сделаем так:

B.gotoAndStop(1);

И, о чудо, объект A возродился из пепла, словно неугомонный феникс... Как же так? Не могу точно ответить на этот вопрос, но видимо мувик хранит полные слепки некоторых кадров, а в промежуточных пытается оперировать исходными объектами. Поэтому, когда мы переходим на такой вот "слепок" - все наши манипуляции с внутренними объектами пропадают.
Так же, если у нас в первом кадре есть наш объект A, во втором кадре его нет, а в третьем он появляется вновь - то, чтобы мы ни делали с ним в первом кадре - все напрасно, при проигрывании анимации, в третьем кадре он появится вновь. Это и понятно, ведь флеш не телепат, чтобы понять, что именно мы от него хотим, удаляя внутренний объект.
Как тогда поступить в нашей ситуации? Как гарантированно удалить объект из мувика? Ну, лично я бы удалял ненужный объект каждый кадр. Решение жесткое, но пока другого (стопроцентно-работающего) я не нашел.
Итак, что-то вроде этого:

B.addEventListener(Event.ENTER_FRAME, onFrameUpdate);
function onFrameUpdate(e: Event): void
{
  if(B.contains(A))
    B.removeChild(A);
}

На этом можно было бы и остановиться, но я приведу еще несколько примеров, где данная проблема остро встает:

  • растеризация объектов. это прием, при котором все объекты сначала "фотографируются" в картинку, затем эти векторные объекты из мувика удаляются совсем, а на их место кладется созданная картинка. слова "удаляются" и "кладется" уже наверно навели на мысль, что, если мы этот мувик запустим на анимацию, то уже скоро все наши манипуляции с ним забудутся, и вновь появятся векторные объекты, а картинка просто исчезнет (ведь в кадре-"слепке" ее не было!)
  • "выдергивание" объектов из мувика. сейчас поясню... чтобы не путаться, я помечу слова цветами, надеюсь, так будет яснее что где =) дело в том, что я иногда ищу в мувике нужный объект и переношу его в другой мувик. при этом искомый объект исчезает из исходного мувика и появляется в новом. Пока все ясно... Теперь я перехожу в исходном мувике на следующий кадр и снова ищу нужный объект. А его там нет... почему? Потому что при переходе на следующий кадр исходный мувик "правильно" сработал и удалил объект и из следующего кадра. Ведь "слепка" пока не было. Наверно, это уже менее ясно, но все же... главное - что я не знаю точно, где мувиклип делает свои "слепки" и не могу предсказать, будет ли перенесенный объект в исходном клипе. Как бороться? Я сделал так - в конце исходного мувика добавил пустой кадр и при любых переходах между кадрами я сначала перехожу на последний кадр, и только потом иду на нужный. Так мувиклип гарантированно восстановит все исходные объекты из "слепка". 
Прошу прощения за последний текстовый фарш... Дело в том, что я и сам не до конца разобрался в этом вопросе, поэтому не могу четко написать "так, мол, и так". Отсюда и неясности все эти. Но надеюсь я буду расти, а вместе с моим ростом будет улучшаться качество моих записей. Во флеш я новичок, поэтому буду и вопросы раскрывать "новичковые", с которыми сам и сталкиваюсь.

Если интересно - не стесняйтесь, задавайте свои вопросы в общей теме, возможно я смогу ответить!

Сообщения, схожие по тематике:

12 коммент.:

  1. Самое вкусное, что я подметил для себя, это приём экипировки персонажа)

    ОтветитьУдалить
    Ответы
    1. отлично... то, что я привел только в качестве примера, оказалось самым интересным))
      буду лучше подбирать материал тогда...

      Удалить
  2. На доступном языке об интересных вещах. по первому вопросу - как в Java, автоматический сборщик мусора, чего жутко нехватает а с++.
    Про загрузку аудиофайла - сам принцип полезен)
    Про удалание обьекта даже понял все, хоть с flash не сталкивался.
    сэнсэй =)

    ОтветитьУдалить
    Ответы
    1. да, сборщики мусора - уже стандартная штука... но мне, как динозавру, она в диковинку))
      здорово, что про удаление все понятно!
      надеюсь, как-нибудь пригодится!
      спасибо за теплый комментарий

      Удалить
  3. Салют
    Вставлю своих пять копеек. Чтобы полностью удалить наследуемый от EventDispatcher объект нужно отписаться от его событий и отписать его от событий других объектов(свести количество ссылок между объектами к 0). Обычно все это забивается в метод free/destroy/dispose игрового объекта и он вызывается вместо removeChild. Кроме того, если есть желание тут же увидеть эффект от удаления можно вызвать внеочередную сессию сбора мусора: System.gc(); Более подробно: http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html
    Касательно удаления объектов с кадров, это осбенность структуры DL в Flash, ты абсолютно прав в своих предположениях :). Подобное пришлось делать только один раз, когда нужно было менять скин анимированному персонажу. Сейчас понимаю что сделал бы все абсолютно по-другому, через скелетную анимацию :).
    По растеризации: сейчас этот метод становится малоактуальным, так как не дает достаточной производительности на мобильных устройствах, вместо реализации велосипеда советую сразу переходить на Stage3D. Сейчас пишу проект на базе Starling, отличные впечетления от производительности. Конечно, это если нацеливатся на кроссплатформенное приложение.

    ОтветитьУдалить
    Ответы
    1. С4, ты невероятно крут! большое спасибо, что поправляешь меня!
      1. насчет System.gc() не знал, спасибо!
      2. по поводу скелетной анимации очень интересно, посоветуешь какие-нибудь примеры/литературу?
      3. все советуют Starling, значит так тому и быть! как начну под ios прогать, сразу его заюзаю! огромнейшее спасибо тебе!
      буду рад, если сможешь найти время и будешь заглядывать сюда и поправлять/направлять меня!

      Удалить
  4. "Дело в том, что в Delphi, на котором я программировал бОльшую часть жизни, удаление объекта происходило просто MyObject.Free()... После вызова этой строчки я мог быть уверен, что оперативная память очистилась от ненужного более объекта. "

    Объекты в Delphi, имплементирующие интерфейс IInterface и, чаще всего, наследованные от TInterfacedObject, удаляются по схожим правилам, при условии использования интерфейсных ссылок.

    IMyInterface = interface
    ...
    end;

    TMyObject = class(TInterfacedObject, IMyInterface)
    ...
    end;

    var
    obj1, obj2: IMyInterface;

    obj1 := TMyObject.Create;
    obj2 := obj1;
    ...
    obj1 := nil; //Объект остается в памяти и все еще доступен по obj2
    obj2 := nil; //объект удаляется, так как на него перестали указывать все интерфейсные ссылки.

    ОтветитьУдалить
    Ответы
    1. спасибо за поправку! действительно, интерфейсы с автоматическим подсчетом ссылок по сути выполняют тот же функционал сборщика мусора...
      не знаю почему, но я так и не смог освоить интерфейсы и всегда боюсь, когда в чужом коде вижу префикс I... так я и не знаю, как там у них все работает...
      надо будет как-нибудь собраться с силами и изучить!
      спасибо!

      Удалить
  5. Я думаю при удалении MovieClip, лучше делать через MC.parent.removeChild(), так будет универсально.

    ОтветитьУдалить
    Ответы
    1. верно!
      в своем тексте же я хотел показать смысл, основу так сказать...
      спасибо за дополнение!

      Удалить
  6. Ты бы почту на fgl проверил что ли)))

    ОтветитьУдалить
    Ответы
    1. привет! сорри... проверил, ответил и все такое))

      Удалить