Прямая ссылка на экземпляры класса мувиклипа в Actionscript 2

Вот действительно разочаровывающая проблема Actionscript 2, которую я имею с клипами, связанными с классами, которые созданы на временной шкале и на которые нужно сразу ссылаться в коде:

- В моей библиотеке есть клип "C", связанный с классом "C".

- Класс "C" расширяет MovieClip.

- У меня есть клип на сцене с метками кадров "выкл" и "вкл". Этот фрагмент ролика имеет имя экземпляра "mc".

- На кадре "on" есть экземпляр класса "C" с именем экземпляра "inst".

- Конструктор для класса "C" включает в себя оператор трассировки для вывода "C constructor!" дайте мне знать, когда будет создан экземпляр на сцене.

Теперь допустим, что я запускаю этот код:

mc.gotoAndPlay("on");
var inst_mc:MovieClip = mc.inst;

if (inst_mc){
    trace("inst_mc found!");
}else{
    trace("inst_mc NOT FOUND!");
}

var inst_c:C = C(mc.inst);

if (inst_c){
    trace("inst_c found!");
}else{
    trace("inst_c NOT FOUND!");
}

Кажется, что создание любого объекта в классе, таком как C, не произойдет, пока не завершится выполнение всего кода для текущего фрейма, потому что вывод будет таким:

inst_mc found!
inst_c NOT FOUND!
C constructor!

Какого черта здесь происходит? Я явно сказал среде разработки Flash, что фрагмент ролика C связан с классом C, а этот класс C является производной MovieClip. Так что в моем коде gotoAndPlay ("on") создаст фрагмент ролика "inst", который находится во фрейме "on". Он может найти экземпляр в порядке, но когда я воспринимаю его как тип C, он не работает. И тогда конструктор происходит после всего этого. Как это исправить? Я надеюсь, что после того, как вы что-то измените на временной шкале, соответствующие объекты будут созданы немедленно - и они есть, за исключением случаев, когда они являются явными типами классов. Я могу сослаться на свой экземпляр, но только как мувиклип. Как, черт возьми, я могу это исправить? Это должно вывести:

C constructor!
inst_mc found!
inst_c found!

Спасибо за любую помощь!

* ОБНОВЛЕНИЕ * Спасибо за ответы! К сожалению, для моего проекта нет простого решения, которое сейчас велико и не может быть легко реструктурировано (оно также слишком велико, чтобы его можно было преобразовать в AS3). Я думал о том, чтобы сохранить MC в кадре 1 и скрыть их, но я думаю, что это излишне увеличивает накладные расходы. Даже если для _visible установлено значение false, не будет ли оно по-прежнему расходовать ресурсы? (Это другая, но связанная с этим проблема - отличается ли производительность, если у вас есть сложный, стационарный MC, который не виден по сравнению с тем, когда его вообще нет?)

Моя текущая стратегия выглядит примерно так:

mc.gotoAndPlay("on");
var inst_mc:MovieClip = mc.inst;
var inst_c:C = C(mc.inst);

if (inst_c){
    // Even though I moved to the "on" frame,
    // the object was already initialized/existed already
    // so i can use its class code now
    inst_c.do_something_now();
}else{
    // The class is not accessible, so set a boolean flag
    // which will get dynamically assigned to the *movie clip*.
    // The constructor in class C will look to see if the flag
    // has already been set.  If so, it calls do_something_now()
    // within C's constructor.
    // In class C, trigger_do_something_now is a defined as a 
    // Boolean with no default value.
    // It is not set in the constructor.
    inst_mc.trigger_do_something_now = true;
}

Этот подход беспокоит меня. Это грязно и запутанно. Но я думаю, что это разумный обходной путь. Что, вы парни, думаете? Спасибо!

2 ответа

Решение

К сожалению, именно так работает "объектно-ориентированный" код в AS2. Вот как вы должны визуализировать это: когда точка воспроизведения временной шкалы находится на кадре А, и вы вызываете gotoAndStop(B), точка воспроизведения немедленно продвигается, и все необходимые объекты создаются, но любые сценарии временной шкалы, которые есть у вас в кадре B, еще не выполняются, потому что Flash все еще завершает выполнение кадра A. Это не произойдет до следующей итерации внутреннего цикла Flash. кадр B обрабатывается.

Так было с тех времен, когда уроки молочного склянки были просто проблеском. И, как вы можете догадаться, с прикрепленными к классу клипами все работает одинаково - когда вы перемещаете точку воспроизведения в кадр B, Flash создает все в кадре B, но не запускает сценарии, которые находятся в кадре B, - и это включает в себя конструкторы только что созданных клипов - до следующего кадра.

Самый простой обходной путь - это изменить временную шкалу так, чтобы рассматриваемый MC был создан в кадре 1, но оставался скрытым (или неактивным, за кадром и т. Д.) До команды gotoAndStop. Вы можете сделать это, изменив его альфа (и т. Д.) В IDE в данном кадре, или просто вызвав метод init в то же время, когда вы выполняете gotoAndStop. Лучшее решение - использовать AS3, если это возможно. Есть и другие пути, по которым вы можете пойти, но я бы порекомендовал один из этих двух.

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

В ответ на ваш побочный вопрос клип с _visible=false влечет за собой очень небольшие накладные расходы, если он находится в пределах сцены. Он не рендерится, но есть очень маленький удар, который, я думаю, связан с тем, что Flash рассчитывает свои границы или что-то подобное. (Я провел тест один раз, и вам действительно нужны тысячи из них, чтобы его можно было измерить.) Даже если это является ударом по производительности, проблема заключается в том, чтобы устранить невидимые клипы за пределами сцены. Затем они не испытывают никаких попаданий процессора, как я обнаружил, они просто используют память скриптов (и если вы откладываете их инициализацию до тех пор, пока они не будут готовы к использованию, очень мало памяти).

Просто помните, что обработчики событий срабатывают независимо от того, является ли клип видимым, поэтому не регистрируйте его для событий, пока вы его не инициализируете. Я бы сделал метод init() со всем, что принадлежит конструктору, и вызвал бы его там, где вы иначе построили бы клип.

Во-первых, позвольте мне извиниться за то, что я сосредоточился только на стороннем аспекте этой проблемы (в моих комментариях к ответу Феномаса); хотя я уже высказал свою точку зрения и даже согласен с тем, что объектная ориентация или, по крайней мере, формальный синтаксис класса был добавлен к AS1 в качестве запоздалой мысли под названием AS2 - и в некоторых моментах, как он показывает - я пренебрег ответом на ваш реальный вопрос,

Перечитывая эту ветку, я думаю, что все сводится к "ожиданию одного кадра". Это обходной путь, который мне приходилось использовать много раз в AS2, поэтому странно, что я пренебрегал, увидев, что это реальная проблема здесь.

В любом случае, ваш обходной путь, вероятно, сработает, но, как вы говорите, он может быстро стать кошмаром для техобслуживания. Опция Fenomas является еще одним допустимым решением. В этом случае я бы не пошел таким путем, если бы мог избежать этого, но не из-за производительности; скорее, потому что перестановка множества вещей, когда вы уже выложили вещи, может быть большой работой.

Так что, возможно, вы могли бы попробовать что-то действительно простое, как:

mc.gotoAndPlay("on");
this.onEnterFrame = function():Void {
    trace(mc.inst instanceof C);
    delete this.onEnterFrame;
};

И я думаю, что это должно работать. Событие enterFrame будет перехвачено вашим обработчиком через один кадр после регистрации в нем. К этому моменту у вас есть готовый фрейм, поэтому вам нужно просто очистить обработчик и сделать то, что вы обычно делаете, сразу после выпуска gotoAndPlay.

Я помню, что когда-то мне приходилось делать что-то очень похожее (это был не совсем тот же сценарий, но он сводился к ожиданию одного кадра), поэтому в то время я написал очень простой класс для централизации этого кода. Это было что-то вроде этого:

class FrameDelay {

    function FrameDelay(scope:Object,callback:Function,args:Array) {
            // get a reference to the current enterFrame handler
            // (if any), so we can restore it back when we're done   
        var oldEnterFrameHandler:Function = _root.onEnterFrame;

    _root.onEnterFrame = function():Void {
        oldEnterFrameHandler();
        callback.apply(scope,args);
        _root.onEnterFrame = oldEnterFrameHandler;
    }
    }

}

И вы бы использовали это так:

mc.gotoAndPlay("on");

new FrameDelay(this,onFrameReady,["a","1"]);

function onFrameReady():Void {
    //  arguments (if any), will be available in the arguments array 
    trace(arguments.length);
    trace(mc.inst instanceof C);
    trace(this); 

}

Вы передаете область действия, функцию и, необязательно, массив аргументов. Скорее всего, они вам не понадобятся, и можно было бы просто сделать так, чтобы конструктор класса принял обратный вызов. Но в некоторых случаях у вас могут быть проблемы с областью при обратном вызове (это проблема AS2), поэтому передача области явно более безопасна.

Также обратите внимание, что я использую _root.onEnterFrame. Хотя я делаю "резервную копию" исходного обработчика и восстанавливаю его обратно, когда я закончу, это не обязательно означает, что другие части кода являются такими вежливыми (!). Таким образом, обработчик может быть перезаписан. Если вы думаете, что это может быть проблемой, возможно, вы можете динамически создать мувиклип в корне на некоторой глубине, которую вы знаете, что он не используется, и заменить _root.onEnterFrame на _root.dummy_mc.onEnterFrame.

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

Другие вопросы по тегам