Доступ к мовиклипам после gotoAndStop

Март 5th, 2010 автор: Oleg Antipov

В этом посте я хотел рассмотреть одно не очень очевидное поведение флеша при смене кадров с помощью gotoAndStop. А именно если вы пишете чтото вроде:

this.gotoAndStop(2);
this.MovieClipFromFrameTwo.x = 90;



То вас ждёт глубокое разочарование – MovieClipFromFrameTwo будет не доступен. Дело всё в том что дочерние объекты нового кадра не доступны до тех пор пока не будет выполнен скрипт нового кадра, а он не будет выполнятся до тех пор, пока не будет завершён текущий, т.е. там где вы писали gotoAndStop(2).

Выход из этого есть – можно использовать два события, и писать в них то, что вы хотели написать после gotoAndStop.

  1. Использовать событие ADDED
  2. Использовать событие RENDER



Изменив в них какой либо из параметров клипа можно быть уверенным что на экране отобразится уже клип с изменённым параметром. Если же вы попробуйте использовать событие ENTER_FRAME для этих целей, то увидите, что объект сначала будет появляться на экране без изменённого параметра и лишь после этого, в следующем кадре, он появится уже с изменённым.

Собственно рассмотрим простенький пример:

В примере есть клип goto_example в нём находится два кадра. На каждом кадре находится экземпляр мовиклипа с часами – на первом кадре mc1 и на втором – mc2.

Четыре кнопки управляют переходом между кадрами:

  1. Первая кнопка просто меняет текущий кадр на следующий. Визуально видно что при этом переходе анимация клипа начинается сначала.
  2. Вторая кнопка использует событие ADDED, которое изменяет кадр клипа с 0 на текущий. К сожалению на этом этапе мы не можем обратится к нашему мовиклипу по имени т.к. оно ему ещё не присвоено. Но ссылка на наш клип уже доступна и лежит в event.target. Также мы можем проверить имя клипа через свойство name.
  3. Третья кнопка использует событие RENDER, которое изменяет кадр клипа с 0 на текущий. Обращение по имени к клипу уже возможно, но нам необходимо до вызова gotoAndStop вызвать stage.invalidate() иначе событие RENDER не возникнет.
  4. Ну и четвёртая кнопка показывает использование события ENTER_FRAME. Думаю невооружённым глазом видно, что при смене кадров наш клип начинает проигрываться заново, но потом резко ставится на нужный кадр. Выглядит всё это очень печально )


Ниже приведён полный код класса goto_example. К клипам mc1 и mc2 я обращаюсь через функцию getChildByName, чтобы не писать никаких проверок )
Код примера:

package 
{ 
     import flash.display.MovieClip; 
	 import flash.events.*; 
 
     dynamic public class goto_example extends MovieClip 
	 {
		//В этой переменной будем хранить номер текущего кадра нашего мовиклипа с часами
	 	var curMCFrame:uint=0;
 
		public function goto_example() 
		{
			//Создаём обрабочики событий нажатия на кнопки
			buttGoto.addEventListener(MouseEvent.CLICK, buttGoto_Click);
			buttAdd.addEventListener(MouseEvent.CLICK, buttAdd_Click);
			buttRender.addEventListener(MouseEvent.CLICK, buttRender_Click);
			buttFrame.addEventListener(MouseEvent.CLICK, buttFrame_Click);
		}
 
		//Кнопка просто меняет текущий кадр на следующий
		function buttGoto_Click(e : MouseEvent):void 
		{
			//Меняем текущий кадр на следующий
			if(currentFrame==1) gotoAndStop(2); else gotoAndStop(1);
		}		
 
		//Кнопка меняет текущий кадр на следующий и вызывает событие ADDED
		function buttAdd_Click(e : MouseEvent):void 
		{
			//Создаём обработчик события ADDED
			addEventListener(Event.ADDED, onAdded);
 
			//Запоминаем номер текущего кадра мовиклипа с часами
			curMCFrame=MovieClip(getChildByName("mc"+currentFrame)).currentFrame;
 
			//Меняем текущий кадр на следующий
			if(currentFrame==1) gotoAndStop(2); else gotoAndStop(1);
		}		
 
		//Кнопка меняет текущий кадр на следующий и вызывает событие RENDER
		function buttRender_Click(e : MouseEvent):void 
		{
			//Внимание! Событие RENDER сработает только если вызвать у  стейджа метод invalidate
			stage.invalidate();
 
			//Создаём обработчик события RENDER
			addEventListener(Event.RENDER, onRender);
 
			//Запоминаем номер текущего кадра мовиклипа с часами
			curMCFrame=MovieClip(getChildByName("mc"+currentFrame)).currentFrame;
 
			//Меняем текущий кадр на следующий
			if(currentFrame==1) gotoAndStop(2); else gotoAndStop(1);
		}
 
		function buttFrame_Click(e : MouseEvent):void 
		{
			//Создаём обработчик события ENTER_FRAME
			addEventListener(Event.ENTER_FRAME, onEntFrame);
 
			//Запоминаем номер текущего кадра мовиклипа с часами
			curMCFrame=MovieClip(getChildByName("mc"+currentFrame)).currentFrame;
 
			//Меняем текущий кадр на следующий
			if(currentFrame==1) gotoAndStop(2); else gotoAndStop(1);
		}		
 
		//Обработчик события ADDED
		function onAdded(event:Event):void 
		{
			// К сожалению на этом этапе мы не можем обратится к нашему мовиклипу по имени
			// т.к. оно ему ещё не присвоено. Но ссылка на наш клип уже доступна и лежит в event.target
			var curchild:MovieClip = (event.target as MovieClip);
 
			//К счастью мы можем проверить имя клипа на данном этапе через свойство name
			if (curchild && curchild.name == ("mc"+currentFrame))
			{
				//Проигрываем с нужного места мовиклип с часами
				curchild.gotoAndPlay(curMCFrame + 1);
 
				//Удаляем обработчик события ADDED
				removeEventListener(Event.ADDED, onAdded);
			}
		}
 
		//Обработчик события RENDER
		function onRender(event:Event):void 
		{
			//Проверяем доступен ли наш клип, т.к. это может быть onRender предыдущего кадра
			if (MovieClip(getChildByName("mc"+currentFrame)))
			{
				//При событии RENDER клип уже доступен по имени.
				//Проигрываем с нужного места мовиклип с часами
				MovieClip(getChildByName("mc"+currentFrame)).gotoAndPlay(curMCFrame + 1);
 
				//Удаляем обработчик события RENDER
				removeEventListener(Event.RENDER, onRender);
			}
		}
 
		//Обработчик события ENTER_FRAME
		public function onEntFrame(e : Event):void 
		{
			//Проверяем доступен ли наш клип, т.к. это может быть onEnterFrame предыдущего кадра
			if (MovieClip(getChildByName("mc"+currentFrame)))
			{
				//Проигрываем с нужного места мовиклип с часами
				MovieClip(getChildByName("mc"+currentFrame)).gotoAndPlay(curMCFrame + 1);
 
				//Удаляем обработчик события ENTER_FRAME
				removeEventListener(Event.ENTER_FRAME, onEntFrame);
			}
		}
	 }
}


Весь пример с fla файлом можно качнуть сдесь: http://www.anegmetex.com/devblog/files/sampGotoAndStop.rar

Категория: Хитрости

10 Комментариев

  1. PITon

    Нечаянно забрел на блог. Почти никогда не пишу комменты. Но тут жада показать что я самый умный возобладала :roll:
    Мне кажется удобнее вышеприведенного использовать недокументированную функцию
    addFrameScript(numberFrame, functionName)
    numberFrame – номер кадра при переходе на который сработает функция functionName. (причем номер нумерация с 0.)
    Например чтобы что-то сделать с клипом на втором кадре:
    this.addFrameScript(1, onFrame2); //не забываем что нумерация с 0, поэтому 1 – это второй кадр.
    this.gotoAndStop(2);

    private function onFrame2():void {
    this.MovieClipFromFrameTwo.x = 90;
    this.addFrameScript(1, null); // таким образом удаляется обработчик перехода на второй кадр, чтобы он не вызывался каждый раз когда происходит переход на второй кадр
    }

    Надеюсь это информация окажется полезной :-)

  2. Oleg Antipov

    Спасибо за интересную информацию :)
    Однако очень часто в кадрах уже хранятся разного рода скрипты для управления анимаций. Я так понимаю они затрутся?

    Также остается открытым вопрос производительности этого метода )

  3. PITon

    Да, действительно скрипты затрутся.
    Но IMHO лучше скрипты в кадрах не писать ;-)
    На производительность вроде жалоб не слышал.
    В любом случае каждый может выбрать какой метод ему по душе :-)

  4. Kirill Byvshev

    Привет. Вот у меня раньше тоже были такие проблемы, что пока скрипт кадра не выполнится, то и мувиклипы будут не доступны. Я думаю, что этого можно всегда избежать, это просто напросто не надо при программинге. Или я не прав. Бывают какие-то случаи, что без этого не обойтись? (сейчас я вообще практически не работаю с флеш средой, художник дает сразу растр, весь код в классах, никаких кадров никаких проблем:)

  5. Oleg Antipov

    Привет. Чёто не очень понятно про что речь – про скрипты в кадрах или про доступ к мовиклипам после gotoAndStop… отвечу для всех случаев )

    1) Скрипты в кадрах. Ну самое распространёное – stop() и this.visible=false. Например эффекты удаляются как только они visible=false, эффект отыграл, сделал себе visible=false а класс уровня уже сам удалил его. Также регистрация объектов при добавлении в уровень. Чтото типа “myLevel(parent).objectsArray.push(this)”. Это очень полезно, когда уровни делаются прямо во flash IDE. Также может быть полезно при отыгрывании звуков зависящих от анимации. Например есть анимация падения подстреленного зомби. Звук удара об пол надо добавить например на 10 кадр. Можно конечно отслеживать из основного класса, но гораздо удобней просто добавить код в кадр “sounds.play(’zomb_floorImpact’,this)”.

    2) Зачем доступ к мовиклипам после gotoAndStop. Самый простой пример, где без этого обойтись сложновато. Допустим делаем изометрическую игру про космолёты. У нас есть клип космолёта с двумя кадрами:
    - на первом кадре располагается клип с 8 направлениями космолёта и с неработающими двигателями
    - на втором кадре располагается клип с 8 направлениями космолёта и уже с работающими двигателями.

    Естественно по ходу необходимо будет то включать движки то выключать, но направление космолёта должно сохранятся. Соотвественно как только переходим с первого кадра на второй – направление сбрасывается на начальное. Но нам нужно текущие направление. Вот тут то нам и поможет доступ к мовиклипам после gotoAndStop )
    По сути похожая ситуация описана в самом посте, только про часы.

    Сорри за многобукаф, надеюсь я ответил на твой вопрос :smile:

  6. Max Firsoff

    Спасибо, помучался со своими рендерами для своего листа, пытался изобразить states как во FlexSDK, понял где грабли =)

  7. выаыв

    Если быстро тыкать любую кнопку кроме первой то вылетает сообщение типа
    An ActionScript error has occurred:
    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at goto_example/buttFrame_Click()

  8. Oleg Antipov

    Хм, странно. У меня всё ок если даже быстро кликать по любой кнопке.

    Какой браузер и какая версия флеш плеера?

  9. Игорь

    тоже самое..ошибка вылетает
    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at goto_example/buttFrame_Click()

    firefox 3.6.13
    10-ый плеер

  10. Oleg Antipov

    Короче, вероятно ошибка в функции buttFrame_Click вот в этом месте:

    curMCFrame=MovieClip(getChildByName(”mc”+currentFrame)).currentFrame;

    Если быстро кликать наверно getChildByName не успевает подхватить новый кадр и обрабатывает ещё старый, где этого мовиклипа ещё не было. Надо сделать проверочку что-то вроде:

    if(getChildByName(”mc”+currentFrame)==null) return;

    Если такого клипа ещё нет, выходим из обработчика.

Оставить комментарий