Мы с вами разобрались уже практически во всех нюансах ActionScript. Однако не нужно забывать, что Flash MX - это все-таки в первую очередь дизайнерская среда. А потому необходимо, чтобы наши классы (унаследованные, скажем, от ) обладали всеми преимуществами, которыми обладают обычные клипы. То есть мы должны уметь поместить на сцену экземпляр нового клипа, и при этом Флэш должен сообразить, что этому клипу нужно вызвать новый конструктор и что мы можем пользоваться всеми новыми функциями. Для этого существует механизм, называемый
Для того чтобы зарегистрировать класс, то есть, фактически, ассоциировать наш класс с каким-то клипом, достаточно выполнить одну строчку кода специального вида. После этого Флэш будет знать, что клип, с которым мы ассоциировали наш класс, является не просто объектом , а экземпляром нашего класса (в большинстве случаев унаследованного от ) и вести будет себя с ним соответствующим образом: при создании (как с помощью простого помещения на сцену, так и с помощью использования ) клипа вызывается конструктор нашего класса; клипы, вложенные в наш клип, становятся доступны как поля класса и т. п.
Итак, какой же код регистрирует класс?
За это отвечает единственный "статический" метод класса Object:
Object.registerClass (<LinkageIdentifier>, <ConstructorFunction>);
Здесь <LinkageIdentifier> - это имя идентификатора (строка), заданное в поле Identifier параметров символа клипа, а <ConstructorFunction> - функция-конструктор нашего класса.
Пример:
function MyClass {
//...
}
//....
Object.registerClass ("MyClassSymbol", MyClass);
Допустим, мы сделали класс Circle с двумя методами:
function Circle() {
}
Circle.prototype = new MovieClip();
Circle.prototype.show = function () {
this._visible = true;
}
Circle.prototype.hide = function () {
this._visible = false;
}
И связали его с клипом CircleClip, который является простым кружком:
Object.registerClass ("CirlceClip", Circle);
Спрашивается: где поместить весь этот код?
Классическое решение для Flash MX - поместить его в первом слое Actions клипа CircleClip в первом кадре.
С одной стороны, это неплохо: в любом случае, определение класса "не потеряется", оно привязано к клипу, с которым связан класс. Но редактировать код таких классов не очень удобно. Более удачным решением будет вынесение собственно кода в отдельный файл, который подключается в указанном выше месте с помощью строки #include <filename>.as. Такой способ ближе к подходу, используемому во Flash MX 2004.
Но это еще не все. В любом случае, определение класса нужно помещать внутри блока #initlclip.
# означает начало блока #endinitclip ). Что такое блок # , будет выполнен раньше кода, не помещенного в блоки # . Это очень удобно; например, если мы помещаем все определения классов внутрь блоков # (так всегда и следует поступать), то мы всегда можем быть спокойны, что при любом использовании имени класса вне контекста # оно уже известно флэш-плееру.
Поэтому вот как должен выглядеть первый кадр слоя Actions клипа CircleClip (или include -файл, который мы подключаем в этом же месте):
#initclip
function Circle() {
}
Circle.prototype = new MovieClip();
Circle.prototype.show = function () {
this._visible = true;
}
Circle.prototype.hide = function () {
this._visible = false;
}
Object.registerClass ("CirlceClip", Circle);
#endinitclip
Если вы теперь проверите, как работают наши класс и клип, то может случиться, что они и дальше никак не связаны.
В чем же дело? Вспомним, что для того, чтобы к символам библиотеки можно было обращаться программно (из кода ActionScript), нужно включить флажок "Export for ActionScript" в параметрах клипа. Только после этого Identifier, заданный там же, начинает что-то значить для компилятора Флэш МХ, иначе в ActionScript символ абсолютно недоступен.
Интересно, в каком порядке инициализируются клипы? Например, у нас есть несколько клипов: Сircle, Triangle и Rectangle, а также связанные с ними классы, код которых мы поместили в первые кадры клипов. О каких классах Флэшу раньше становится известно?
А какая нам разница? В данном случае, видимо, никакой.
Но посмотрим, какие проблемы здесь в принципе могут возникнуть.
Пусть у нас есть клип RoundedRectangle, который мы хотим наследовать от Rectangle.
Например, пусть в клипе Rectangle (первый кадр слоя Actions ) имеем такой код:
#initclip
function Rectangle() {
//....
}
Rectangle.prototype = new MovieClip();
Rectangle.prototype.getArea() {
return this.width * this.height;
}
//...
#endinitclip
А в клипе RoundedRectangle (первый кадр слоя Actions ) - такой код:
#initclip
function RoundedRectangle () {
//....
}
RoundedRectangle.prototype = new Rectangle();
RoundedRectangle.prototype.getArea() {
//...
return somethingMoreComplex;
}
//...
#endinitclip
Мы запускаем программу, и может оказаться, что с классом RoundedRectanlge какие-то проблемы, он как будто не отнаследовался от Rectangle. Почему?
Дело в том, что в тот момент, когда флэш-плеер наткнулся на строчку
RoundedRectangle.prototype = new Rectangle();
он еще не знал, что такое Rectangle, а следовательно, не смог создать его экземпляр.
В таких случаях порядком # специального вида.
Как мы помним, Флэш гарантирует, что весь код, помещенный внутрь блоков # , будет выполнен раньше кода, не помещенного в эти блоки.
А что, если мы используем имя класса внутри другого контекста # ? Это и есть проблема из предыдущего параграфа. Мы использовали имя Rectangle в одном блоке # , а RoundedRectangle - в другом, поэтому мы можем получить уже описанную выше проблему.
Все, что нам нужно, - это как-то сообщить Флэшу, что один блок # нужно обрабатывать прежде, чем другой.
Для этого и существует вторая форма использования директивы # :
# , где N - целое число.
Флэш гарантирует, что блок с меньшим числом будет обработан раньше.
Приведем пример неправильного использования # (такое вполне можно сделать по ошибке). Для каждого нового класса-клипа заводим уникальный номер # . В результате очень быстро эти номера начинают идти через 10 (потому что никто не помнит, какой последний - 13-й или 14-й), затем через 100 - и через некоторое время появляются # с номерами 100000.
Ничего особо плохого, кроме путаницы, в этом нет, но ничего хорошего - тоже.
Правильной же стратегией является следующая:
# initclip не ставятся.# initclip 0, для второго - # initclip 1 и т. п.Исправим наш пример:
Клип Rectangle (первый кадр слоя Actions ):
#initclip 0
function Rectangle() {
//....
}
Rectangle.prototype = new MovieClip();
Rectangle.prototype.getArea() {
return this.width * this.height;
}
//...
#endinitclip
Клип RoundedRectangle (первый кадр слоя Actions ):
#initclip 1
function RoundedRectangle () {
//....
}
RoundedRectangle.prototype = new Rectangle();
RoundedRectangle.prototype.getArea() {
//...
return somethingMoreComplex;
}
//...
#endinitclip
Механизм - это еще одно средство динамического создания объектов (наравне с duplicateMovieClip ) во Флэш, который появился в версии Флэш 5. Хотя, пожалуй, для всех программистов это наиболее понятный и естественный способ: создание объекта из класса, или экземпляра символа из символа библиотеки.
Создается объект следующим образом:
myMovieClip.attachMovie(idName, newName, depth [, initObject] );
Здесь myMovieClip - это клип, в который нужно поместить новый клип, idName -идентификатор, указанный в свойствах символа, экземпляр которого вы хотите создать (строка), newName - имя нового объекта, depth - уровень, на который нужно поместить экземпляр символа (см. ниже), а - что-то вроде параметров конструктора, а точнее, уже созданный объект, полями которого нужно проинициализировать вновь создаваемый объект (см. ниже).
Пример:
_root.attachMovie ("MySymbol", "my_mc", 1);
Для того чтобы использовать , нужно обязательно установить галочку Export For ActionScript в свойствах символа.
Заметим, что объекты, созданные с помощью , помещаются в то же дерево владения, что и объекты, перенесенные на сцену непосредственно, а также объекты, созданные с помощью duplicateMovieClip.
Поскольку про duplicateMovieClip мы уже подробно рассказывали в третьей лекции, часть материала вам сейчас покажется знакомой, и мы надеемся, что за счет этого то, что мы описываем здесь, будет понятнее. В конце концов, все время листать книгу, возвращаясь к третьей лекции, было бы исключительно неудобно.
Когда же следует использовать , а когда - duplicateMovieClip?
Если формально, то разницы практически нет, за исключением того, что для использования duplicateMovieClip нужно иметь уже готовый объект, а для использования - нет. Причем, если готовый объект был помещен на сцену непосредственным перетаскиванием, вы его не сможете удалить с помощью removeMovieClip, а это иногда может быть неудобно.
Кроме того, используя , можно положить новый объект в любое место, в то время как duplicateMovieClip позволяет создать его только в том же клипе, в котором лежит исходный клип.
А как же передать параметры конструктору, если мы создаем объект с помощью (или duplicateMovieClip )? К сожалению, такого механизма не существует. Flash MX предлагает некоторую замену, а именно дополнительный параметр функций и duplicateMovieClip - , в качестве которого можно передать готовый объект, полями которого затем будет проинициализирован вновь создаваемый объект.
Правда, нам эта функциональность кажется несколько сомнительной, и мы предлагаем следующее.
Если вы хотите добиться функциональности конструктора, сделайте отдельную функцию init вашему классу и вызывайте ее после создания клипа.
Если же вы хотите иметь конструктор копирования (функцию клонирования), лучше тоже напишите ее сами, чтобы быть уверенным, что все поля будут скопированы надлежащим образом (вы можете сделать поверхностное или глубокое копирование, в зависимости от того, что вам нужно).
Вернемся к параметру depth. Этот параметр отвечает за
Зачем нужны
Объясним более детально.
Мы знаем, что объекты можно поместить на сцену непосредственно либо динамически (с помощью duplicateMovieClip или ). Каждый объект, независимо от способа помещения на сцену, лежит на своем
Если объект помещается на сцену непосредственно, то
Числовые значения
swapDepths );Если объекты помещаются на сцену динамически, то depth. Значение этого параметра должно быть целым числом и может быть как положительным, так и отрицательным. Чуть ниже мы рассмотрим, зачем может понадобиться отрицательная величина depth.
Все эти уровни, как правило, располагаются выше depth ). Дело в том, что непосредственно нарисованные объекты имеют очень большие по модулю отрицательные значения depth (в районе -16384).
Если это зачем-то нужно, :
my_mc1.swapDepths (my_mc2);
Этот метод можно использовать для всех объектов, как помещенных на сцену непосредственно, так и динамически, в том числе менять местами
Пример:
Пусть у нас в библиотеке есть клип " Symbol2 " (синий кружок). Один его экземпляр вытащен на сцену непосредственно, назван s1 и раскрашен в красный цвет. Остальные создаются с помощью следующего кода:
attachMovie ("Symbol2", "s2", 1);
s2._x = 100;
s2._y = 200;
attachMovie ("Symbol2", "s3", 0);
s3._x = 70;
s3._y = 200;
s2._alpha = 30;
Вот что получается (здесь внизу - s1, слева - s3, справа - s2 ):
Все, как мы и ожидали, s2 - выше всех, s1 - ниже всех.
Теперь можем выполнить команду s1.
После чего получим такую картинку:
Мы видим, что s2, как и раньше, выше всех, а s1 и s3 поменялись местами.
А сейчас представьте, что мы захотели добавить к нашей композиции фон (также динамически). Добавим такой код:
attachMovie ("Background", "bkg", -1);
//обратите внимание, отрицательный depth
bkg._x = 50;
bkg._y = 200;
Результат получим следующий:
Все правильно, под клипом bkg теперь находится только синий кружок, который мы (с помощью ) поменяли местами с красным кружком, непосредственно помещенным на сцену. А у красного кружка до выполнения был bkg оказался выше синего кружка, хоть у него (у bkg ) и отрицательный depth.
Иногда бывает нужно динамически создать много объектов. В таких случаях пользуются счетчиками
Пример:
this.depthCounter = 0;
//....
for (var i = 0; i < 10; i++)
this.attachMovie ("SomeSymbol", "symbol"+(depthCounter++),
depthCounter);
Интересным здесь является также тот момент, что счетчик хранится в клипе, в который кладутся новые объекты. То есть, если когда-нибудь позже мы вернемся к созданию новых объектов в клипе, в котором до того уже создавали динамические объекты, мы продолжим считать
Все объекты, созданные с помощью или duplicateMovieClip, можно благополучно удалять посредством removeMovieClip. Здесь все достаточно очевидно, за исключением того, что при удалении определенных объектов у вас будут образовываться "дырки", то есть пустые
Даже только одной первой функции уже достаточно для реализации игры "жизнь", поскольку там не важно, как размещены объекты друг относительно друга по вертикальной оси.
Для реализации такого
Опишем два ключевых момента в реализации более сложного
Во-первых, такой
Во-вторых, нужно помнить, какой объект на каком
Допустим, что вам нужно контролировать изменения значений какого-то поля объекта. Например, нужно запретить изменение значения какой-либо переменной либо "навесить" дополнительное поведение к уже готовому коду.
Классическим решением данной проблемы является использование свойств вместо полей, в таком случае всю обработку изменения вы можете произвести в setter -методе.
Если объект "свой", то есть мы можем ему завести setter -метод, то все достаточно тривиально, и мы это обсуждать не будем.
Допустим, что все обстоит несколько сложнее, а именно: у объекта уже есть поле, которое всеми активно используется, а вам зачем-то очень нужно контролировать его изменение, при этом сам объект "чужой", то есть вы не хотите/не можете изменять его исходный код.
Хорошо было бы иметь функцию, которая позволяет следить за полями в "чужом" объекте. Рассмотрим первый возможный вариант. Напишем функцию watchFunction, которая стирает существующее поле в объекте, сохраняя его значение, создает в объекте вместо него свойство с таким же именем и определяет такие getter и setter, чтобы можно было передать функцию реакции:
function reaction (sender, oldValue, newValue){ //функция реакции
trace ("Object: " + sender + ", old value: "
+ oldValue + ", newValue: " + newValue);
return newValue; //нужно вернуть значение,
//которое будет присвоено свойству
}
circle.myField = 5; //исходная переменная в объекте
//заводим функцию прямо в Object.prototype, чтобы можно было
//вызывать ее для любого объекта.
Object.prototype.watchFunction = function (propName, reaction) {
var tmpValue = this[propName]; //сохраняем значение
delete this[propName]; //удаляем переменную
this["get" + propName] = function () { //определяем getter
return this["field" + propName]; //"field" + propName -
// новое имя переменной
};
this["set"+propName] = function (newValue) {
//определяем setter
this["field" + propName] =
reaction(this, this["field" + propName], newValue);
// вызываем функцию реакции, возвращенное значение устанавливаем
// как новое значение свойства
};
//регистрируем новое свойство
this.addProperty (propName, this["get"+propName], this["set"+propName]);
this["field"+propName] = tmpValue;
// восстанавливаем исходное значение
}
circle.watchFunction ("myField", reaction); //включаем слежение
circle.myField = 6;
circle.myField = 7;
Теперь, если вы посмотрите на вывод программы, то увидите вот что:
Object: _level0.circle, old value: 5, newValue: 6 Object: _level0.circle, old value: 6, newValue: 7
А если нам почему-то не хочется использовать такой способ?
Есть еще один вариант - во Флэш МХ есть функция watch, которая позволяет следить за переменными объекта.
Ее использование выглядит следующим образом: object.watch (propName, reactionFunction, userData), где propName - это имя свойства, за которым нужно следить (строка), reactionFunction - это функция реакции, определение которой должно выглядеть так:
function reactionFunction (propName, oldVal, newVal, userData) {
//...
return newVal; //здесь возвращается значение,
// которое будет в итоге присвоено переменной
}
Обратите внимание, что параметр userData - это дополнительные данные, которые могут быть переданы в функцию.
Пример использования стандартной функции watch:
function reaction (sender, oldValue, newValue) { //функция реакции
trace ("Object: " + sender + ", old value: " +
oldValue + ", newValue: " + newValue);
return newValue; //нужно вернуть значение,
// которое будет присвоено свойству
}
circle = {};
circle.myField = 5; //исходная переменная в объекте
circle.watch ("myField", reaction); //включаем слежение
circle.myField = 6;
circle.myField = 7;
Рассмотрим различия в интерфейсе стандартной функции watch и "нашей" функции watchFunction, которую мы рассмотрели в предыдущем примере.
Фактически, их только два:
watch в callback -метод реакции в качестве первого параметра передается имя поля объекта, за которым производится watch есть возможность передать дополнительные данные в функцию реакции через четвертый параметр функции реакции и функции watch. В нашей функции такая возможность не предусмотрена.Функцию watch нельзя использовать для мониторинга полей get / set.
Ни один из приемов, к сожалению, нельзя использовать для мониторинга "стандартных" полей типа _x, _y и пр.
Единственное, что можно предложить для мониторинга значений таких полей, - это , но это очень плохой способ, ибо реакцию вы получите с большой задержкой, и, видимо, после перерисовки изображения. Кроме того, значение может успеть измениться к первоначальному значению, пока сработает callback -метод .
Иногда для отслеживания времени проще обратиться к программным методам, а не делать дополнительные кадры для того, чтобы узнать, что прошло какое-то время. В таких случаях удобно бывает воспользоваться функцией , которая обеспечивает вызов нужной нам
Существует две функции , первая форма предназначена для вызова методов, которые не обращаются к this, то есть вызова как бы "просто функций", а другая - для вызова методов объектов:
setInterval (function, interval, params); setInterval (object, methodName, interval, params);Если пользоваться первой версией , то в функции function, переданной в качестве параметра , this будет не определен. Если же пользоваться второй версией , то он будет ссылаться на объект object, переданный как параметр .
Обе версии возвращают так называемый intervalID, который потом нужно передать в функцию clearInterval, чтобы прекратить вызовы
Обратите внимание, что в первом случае передается непосредственно функция, тогда как при использовании второй функции ожидается имя метода, то есть его нужно брать в кавычки.
Interval задается в миллисекундах.
Существуют следующие правила для вызовов :
interval меньше, чем период обновления клипа (обратная величина частоте кадров), то есть, updateAfterEvent в обработчике, чтобы обеспечить достаточно частую перерисовку экрана. Функция updateAfterEvent не имеет параметров, и использовать ее можно только в контексте функции, переданной в качестве обработчика setInterval или в контексте обработчика onClipEvent.interval больше, чем период обновления клипа (обратная величина частоте кадров), то есть, Приведем пару примеров использования , которые также помогут нам лучше понять механизм работы этой функции.
Пример 1.
N = 10;
function code1 () {
for (var i = 0; i < N; i++);
trace ("code1 invoked");
}
function code2 () {
for (var i = 0; i < N; i++);
trace ("code2 invoked");
}
int1 = setInterval (code1, 10);
int2 = setInterval (code2, 100);
В данном случае из результатов Output видим, что все работает, как ожидается, то есть обе сallback -функции вызываются с ожидаемой частотой, примерно один вызов второй на 10 вызовов первой.
А теперь попробуем увеличить время, необходимое для выполнения кода функций code1() и code2().
Пример 2.
N = 10000;
function code1 () {
for (var i = 0; i < N; i++);
trace ("code1 invoked");
}
function code2 () {
for (var i = 0; i < N; i++);
trace ("code2 invoked");
}
int1 = setInterval (code1, 10);
int2 = setInterval (code2, 100);
Интересно, что функции оказываются в равноправных условиях, то есть теперь количество вызовов каждой из них примерно одинаково, и вызываются они по очереди.
Так происходит потому, что из-за изменения времени выполнения функций соблюсти интервалы становится невозможно (ни один, ни второй), в результате заявленные интервалы вообще перестают влиять на частоту вызовов.
А теперь проведем небольшое исследование на синхронность. Вопрос состоит в следующем: какой код может быть прерван, а какой - нет?
Создадим ролик из трех кадров, в первый кадр поместим такой код:
function code1 () {
_global.v = 1;
if (_global.v != 1)
trace ("interrupted");
}
function code2 () {
_global.v = 2;
if (_global.v != 2)
trace ("interrupted");
}
int1 = setInterval (code1, 10);
int2 = setInterval (code2, 10);
Во второй - такой:
_global.v = 3;
if (_global.v != 3)
trace ("interrupted");
И в третий - такой:
gotoAndPlay (2);
В этом примере есть три "synchronized" -фрагмента, выделенные жирным шрифтом. Допустим, что Флэш может прерывать выполнение кода из одного обработчика для вызова другого, или же прерывать выполнение кода из кадра (второго кадра, в данном случае), с той же целью. Тогда рано или поздно наступит ситуация, когда один из трех фрагментов кода будет прерван другим, и на консоль будет выведено сообщение "interrupted!". Но на практике этого не происходит, из чего следует, что весь подобный код выполняется синхронно, и не может быть прерван.
А может ли обработчик быть вызван между вызовами кода из двух разных кадров? Это логично, иначе, когда еще ему вызываться. Но проверим это, переместив две строчки из второго кадра в третий.
Теперь второй кадр выглядит так:
_global.v = 3;
А третий - так:
if (_global.v != 3)
trace ("interrupted");
gotoAndPlay (2);
И действительно, в консоль выводятся сообщения "interrupted".
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.