В этой лекции мы познакомимся со способами
создания клипов.
Клип - это, в
простейшем случае, специальный объект, содержащий
анимацию, которую можно проиграть. Создаются такие
клипы с помощью среды Flash
MX. В более сложном случае клипы
могут содержать в себе другие
клипы, а также могут быть созданы программно.
Следует отличать клипы от
флэш-роликов, под
которыми мы будем понимать готовый анимационный "фильм",
возможно, состоящий из многих
клипов, и откомпилированный в файл *.swf.
Оказывается, существует возможность такой
ролик загрузить прямо
внутрь вашего ролика и
проиграть его там. В этой лекции мы научимся создавать
простейшие клипы и
подгружать ролики, а также
начнем изучать специальные средства языка ActionScript,
предназначенные для работы с
клипами. Также мы изучим некоторые особенности
ActionScript, связанные с тем, что он является
интерпретируемым языком.
Доступ к переменной или клипу по имени. Операторы
eval и set
Хотя программы, транслируемые
интерпретатором, как правило, проигрывают
откомпилированным программам в скорости, они имеют и ряд
преимуществ. В частности, иногда очень удобно в момент
трансляции иметь информацию времени выполнения. Часто
это помогает при взаимодействии с внешними по отношению
к ActionScript объектами Флэш (такими, как экземпляры
клипов). И более того, в
следующих лекциях мы увидим нечто похожее на указатели
на функции и множественное наследование - то, чего
многим не хватает в языке Java.
В первую очередь, информация времени
выполнения позволяет нам обратиться по имени (которое
неизвестно на момент компиляции) к какому-либо объекту
(это может быть экземпляр клипа,
созданный "руками", а может быть и
переменная).
Обращение к переменной
Документированный способ
Чтобы обратиться к
переменной, имя которой является значением
выражения типа String,
можно воспользоваться оператором
eval. Синтаксис
обращения к этому оператору совпадает с синтаксисом
вызова функции с одним аргументом (более того, в
References вы найдете
eval в разделе
встроенных функций, хотя по смыслу производимых действий
это, конечно же, оператор). Итак, запись
a1 = 10;
i = 0;
trace(eval("a1"));
trace(eval("a" + ++i));
дважды выводит в консоль десятку. Отсюда
видно, что в качестве аргумента
eval можно
использовать любое выражение: оно будет интерпретировано
как строка, и
eval выдаст значение
переменной с
соответствующим именем. Если такой
переменной нет, то будет возвращено, разумеется,
undefined.
Недокументированный, но удобный способ
Тех же самых результатов мы могли добиться
и с помощью следующего кода:
a1 = 10;
i = 0;
trace(this["a1"]);
trace(this["a" + ++i]);
То есть доступиться к полю
b объекта
а можно при помощи
конструкции a["b"]. Это
очень напоминает работу с массивом; об этой аналогии мы
подробнее поговорим в главе, посвященной контейнерам. А
пока укажем на то, что данная возможность, вообще-то,
является недокументированной. Обычно
недокументированными возможностями следует пользоваться
с осторожностью - в следующих версиях продукта они могут
бесследно исчезнуть. Впрочем, описанное только что
использование квадратных скобок настолько удобно и
широко распространено, что вряд ли им в будущем что-либо
грозит. (Например, во Flash MX 2004 они тоже работают.)
Обращение к полям "вложенных" объектов
Во Флэш МХ обращение по имени, фактически,
может быть использовано вместо указателей. Оставляя за
скобками вопрос о том, приемлема ли подобная практика с
точки зрения стиля программирования, подумаем о
возможных сложностях ее применения. Предположим, вы
написали функцию, в которую передается имя
переменной, с которой она
будет работать. Поначалу вы передаете туда
переменные, заведенные
непосредственно в том же клипе,
в котором вы завели вашу функцию. Но затем вы можете
захотеть передать туда, например, поле объекта,
"вложенного" в ваш клип
(кавычки здесь стоят по той причине, что на самом деле в
вашем клипе хранится только
ссылка на "вложенный" объект). То есть строка,
описывающая это поле, будет выглядеть примерно так: "а.х".
В этом случае использование
eval - единственный
вариант, а квадратные скобки уже не сработают. Вот
пример, который иллюстрирует все вышесказанное.
(Обратите внимание: ряд строчек в примере мы формируем с
помощью одинарных кавычек - просто потому, что двойные
уже заняты. Также обратите внимание, что
клип, в котором мы сейчас
работаем, - это
_root. Но можно было
с тем же успехом вместо
_root писать
this).
a = {};
a.x = 10;
b = 15;
obj = "a";
field = "x";
// Оба варианта работают
trace('eval(obj + "." + field) = ' + eval(obj + "." + field));
trace('_root[obj][field] = ' + _root[obj][field]);
path1 = obj + "." + field;
path2 = "b";
// Работает только первый вариант
trace('eval(path1) = ' + eval(path1));
trace('_root[path1] = ' + _root[path1]);
// Оба варианта работают
trace('eval(path2) = ' + eval(path2));
trace('_root[path2] = ' + _root[path2]);
После запуска этого примера в консоли
получаем:
eval(obj + "." + field) = 10
_root[obj][field] = 10
eval(path1) = 10
_root[path1] =
eval(path2) = 15
_root[path2] = 15
То есть запись, в которой в квадратные
скобки была передана строка с точкой внутри, не
сработала; все же остальное работает нормально.
Создание переменной (поля)
Чтобы создать новую
переменную по имени, оператора
eval
нам будет недостаточно. Выражение, которое он
возвращает, не является lvalue, то есть
"пригодным для употребления слева от оператора
присваивания". Если мы хотим создавать новые
переменные по имени, нужно
пользоваться оператором
set. (В References
этот оператор упомянут в разделе Actions). Синтаксис его
такой же, как у вызова функции с двумя аргументами;
причем, первым аргументом является имя
переменной, а вторым -
значение, которое нужно этой
переменной присвоить. С полями объектов,
вложенных в текущий (иными словами - со строкой имени, в
которой встречается точка), оператор
set
не работает. Вот пример: код
set("s", 33);
trace('eval("s") = ' + eval("s"));
set("s1.y", 33);
trace('eval("s1.y") = ' + eval("s1.y"));
выдает на выходе
eval("s") = 33
eval("s1.y") =
Ограничения по сравнению с ECMA-262 ver.3
Мы уже говорили о том, что язык
ActionScript 1.0, в основном, соответствует третьей
версии стандарта ECMA-262. Однако некоторые из
упомянутых в стандарте вещей не вошли в ActionScript
1.0. Из этих вещей можно отметить отсутствие ряда
встроенных объектов (например, объектов для работы с
регулярными выражениями и с ошибками). Но сейчас мы
должны сделать акцент на том, что возможности оператора
eval
в ActionScript существенно урезаны по сравнению как со
стандартом, так и с хорошо известным языком JavaScript.
А именно, в отличие от ECMA-262 v.3 или JavaScript, во
Flash MX оператор
eval может вычислять
только значения выражений. Оттранслировать же кусочек
кода, выражением не являющийся (а содержащий управляющие
конструкции или же определения функций, создание
объектов и массивов и т.д.), оператор
eval
не может. А ведь в JavaScript, скажем, в
eval
можно передать для трансляции достаточно сложный код.
Решение разработчиков Флэш урезать функциональность
этого оператора объясняется достаточно просто. Поскольку
Флэш-плеер предназначен для загрузки с сайта Macromedia
(не во все версии браузеров входит последняя версия
плеера), то его размер должен быть минимален. Поэтому
были приняты все меры, чтобы оставить "за бортом" плеера
сложный лексический анализатор и компилятор, выдающий
нечто вроде байт-кода. И, в частности, была урезана
функциональность
eval - ведь для
реализации обработки им любого кода все эти инструменты
пришлось бы интегрировать в плеер.
Иерархия клипов, способы ее модификации
В этой главе мы будем говорить, в
основном, о создании клипов
"вручную", хотя несколько слов будет сказано и о
программном дублировании
имеющихся клипов - весьма
полезной возможности, с которой имеет смысл
познакомиться поскорее.
Напомним еще раз, как быстрее всего
сделать произвольный клип и
правильно написать внутри него код. Итак, выбираете
инструмент для рисования эллипсов или прямоугольников.
Рисуете. Выделяете нарисованный объект или несколько
объектов (кстати, имейте в виду, что граница и заливка
прямоугольника или эллипса - это разные графические
примитивы, причем граница может состоять из нескольких
отдельных отрезков). Выбираете из контекстного (по
правой кнопке мыши) меню пункт Convert to Symbol
(появится диалог создания нового символа, с которым вы
уже знакомы). И в правой части панели Properties
вводите имя экземпляра.
Дальше вас подстерегает следующая
"ловушка". Если вы прямо сейчас приметесь писать в
панели Actions код, то, скорее всего, получите не
то, что хотели. Дело в том, что после всех только что
произведенных действий у вас выделен экземпляр вновь
созданного символа. И код, вводимый в панели Actions,
будет относиться к этому конкретному экземпляру (и может
представлять собой только описание его реакции на
события). Соответственно, в
выпадающем списке, расположенном в верхней части панели
Actions, вы увидите надпись вроде "Actions
for clip1 (Symbol 1)". Здесь
Symbol 1 - это имя
символа, а clip1 - имя
экземпляра. А вы, скорее всего, хотите написать код,
относящийся либо к корневому клипу,
либо к вновь созданному символу. Чтобы выполнить первую
задачу, еще раз выделите в линейке времени нужный
кадр, даже если он и
выглядит уже выделенным. Вы увидите (пример приведен для
случая, когда вы редактируете первый
кадр), что надпись в
верхней части панели Actions заменится на
следующую: Actions for Frame 1 of
Layer Name Layer 1 (то есть код относится к
первому кадру и к слою по
имени Layer 1). Чтобы
выполнить вторую задачу, вам нужно будет либо открыть
панель Library и дважды щелкнуть на нужном
символе, либо выделить экземпляр на
сцене и в контекстном меню
выбрать Edit или Edit in Place (во втором
случае вы по-прежнему редактируете именно символ, но
видите, хотя и не можете редактировать, все, что
расположено вокруг выбранного экземпляра). Надпись в
верхней части панели Actions, опять-таки, будет
иметь вид: Actions for Frame 1 of
Layer Name Layer 1 (в случае, если вы выделяете
первый кадр слоя
Layer 1, что и происходит
само собой по умолчанию при первом редактировании
символа). То есть, никакой разницы с надписью, которая у
нас была при аналогичном редактировании корневого
клипа, нет. Как же быстро
определить, какой клип мы
редактируем? Взглянуть на строку непосредственно под
линейкой времени. В последнем случае там написано
Scene 1 Symbol 1, а при
редактировании корневого клипа
было написано просто Scene 1.
Понятно, что в случаях клипов
большой вложенности (о чем мы сейчас поговорим) в этой
строке будет приведен более длинный путь от вершины до
листа дерева владения. А о
том, что означает надпись Scene 1,
будет сказано в подразделе данного параграфа,
посвященном работе со сценами.
Отношения владения для клипов и объектов
Под отношением
владения для клипов
мы здесь понимаем весьма простую вещь: "кто в кого
вложен". Сделать вложенные друг в друга
клипы несложно (а,
зачастую, это совершенно необходимо). Мы уже говорили,
что делается это следующим образом: экземпляр одного
клипа вкладывается в символ
другого (что полностью аналогично семантике
владения в объектном
языке). Впрочем, если говорить более подробно, семантика
владения может быть
устроена по-разному: с помощью ссылок или с помощью
хранения одного объекта внутри другого. Скажем, для
объектов (не встроенных типов) языка Java мы имеем
вариант со ссылками. Для С++ возможны оба варианта.
Наконец, можно представить себе и некий промежуточный
вариант: некоторая часть данных, относящихся ко
вложенному объекту, действительно явным образом хранится
в объекте-владельце, в то время как другая (более
объемная) часть данных доступна по ссылке.
В случае Флэш МХ мы как раз можем
наблюдать либо вариант со ссылками, либо промежуточный
вариант (а разобравшись в деталях, мы поймем, что в
основном имеем дело именно с промежуточным вариантом;
вариант со ссылками - всего лишь его крайний случай).
Давайте же посмотрим, какая именно
информация о вложенном экземпляре хранится в
символе-владельце (а когда будет создан экземпляр этого
символа - то и в экземпляре-владельце). Создадим в новом
документе два символа: один с изображением круга, а
другой - квадрата. Затем вытащим на
сцену три экземпляра круга
и три - квадрата. В панели Properties поменяем
двум кругам и двум квадратам ширину и высоту - так,
чтобы они выглядели вытянутыми по горизонтали или по
вертикали. Еще одному кругу и одному квадрату поменяем
прозрачность (для этого в правой части панели
Properties в выпадающем списке Colors выберем
пункт Alpha и в появившемся рядом поле установим
значение в 30%). А теперь - самое интересное. После всех
этих манипуляций мы можем щелкнуть правой кнопкой мыши
на вытянутом полупрозрачном круге, выбрать пункт
контекстного меню Swap Symbol (четвертый снизу),
и на экране появится диалог подмены символа.
В этом диалоге будут перечислены все
символы, имеющиеся на данный момент в библиотеке вашего
ролика. Если теперь вы в
этом диалоге вместо круга выберете квадрат и нажмете
OK, то увидите, что, действительно, на месте круга
на сцене теперь находится
квадрат. Однако настройки, которые вы устанавливали для
круга, никуда не пропали! Так что "квадрат" теперь
является, скорее, прямоугольником, поскольку круг ранее
тоже был вытянут и превращен в эллипс. И настройки
прозрачности тоже никуда не делись (а если вы их
измените и поменяете символ обратно на круг - вы увидите
и в круге те же изменения). Подчеркнем, что настройки
относятся именно к экземпляру, поскольку все остальные
круги и квадраты на сцене
не меняются, когда вы растягиваете один из них. (Но они
изменятся, если вы отредактируете символ. Для чего,
напомним, надо просто дважды щелкнуть на значке символа
в библиотеке или же, щелкнув правой кнопкой мыши по
экземпляру, выбрать из контекстного меню Edit или
Edit in place. Не забудьте, как вернуться назад -
для этого служит полоса с иерархией
клипов. Если что,
посмотрите, как это делается, в первой лекции.)
Таким образом, отношения
владения между
клипами во Флэш МХ хорошо
описываются следующей схемой.
Для сравнения здесь изображено
владение объектов во Флэш
МХ, с которым мы подробнее познакомимся позднее. Вы
можете убедиться, что нижняя строчка схемы вполне
отображает картину, с которой мы только что
познакомились. В самом деле, в экземпляре-владельце
хранится вложенный экземпляр, содержащий в себе
настройки размеров, прозрачности и местоположения. А
кроме того, содержащий нечто вроде ссылки на символ, в
котором уже хранится основная информация о том, что
собой данный экземпляр представляет. Но это именно
ссылка, поскольку при необходимости она может быть
перенаправлена и указывать на другой символ.
Почему мы уделяем столько внимания
отношению владения? А
потому, что с ним мы будем напрямую иметь дело из
ActionScript и нам важно показать, в каком именно смысле
к клипам можно относиться
как к объектам языка ActionScript. Мы уже кратко
упоминали об этом в первой главе, но повторим еще раз,
поскольку это весьма важно: вы не можете обратиться к
символу из ActionScript. По крайней мере, до тех пор,
пока вы не связываете с ним класс при помощи механизма
регистрации классов (речь об этом пойдет в девятой
главе). А вот к экземпляру обратиться ничего не стоит;
только для этого вы должны присвоить ему определенное
имя. Делается это, как обычно при работе с экземплярами,
в панели Properties. В левой ее части, над полями
ввода, определяющими размеры и месторасположение
экземпляра, имеется поле, куда вводится имя (кстати,
справа от него находится кнопка, вызывающая диалог
подмены символа). Имя должно удовлетворять требованиям,
предъявляемым к идентификатору (иначе вы не сможете
удобным образом использовать его из ActionScript).
Далее, если вы присвоите вложенному экземпляру имя
slave, а затем
экземпляру-владельцу - имя master,
то к вложенному экземпляру вы сможете обращаться вот
так: master.slave. Если вы
вытаскиваете экземпляр на основную
сцену, он автоматически попадает в корневой
экземпляр по имени
_root (так что если
master лежит на основной
сцене, то обратиться к нему
можно так:
_root.master). При этом, если вы пишете
код в кадре определенного
клипа, по умолчанию все
объекты, переменные и
экземпляры клипов, к
которым вы обращаетесь, считаются лежащими именно в этом
клипе. В нашем примере,
если вы пишете код в кадре
основной сцены (то есть в
корневом клипе), обращаться
к вышеназванным клипам
можно master и
master.slave. А если код
пишется в каком-то постороннем
клипе, вам придется написать
_root.master
и _root.master.slave.
Чуть выше мы упомянули, что в
клипе могут лежать объекты
и переменные ActionScript.
Что это означает? А то, что экземпляр
клипа является объектом в
смысле ActionScript и, следовательно, к нему может быть
добавлено в качестве полей любое количество ссылок на
примитивные или не примитивные объекты ActionScript (в
том числе другие клипы).
Поскольку объектом является именно экземпляр, то все
упомянутые поля создаются именно в нем. С другой
стороны, что означает код, который мы пишем в
кадрах линейки времени?
Ведь это мы делаем при редактировании символа? Верно,
однако выполняется этот код в каждом экземпляре
отдельно; причем, если экземпляр существует ограниченное
число кадров (в линейке
времени владельца), то именно такое число
кадров (и, связанный именно
с этими кадрами код) будет
выполнено во время существования экземпляра. После чего
он будет выгружен. Этот код может заводить ссылки на
объекты (примитивные или нет) - они размещаются в
экземпляре, выполняющем код (если явным образом не
указать иного; например a = 5;
означает создание переменной
в текущем клипе, а
_root.a
= 5; - в корневом клипе.
Кстати, здесь и в дальнейшем мы называем
клипами как экземпляры, так
и символы; но чаще - экземпляры. Уточнять, что имеется в
виду, мы будем только в тех случаях, когда смысл не
понятен из контекста).
К настоящему моменту должно вполне
проясниться следующее: почему на схеме
владения владельцем
вложенного клипа назван
экземпляр, а не символ клипа-владельца?
Ведь при редактировании ролика
мы вкладываем экземпляр "подчиненного"
клипа внутрь символа
владельца? Дело в том, что обратившись из ActionScript к
экземпляру-владельцу, а через него - к вложенному
экземпляру, мы можем изменить какие-то параметры
вложенного клипа (например,
строчка кода наподобие
master.slave._width = 50; меняет ширину
клипа
slave). При этом изменение
коснется именно экземпляра master.
Если, к примеру, существует еще один экземпляр того же
символа (под названием, скажем,
master1), то в его вложенном
клипе
master1.slave все
останется как было. То есть при создании экземпляра
класса-владельца создаются новые экземпляры всех
клипов, вложенных в него.
Все это вполне соответствует тому, что мы
можем сконструировать класс, в поле которого будет
храниться некоторый вложенный объект (заметьте, что в
этой фразе мы применили вариант семантики
владения, соответствующей
C++, а не Java или ActionScript: в поле хранится объект,
а не ссылка на него). Чтобы сделать аналогию с
клипами более точной, нам
следовало бы сказать так: в поле хранится все-таки
ссылка, но при создании нового объекта класса-владельца
создается и новый объект, находящийся во
владении. Итак, мы
конструируем класс такой, что в некотором его поле
хранится ссылка на (всякий раз вновь создаваемый)
объект, находящийся во владении.
Мы даже можем установить этому объекту параметры по
умолчанию. Однако, создав объект класса-владельца, мы
сможем затем получить доступ к вложенному объекту и его
изменить. И эти изменения коснутся только того
вложенного объекта, которым владеет именно данный
конкретный объект-владелец. Таким образом, реальным
владельцем является все-таки объект, а не класс (в
случае клипов - экземпляр,
а не символ).
Итак, мы увидели весьма глубокую аналогию
между тем, как клипы
владеют экземплярами других клипов,
и тем, как объекты владеют другими объектами. Вспомним
теперь, что еще мы производили некоторую очень странную
операцию: замену символа для данного экземпляра.
Взглянем еще раз на рисунок, схематически описывающий
владение во Флэш МХ, и
спросим себя: означает ли выявленная аналогия то, что мы
в состоянии заменить объекту базовый класс? Как это ни
удивительно, да, означает! Подробнее мы познакомимся с
такими фокусами в лекции 7 и, особенно, 8 (где на этом
принципе базируется эмуляция множественного
наследования).
Управление вертикальным порядком при редактировании
Вопросы
вертикального порядка (который также называют
z-order или z-порядок) весьма важны, и
поэтому в настоящей главе мы обсудим их дважды. Вначале
мы рассмотрим частный случай: предположим, что никакие
действия наподобие динамической загрузки
клипов или
дублирования клипов (с
помощью ActionScript), а также подгрузки готовых
роликов нами
предприниматься не будут. То есть, обсудим все вопросы,
касающиеся z-порядка,
возникающего при ручном (не программном) манипулировании
клипами и графическими
примитивами.
Итак, в первую очередь
z-порядок естественным
образом возникает за счет вложения
клипов друг в друга. Если не предпринимать
специальных усилий, то все экземпляры
клипов, вложенных в данный,
будут находиться на переднем плане по сравнению с
графическими примитивами, расположенными в этом
клипе. При этом сами
экземпляры будут расположены так: выше те, которые вы
вытащили на сцену
последними. (Если ничего больше с порядком не делать, то
это порождает естественный z-order
расположения клипов разной
вложенности). Однако порядок следования экземпляров вы
можете легко изменить. Выделите нужный
клип и выберите пункт
главного меню: Modify / Arrange - в появившемся
субменю вы найдете команды, позволяющие переместить
клип на одну позицию вверх
или вниз, или же на вершину (или, наоборот, на самое
дно) z-порядка внутри
данного слоя данного клипа.
О слоях мы скажем чуть позже, а пока обсудим ситуацию с
графическими примитивами. Можно ли управлять их
z-порядком таким же
способом, какой только что был описан для
клипов? Оказывается, можно,
но для этого примитивы надо сначала сгруппировать.
Выделяете черной стрелкой (удерживая нажатой клавишу
Shift) все необходимые примитивы, потом выбираете
Ctrl+G (или из главного меню Modify / Group),
и получаете группу, с которой описанный только что для
клипов способ упорядочения
тоже работает. Правда, вы уже не сможете редактировать
графические примитивы, входящие в группу - пока не
разгруппируете их. В этом смысле
клип гораздо удобнее группы. Кстати, в группу
могут входить и экземпляры клипов.
Однако тот способ работы с
z-порядком, который мы
только что описали, употребляется нечасто. Гораздо чаще
употребляются слои. Мы уже говорили в первой главе о
том, как создавать слои.
Слева от линейки времени находится
информация о слоях. (Кстати, каждому слою в линейке
времени соответствует отдельная строка - то есть разные
объекты, присутствующие в различных слоях, могут
появляться и исчезать, а также участвовать в анимации
независимо друг от друга. Раздельное появление и
исчезновение можно, впрочем, настроить и без слоев, а
вот различные приемы анимации по отношению к разным
объектам - нет. Но про анимацию мы в данной книге не
пишем из принципиальных соображений. Если вы пытались
поднять - обеими руками - книгу, в которой подробно
описаны и программирование и анимация на Флэш МХ, вы нас
поймете.) Вы можете создавать слои при помощи кнопки в
левом нижнем углу под списком слоев. Также можно
объединять несколько слоев в папку (это ничего не
значит, просто для удобства) - соответствующая иконка
расположена через одну от иконки создания слоев. Также
любой слой может быть переименован, а еще - скрыт
(только при редактировании - чтобы было видно нижние
слои) или заблокирован (чтобы ничего случайно не
испортить). Для выполнения последних двух действий нужно
кликнуть на точку, расположенную в нужном слое под
значком глаза или замочка (эти значки - в правой верхней
части области управления слоями). Наконец, щелкнув на
один из цветных квадратиков (расположены рядом с точками
для скрытия / открывания слоев и запрета / разрешения их
редактирования) вы переведете соответствующий слой в
контурный режим (все объекты данного слоя будут показаны
контурами соответствующего цвета).
Итак, самый распространенный способ
управления z-порядком при
редактировании - это выделение слоев внутри данного
клипа. Слои можно легко
перетаскивать и тем самым менять их
z-порядок. Можно даже
заставить какой-то слой временно не показываться - не
только в процессе редактирования, но и в runtime.
Для этого надо выделить слой, щелкнуть на нем правой
кнопкой мыши и выбрать пункт контекстного меню Guide
(объяснять, почему называние такое, мы не будем, скажем
только, что дано оно по аналогии с некоей специфической
анимационной возможностью). Каждый
кадр каждого слоя можно редактировать независимо
(на редактируемом слое рядом с его названием появляется
изображение карандаша). Можно даже независимо прикрепить
код к кадрам из разных
слоев (выполняется код, начиная с верхних слоев). Но,
конечно, разыскивать потом код по многочисленным слоям -
это не дело. Так что лучше с кодом обходиться аккуратно
и помещать его в самый верхний (возможно, в остальном -
пустой) слой, чтобы легче было найти любой фрагмент
кода. Осталось заметить, что, в отличие от вложенных
клипов, разные слои одного
и того же клипа всегда
демонстрируются синхронно.
Функции управления порядком следования кадров
Программные способы управления порядком
следования кадров появились
уже в самых ранних версиях Flash. Вскоре мы перечислим
функции, которые этим занимаются. Но прежде дадим
краткое описание алгоритма, по которому производится
переключение кадров.
Итак, кадры в
каждом клипе
ролика переключаются
практически одновременно, по сигналу от таймера, который
сообщает, что время, отведенное на показ
кадра, истекло. (Частота
кадров определяется
частотой кадров корневого
клипа
_level0
- чуть позже мы объясним, что значит
_level0;
для большинства случаев это то же самое, что и
_root.
Установить частоту кадров
можно из пункта главного меню Modify / Document.)
Если к тому моменту, когда уже пора показывать следующий
кадр, завершились еще не
все расчеты или перерисовки в предыдущем, следующему
кадру придется подождать.
Частота кадров может
оказаться ниже, чем задано в соответствующем пункте
меню, но кадры никогда не
теряются. Еще один важный момент: если какой-то
клип впервые появился в
кадре номер
n, то, независимо от этого
номера, сам клип начнет
проигрываться со своего первого
кадра.
Функции управления порядком
кадра, фактически,
производят два типа действий (в различных комбинациях).
Они дают команду перейти с одного
кадра на другой и устанавливают режим
проигрывания "остановлен" или "проигрывается". Когда
клип находится в режиме
"проигрывается", при наступлении времени переключения
кадров он переходит на
следующий кадр и выполняет
код, который в нем записан. (Следующим
кадром после самого
последнего кадра корневого
клипа будет снова первый,
если ролик играется в
циклическом режиме - см. далее; если же
ролик не зациклен, то
корневой клип перейдет в
режим "остановлен". Клипы,
не являющиеся корневыми, доиграв до конца, в любом
случае зацикливаются, если этому не препятствуют
управляющие функции.) Как мы уже говорили, дочерние
клипы типа
MovieClip
проигрываются совершенно независимо от родительских.
Итак, перечислим функции, которые
управляют порядком кадров
во Флэш МХ. А после этого разберем алгоритм перехода
между кадрами более
детально. (Из приведенного ниже списка вы увидите, что
многие функции предназначены, не только для переключения
между кадрами, но и между
сценами. Что такое
сцены и как с ними
работать, будет описано чуть дальше).
-
stop() -
устанавливает текущим состоянием
клипа состояние
"остановленное". Если речь идет о корневом
клипе, то, в случае
отсутствия явных инструкций (когда в последнем
кадре не стоит
stop или
gotoAndPlay),
ролик либо
останавливается в конце проигрывания, либо
зацикливается (в зависимости от внешних настроек).
Если вы просматриваете ролик
прямо в среде Флэш МХ (нажав Ctrl+Enter), то
установить или убрать зацикливание можно с помощью
пункта меню Control / Loop - но нужный пункт
есть только в меню проигрывателя, а не редактора.
Если же вы просматриваете
ролик в браузере, то зацикливание произойдет
или нет в зависимости от установок публикации
(которые отражаются в html; разумеется, нужные
параметры могут быть прописаны в html и вручную -
см. главу 1, а также главу 13, раздел о
html-шаблонах). Соответствующее поведение можно
настроить в диалоге Publish Settings
(вызывается при выборе пункта главного меню File
/ Publish Settings), в закладке HTML.
Если вы не хотите, чтобы поведение
ролика зависело от
внешних настоек, а зацикливать (при помощи явного
указания, на какой кадр
переходить в конце) ролик
вы тоже не планируете, поставьте в последнем
кадре
stop(). Не делать
этого можно лишь тогда, когда корневой
клип содержит
единственный кадр (а
вся функциональность размещена, к примеру, в
дочерних клипах).
-
play() -
устанавливает текущим состоянием
клипа состояние
"проигрывается".
-
gotoAndStop(scene, frame)
- переводит корневой клип
на указанный кадр
указанной сцены.
Сцена задается именем
(строкой), а кадр -
номером или меткой (в последнем случае - также
строкой). После перехода устанавливает корневому
клипу режим
"остановлен". При применении для не корневого
клипа параметр
scene игнорируется.
-
gotoAndStop(frame)
- переводит произвольный клип
на указанный кадр (кадр
задается так же, как и в предыдущем случае). После
перехода устанавливает режим "остановлен".
-
gotoAndPlay(scene, frame)
- переводит корневой клип
на указанный кадр
указанной сцены.
Сцена задается именем
(строкой), а кадр -
номером или меткой (в последнем случае - также
строкой). После перехода устанавливает корневому
клипу режим
"проигрывается". При применении для не корневого
клипа параметр
scene игнорируется.
-
gotoAndPlay(frame)
- переводит произвольный клип
на указанный кадр (кадр
задается так же, как и в предыдущем случае). После
перехода устанавливает режим "проигрывается".
-
nextFrame() -
устанавливает текущим кадром
следующий по порядку, а текущим состоянием
клипа - состояние
"остановлен".
-
nextScene() -
устанавливает текущим кадром
первый кадр на
следующей по порядку сцене,
а текущим состоянием клипа
- состояние "остановлен".
-
prevFrame() -
устанавливает текущим кадром
предыдущий по порядку, а текущим состоянием
клипа - состояние
"остановлен".
-
prevScene() -
устанавливает текущим кадром
- первый кадр на
предыдущей по порядку сцене,
а текущим состоянием клипа
- состояние "остановлен".
Если какая-либо функция не может перейти
по указанному адресу (из-за того, что адрес
некорректен), то перехода не происходит, но
соответствующее состояние "остановлен" или
"проигрывается" устанавливается все равно.
Управление порядком кадров: Actions или методы
класса MovieClip? Работа со сценами
Давайте внимательно посмотрим на список
функций для управления порядком выполнения
кадров. Если вы попытаетесь
разыскать эти функции в справке (Reference), вы
обнаружите, что большинство из них присутствует в двух
местах. Во-первых, в разделе Actions. И,
во-вторых, в качестве методов - в классе
MovieClip.
Дело здесь в том, что в ранних версиях Flash не было
классов и методов, а были только системные команды,
которые назывались Actions. Разумеется, для
управления порядком следования
кадров в клипе также
служили соответствующие Actions. Во Flash MX тоже
имеются Actions, более того, этим термином в
справочнике называются также фундаментальные инструкции
языка, наподобие приемов для работы с циклами. Мы не
будем пользоваться такой терминологией; поскольку сейчас
нас интересуют те Actions, которые предназначены
для управления порядком кадров
в клипе, и именно их мы
будем под словом Actions подразумевать в этом
параграфе. Также мы будем называть их командами. Начиная
с пятой версии Flash, синтаксис этих команд совпадает с
синтаксисом вызова функций. Правда, во Флэше вызов
функций (как и обращение к полям объектов) практически
всегда должен делаться с явным указанием объекта, метод
которого вызывается. То есть вызов, как правило,
делается через точку. Но одним из важных исключений
является случай, когда мы вызываем метод того
клипа, в котором написан
код. Так вот, поскольку клип
является объектом класса
MovieClip (или
объектом наследника
MovieClip), то у
него непременно есть методы, отвечающие за управление
порядком кадров (те, что
перечислены выше). Более того, названия их в точности
совпадают с именами соответствующих Actions.
Поэтому, когда мы пишем play(),
не указывая объекта, метод которого мы вызываем, может
создаться впечатление, что мы просто вызвали метод
play() у
клипа, в котором
выполняется код. Или это все-таки вызван
Action
play() для данного
клипа? Есть ли способ это
узнать, и существует ли какая-либо разница между этими
двумя случаями? Оказывается, разница минимальна, но
заметить ее все-таки можно. И состоит она в том, что
только с помощью Actions может быть совершен
переход между различными сценами
ролика.
Давайте разберемся для начала, что же
такое сцены. Во Флэш МХ они
являются, скорее, рудиментом прежних версий; тем не
менее, их использование иногда удобно (скажем, для
создания предзагрузчиков; см. главу 14). Итак,
сценой называется
последовательность сгруппированных вместе
кадров корневого
клипа. Можно выразиться и
по-другому: использование сцен
- это способ сгруппировать вместе ряд
кадров корневого
клипа. Слова про корневой
клип здесь существенны:
сцены могут создаваться
только в нем. В каждой сцене
может быть произвольное количество
кадров; но, как только все
кадры, которые вы в сцене
создали, закончатся, начинает проигрываться следующая
сцена. Важно, что графику и
экземпляры клипов вы
создаете в каждой конкретной сцене
отдельно - при переходе к следующей
сцене ни один дочерний
клип и ни один графический
примитив не сохраняется, все нужно создавать заново. В
общем-то, для этого сцены и
предназначены: они облегчают переход к новому этапу
анимации, где нужно начать все "с чистого листа".
Удобство сцен состоит в
том, что редактируются они совершенно независимо и не
влияют друг на друга; чтобы эмулировать подобное
поведение (старт "с чистого листа") внутри одной
сцены вам понадобилось бы
отступить от кадров,
относящихся к одной из псевдосцен, какое-то количество
пустых кадров и затем
начать рисовать следующую псевдосцену. Это гораздо менее
удобно: нужно было бы вручную искать, где начинается
псевдосцена, после исчерпания пустых
кадров вторую псевдосцену
отодвигать от первой, вручную делать перескок от конца
первой псевдосцены к началу второй и т.д. В лекции 14
показан пример, где сцены
особенно полезны, поскольку предварительную и основную
сцены пишут разные люди.
Вообще-то, в современных анимационных
роликах на Флэш МХ
сцены используются нечасто.
Но знать, что они есть, полезно хотя бы для того, чтобы
понимать, что означает надпись
Scene 1 в строке под линейкой времени, где
отражена иерархия редактируемых
клипов.
Как создать новую
сцену? Для этого существует панель Scene;
вызовите ее и нажмите на плюсик в нижней ее части.
Переключаться между сценами
можно с помощью этой же панели. Можно также
воспользоваться списком сцен,
выпадающим при нажатии на "кинематографическую
хлопушку", расположенную под линейкой времени с правой
стороны (в правой части строки, где выведена иерархия
редактируемых клипов).
Так каким же образом при помощи работы со
сценами мы сможем отличить
воздействие Actions и методов
MovieClip
на порядок кадров?
Оказывается, очень просто. Actions могут работать
со сценами (точнее, это
могут делать четыре из них:
gotoAndPlay, gotoAndStop,
prevScene и
nextScene), а методы
MovieClip
- нет. Написав в корневом клипе
nextScene без
дополнительного указания объекта, для которого
производится вызов, мы обнаружим, что переход между
сценами работает. И отсюда
сделаем вывод, что вызвался именно
Action, а не несуществующий метод
_root.nextScene.
В остальном же разницы нет никакой. Заметим также, что,
поскольку сцены бывают
только внутри корневого клипа,
то при вызове nextScene
или аналогичной инструкции не из
клипа
_root ничего не
произойдет. И, таким образом, выяснить, что же
происходит внутри любого не корневого
клипа - вызывается ли
Action или метод
клипа - напрямую мы не
можем. Но есть косвенный метод: оказывается, можно
переопределить, например, метод
play класса
MovieClip и
заставить его выдавать сообщение всякий раз, когда его
вызывают. И тогда станет видно, что когда исполняется
команда play без указания
объекта, к которому она относится, выполняется именно
Action, а вовсе не метод
клипа. Еще раз подчеркнем,
что мы имеем дело с исключением. Если бы не было
Actions с именами, совпадающими с именами методов, в
данном случае вызывались бы именно методы
клипа.
Окончательный вывод таков: если вы
пишете, скажем, stop, не
указывая объекта, к которому он относится, будет вызван
Action
stop для текущего
клипа (а не его метод
stop). Но разницу вы не
почувствуете, если не предпримете специальных ухищрений.
Единственное отличие Actions - их набор несколько
богаче, чем набор соответствующих методов
MovieClip,
поскольку включает в себя способы работы со
сценами.
Однако не стоит думать, что методы
управления порядком кадров
класса
MovieClip ущербны в том смысле, что вы не
сможете с их помощью переключить
сцену. Оказывается, как для этих методов, так и
для Actions типа
gotoAndPlay (в случае, когда имя желаемой
сцены не указано),
нумерация кадров в
сценах является сквозной.
Чтобы пояснить это, предположим, что у нас имеются две
сцены
Scene 1 и
Scene 2 по 10
кадров в каждой. Тогда
команда gotoAndPlay(15)
переведет клип на 5
кадр второй
сцены (так же как и
gotoAndPlay("Scene 2", 5)).
И в этом случае уже все равно, писать ли просто
gotoAndPlay(15), или же
_root.gotoAndPlay(15).
Напоследок дадим один совет. Даже если вы
пишете на Флэш преимущественно программный код, вам
придется, скорее всего, работать с анимациями,
сделанными кем-то еще. А при создании анимаций неизбежно
использование многих из вышеприведенных методов. Так
вот, хотим предупредить: по возможности, пореже
передавайте в методы gotoAndStop
или gotoAndPlay номер
кадра. И проследите, чтобы
этого не делал никто в вашем проекте. Вместо
использования номеров оперируйте метками
кадров. (Чтобы присвоить
метку ключевому кадру,
выделите его в линейке времени, и в левой верхней части
панели Properties введите идентификатор в
текстовое поле с названием Frame подсказкой
<Frame Label>). Дело в
том, что вам может понадобиться сдвинуть
кадры вперед или назад по
линейке времени. Метка в этом случае сдвинется вместе с
кадрами, а вот номера
кадров, естественно,
поменяются. Поэтому способ с метками гораздо устойчивее
к исправлениям.
Детальный разбор алгоритма переключения кадров
Теперь давайте рассмотрим алгоритм
переключения кадров более
подробно. Сначала поясним общий принцип. При выполнении
кода, содержащего команды управления порядком
кадров, эти команды не
инициируют мгновенного перехода на соответствующий
кадр, но все "заказанные"
переходы (и соответствующие им переключения режимов
проигрывания / остановки) помещаются в очередь. Когда
код в данном кадре выполнен
до конца, выполняется каждый из переходов, помещенных в
очередь. При этом выполняется и код в каждом из
кадров, на который мы
попадаем (а "заказы на переходы" из этого
кадра кладутся в конец
очереди).
А теперь на примере работы со
сценами закрепим наши
знания по механизму переключения
кадров. Предположим, в одном и том же
кадре вы написали подряд
play и
stop - кажется, что
флэш-плееру даны две взаимоисключающие инструкции. Что
произойдет? Нам нужно вспомнить, что все запланированные
переходы совершаются в порядке очереди (но после того,
как будут выполнены все остальные команды). Кроме того,
хотя команды, изменяющие состояние
клипа на "остановлен" или "проигрывается", и
влияют на это состояние немедленно в момент своего
выполнения (или в момент совершения соответствующего им
перехода, если это gotoAndPlay
или gotoAndStop), но само
состояние будет востребовано лишь к следующему плановому
переключению кадров. А к
этому времени оно может быть изменено другой командой.
Так что все вышеупомянутые инструкции лишь устанавливают
текущий кадр (и состояние
играть / остановиться), переключение на который
произойдет только когда придет время. А это значит, что
из нескольких последовательных инструкций,
"противоречащих" друг другу, лишь выполненная последней
установит реальное состояние, в которое будет переключен
клип (или весь
ролик). Однако, в последней
фразе важна оговорка "противоречащих друг другу".
Скажем, nextScene и
stop друг другу, как
выясняется, не противоречат. Если вы действительно
выполните nextScene, а
потом stop, то
ролику в качестве
следующего кадра будет
указан первый кадр
следующей сцены, а в
качестве следующего состояния - остановленное. То же
самое делает, кстати, и nextScene
сама по себе. А если вы напишете
nextScene, а потом play
(в том же кадре), вы
увидите, что команда play
перезаписала следующее состояние, но не следующий
кадр. То есть в следующем
кадре
ролик перейдет на новую
сцену, но не остановится, а начнет играть дальше.
(Если бы play стояло до
nextScene, то такого
эффекта не было бы - nextScene
установило бы будущее состояние как "остановленное",
заменив то, которое было установлено командой
play). Наконец, отметим,
что вызов метода gotoAndPlay
клипа
_root
после команды nextScene
отменяет ее действие. Дело в том, что этот метод, хотя
ему, в отличие от одноименного
Action, не передается идентификатор
сцены, все же устанавливает
текущий кадр. И выбирает
этот кадр (в соответствии с
номером или меткой) в текущей
сцене. Тот же эффект будет достигнут, если после
nextScene вызывать
_root.gotoAndStop.
А теперь рассмотрим более сложный пример,
который зато проявляет свойства алгоритма переключения
кадров достаточно полно.
Давайте сделаем новый флэш-ролик
с тремя ключевыми кадрами.
В первый из кадров поместим
следующий код:
trace(1);
во второй:
trace(2);
if (i++ < 5){
gotoAndPlay(3);
gotoAndPlay(2);
stop(); // Эта команда здесь бесполезна
trace("-------")
}
if (i > 7) stop();
наконец, в третий
trace(3);
gotoAndPlay(1);
Запускаем и получаем в консоли такую
последовательность:
1
2
-------
3
2
-------
1
3
2
-------
1
3
2
-------
1
3
2
-------
1
3
2
1
2
3
1
2
Можем ли мы ее объяснить, исходя из наших
представлений об алгоритме переключения
кадров? Оказывается, да. В
самом деле, мы начинаем с первого
кадра (печатается 1), доходим до второго обычным
образом (печатается 2), после чего запоминаем, что нам
надо будет перейти на кадры
3 и 2. Но это мы пока только запоминаем, а тем временем
выводим длинную горизонтальную черту. Затем следует
проверка, в которую мы не попадаем, и код в
кадре 2 кончается. Поэтому
мы начинаем выполнять переходы, которые ждут у нас в
очереди. Переходим на кадр
3 и выполняем код, который находится там (выводится
число 3, в очередь ставится переход на
кадр 1). Затем переходим на
кадр 2 (этот переход только
что был первым на очереди), в результате печатается
двойка, а в очередь попадают переходы на
кадры 3 и 2. Затем
печатается горизонтальная черта. Код во втором
кадре закончился, на
очереди у нас переход в кадр
1 (вся очередь выглядит так: 1 - 3 - 2). Переходим
(печатается 1), далее на очереди переход в 3 (переходим,
печатается 3, в очередь попадает переход в 1), следующий
на очереди - переход в 2. После выполнения
кадра 2 распечатаются
двойка, горизонтальная черта, в очередь добавятся
переходы в 3 и 2. Очередь снова приняла вид 1 - 3 - 2,
так что дальше все будет происходить циклически до тех
пор, пока i не станет
больше пяти. Затем кадры
начнут идти по порядку, а потом (когда
i станет больше 7), все
остановится. Мы видим, что именно так все и произошло.
Обратим еще внимание на тот интересный
факт, что функция stop(),
расположенная во втором кадре
после команд переходов, у нас не сработала. Точнее, она
срабатывала и даже не один раз. (Мы, кстати, не писали о
том, что эта команда тоже попадала в очередь переходов и
изменений состояния, но это было именно так.) Но каждый
раз после этой команды в очередь попадала команда
gotoAndPlay(1) перехода на
первый кадр, которая меняла
текущий режим проигрывания на "проигрывается". Как
проверить, что команда stop()
попадает в очередь? Поставить
stop() в конце третьего
кадра. Переходы из очереди выполняются, когда
весь код из кадра уже
выполнен. Поэтому, если бы stop()
в очередь не попадал, ситуация осталась бы прежней.
Однако в этом случае мы увидим, что двух циклов со
стандартным порядком кадров
(1 - 2 - 3 - 1 - 2 - 3) мы уже не видим. Итак,
stop() тоже помещается в
очередь. Еще интересно следующее:
gotoAndPlay поменяет состояние
клипа с "остановлен" на
"проигрывается", если указать переход на несуществующий
кадр? Теперь закомментируем
stop() в третьем
кадре и направим
gotoAndPlay из этого же
кадра на несуществующий
кадр (скажем, 111).
Переходы на кадр 1 из лога,
разумеется, пропадают. Однако циклы со стандартным
порядком кадров (1 - 2 - 3
- 1 - 2 - 3) в конце имеются, а это значит, что действие
команды stop() отменено
переходом gotoAndPlay(111)
(и именно им - если сомневаетесь, закомментируйте этот
переход и увидите, что циклы со стандартным порядком
кадров опять пропали).
Кстати, а что произойдет, если мы
попытаемся перейти на тот кадр,
на котором уже находимся? Оказывается, не произойдет
совершенно ничего (в противном случае это приводило бы к
зацикливанию).
Подгрузка других флэш-роликов в текущий
Есть еще один очень важный способ
воздействовать на иерархию клипов
в ролике, в дополнение к
уже описанным. Вы можете взять и загрузить внутрь вашего
ролика еще один! (Для этого
он должен быть скомпилирован и вы должны знать путь к
*.swf-файлу.) Делается это при помощи функции
loadMovieNum, первым
аргументом которой передается путь к подгружаемому
ролику (путь считается
относительно подгружающего ролика),
а вторым - номер так называемого уровня, на который
подгружается ролик. Все
изначальное содержимое нашего исходного
ролика считается
размещенным на нулевом уровне. Чем выше уровень - тем
выше подгруженный ролик
находится в z-порядке.
Однако здесь мы прервем дальнейшие
пояснения по загрузке внешних
роликов, поскольку более подробно этот процесс
будет уместно разобрать в лекции 14. Пока что нам важно
знать, что подгрузка других
роликов возможна, чтобы раз и навсегда
разобраться с z-порядком.
_level, _root и _global
Итак, мы выяснили, что внешний
ролик может быть подгружен
на определенный уровень и что от номеров уровней
зависит, какой из роликов
будет показываться поверх другого. Корневой
клип нашего
ролика (к полям и объектам
которого мы обращаемся через ссылку
_root),
как мы уже говорили, расположен на нулевом уровне.
Однако тут возникает интересный вопрос. Ведь у
ролика, который мы
подгрузим, скажем, на уровень 1, тоже есть какой-то код
в кадрах. И вполне
вероятно, что этот код обращается к каким-то своим
корневым переменным -
естественно, через ссылку
_root, ведь при
разработке подгружаемого ролика
не обязательно заранее знали, что его будут подгружать в
другой... Не будет ли конфликтов? Оказывается, если все
было так, как мы описали, конфликтов как раз не будет.
Дело заключается в свойствах ссылки
_root.
Эта ссылка указывает не на какой-то "корневой
клип вообще" - такого и не
существует - а именно на корневой
клип данного ролика.
Хорошо, а можно ли из одного
клипа обратиться к
переменным и объектам
другого? Да, к этому нет ни малейших препятствий. Для
этого существуют ссылки
_level0,
_level1,
_level2
и т.д. - каждая относится к корневому
клипу
ролика, подгруженного на соответствующий уровень.
Очевидно, что в обычном случае (до тех пор, пока мы не
начинаем подгружать одни
флэш-ролики в другие)
_level0 - это то же
самое, что и
_root. Но
использовать
_level0 вместо
_root
нежелательно именно потому, что ваш
ролик потом могут захотеть
подгрузить другие. Используйте ссылки
_level
только для работы с подгруженными
роликами.
Далее возникает еще один вопрос.
Предположим, два подгружаемых
ролика желают обменяться данными (или вызывать
какие-то функции, определенные в другом
ролике), но при этом не
знают заранее, на какой уровень кого подгрузят. Как быть
в этом случае? Необходимы данные глобального доступа. И,
действительно, существует объект
_global, в котором
лежит множество глобальных функций и в котором можно
завести свои функции (и просто
переменные). Этот объект одинаков для всех
подгруженных роликов,
выполняющихся в одном флэш-плеере. Но именно в силу
глобальности этого объекта нужно пользоваться им
аккуратно и по возможности добавлять к именам своих
методов и переменных
уникальные префиксы или суффиксы - в общем,
предпринимать все усилия для того, чтобы сделать имена
уникальными, иначе неизбежны конфликты.
Наряду с предопределенными ссылками
_root,
_global
и набором ссылок, начинающихся со слова
_level,
есть еще одна важная предопределенная ссылка. Это ссылка
_parent. Мы описываем ее
здесь, чтобы подчеркнуть ее родство с остальными
предопределенными ссылками (а не со
свойствами
клипа, например).
_parent всегда указывает
на клип, который является
владельцем текущего клипа
(а текущим считается тот клип,
в кадре которого содержится
выполняемый в данный момент код).
Свойства клипа, путь к нему и setProperty
Мы уже рассказали о том, как управлять
порядком кадров внутри
клипа. Теперь пора
поговорить о том, как программно управлять разными
свойствами
клипа как целого, которые
мы можем настроить и из редактора, но потом, возможно,
захотим поменять алгоритмическими средствами. Примеры
таких свойств -
месторасположение клипа,
угол разворота, растяжение или сжатие по взаимно
перпендикулярным осям, прозрачность. Синтаксис для
работы со свойствами такой
же простой, как и для работы с обычными
переменными, например:
clip._x = 10. Но, в
отличие от обычной переменной,
присваивание нового значения
свойству вызывает соответствующее действие
(например, результатом выполнения присваивания
clip._x = 10 будет
передвижение клипа
clip в такое положение,
чтобы его начало координат имело в координатах
родительского клипа
абсциссу, равную 10). Далее мы приведем несколько
наиболее употребительных свойств
клипов с комментариями.
-
_alpha -
прозрачность, а точнее - непрозрачность. В
процентах. Значение 100 соответствует нормальному
виду клипа, значение 0
- полностью прозрачному.
-
_height - размер
клипа по вертикали в
пикселях.
-
_name - строковое
имя экземпляра клипа.
-
_rotation - угол
поворота клипа
относительно начального положения (откладывается
против часовой стрелки, измеряется в градусах).
-
_target (только
для чтения) - строка, описывающая расположение
данного клипа в
иерархии. Вместо точек (как обычно) уровни иерархии
разделяются прямой косой чертой (прямой слэш). Если
путь к клипу начинается
с
_level0, само слово
_level0
опускается и путь начинается прямо с косой черты
(например, вместо
_level0/clip
пишется просто /clip).
Если же путь к клипу
начинается с
_level1,
_level2
или любого другого уровня, кроме нулевого, то
уровень указывается явно. Напомним, что ненулевой
уровень означает ролик,
подгруженный из отдельного *.swf-файла. Заметьте,
что при указании полного пути к
клипу слово
_root
является бесполезным, так как оно действует только
внутри одного конкретного
ролика и не описывает
ролики, подгруженные друг в друга. А вот
слово
_level в данном случае гораздо более
информативно (и потому применяется именно оно).
-
_url (только для
чтения) - это URL того *.swf-файла, в котором
размещен данный клип.
Полезность этого свойства
станет более понятной, если мы снова обратим
внимание на тот факт, что существует возможность
подгружать в некоторый ролик
другие ролики (в виде
готовых *.swf-файлов). Подгружен дополнительный
ролик может быть совсем
не из того места, откуда запущен исходный (более
того, адрес, откуда подгружать
ролик, может быть сформирован динамически, то
есть в процессе выполнения).
-
_visible -
булевское свойство,
которое означает, показывается
клип или полностью скрыт. От случая
_alpha == 0 случай
_visible == false
отличается тем, что в последнем случае
клип не получает
сообщения о том, что на нем произошло нажатие мыши
(и аналогичные, для которых важно, находится мышь
над клипом, или нет).
Код, записанный в кадрах
символа, выполняется для любого экземпляра,
независимо от значения
свойства _visible.
-
_width - размер
клипа по горизонтали в
пикселях.
-
_x - абсцисса
положения начала координат данного
клипа, выраженного в
координатах родителя.
-
_xmouse (только
для чтения)
-
_xscale -
отношение текущего размера по горизонтали к
исходному (выраженное в процентах).
Свойства
_xscale и
_width связаны между
собой: при изменении одного изменяется и другое.
-
_y - ордината
положения начала координат данного
клипа, выраженного в
координатах родителя.
-
_ymouse (только
для чтения)
-
_yscale -
отношение текущего размера по вертикали к исходному
(выраженное в процентах).
Свойства _yscale
и _height связаны
между собой: при изменении одного изменяется и
другое.
Все свойства,
которые не помечены здесь как "только для чтения", можно
изменять. Даже свойство
_name, через которое
доступно имя экземпляра клипа!
В самом деле, давайте создадим
Флэш-ролик, на сцену
которого положим произвольный клип
(и назовем этот экземпляр clip).
Затем в первом кадре
_root
напишем следующий код:
a = clip;
trace("clip._name = " + clip._name);
trace("newName._name = " + newName._name);
clip._name = "newName";
trace("--------");
trace("a._name = " + a._name);
trace("clip._name = " + clip._name);
trace("newName._name = " + newName._name);
После запуска
ролика (Ctrl+Enter) получим в консоли
следующее:
clip._name = clip
newName._name =
--------
a._name = newName
clip._name =
newName._name = newName
Имя клипа
действительно изменилось! И по новому имени мы можем к
нему обратиться, а по старому - нет! Показательно то,
что ссылка, сделанная нами на клип
до смены имени, осталась рабочей.
А теперь зададимся вопросом, можно ли
обращаться к свойствам в
том случае, когда мы узнаем имя объекта или
свойства только во время
выполнения программного кода? То есть когда имена
объекта и/или свойства
записаны у нас в некоторой
переменной (или просто являются результатом
вычисления некоего выражения)? Оказывается, можно, и для
этого существует функция
setProperty. Она принимает следующие параметры:
строку с именем клипа,
название свойства (без
кавычек!), значение, устанавливаемое
свойству. Например:
setProperty ("clip", _visible, false);
Можно ли сделать то же самое при помощи
недокументированных но удобных квадратных скобок? Да.
Пишется это так:
clip["_visible"] = false;
Заметим, что здесь мы смогли использовать
строку с именем свойства,
что удобно, так как информацию о том, какое именно
свойство нам нужно, можно
поместить в переменную.
Правда, имя клипа мы здесь
не могли задать в виде строки, но это можно исправить,
например, так:
_root["clip"]["_visible"] = false;
Зато setProperty
удобнее тем, что в первый аргумент можно передать путь в
виде, скажем "clip1.innerClip.innerInnerClip"
(или в формате со слэшами "clip1/innerClip/innerInnerClip").
То есть для вызова setProperty
нам не надо знать заранее, на какой глубине вложенности
в другие клипы находится
тот клип, путь к которому
мы передаем в эту функцию.
Дублирование клипов, создание пустых клипов
Мы с вами уже познакомились с различными
аспектами создания клипов
(символов и экземпляров) в процессе ручного
редактирования ролика.
Однако экземпляры клипов
можно создавать и программно. Для программного создания
клипов существует три
способа. Два из них предназначены для того, чтобы
создавать экземпляры символов, имеющихся в библиотеке
(один из них - duplicateMovieClip
- мы рассмотрим сейчас, другой -
attachMovie - в девятой главе). Третий (createEmptyMovieClip)
создает пустой клип,
который в дальнейшем можно использовать для разных
целей: рисовать в нем программно (см. главу 10) или
прикреплять к нему дочерние клипы
при помощи attachMovie.
Этот способ мы также подробнее рассмотрим в данном
подпараграфе.
Итак, что же представляет собой функция
duplicateMovieClip? Она
предназначена для дублирования
экземпляров клипов, имеющихся в нашем
ролике, и принимает три
аргумента. Первый - это путь к тому экземпляру, который
мы собираемся продублировать (путь передается в том же
формате, в котором хранится путь к
клипу в свойстве
_target). Второй аргумент
- имя нового экземпляра (путь к нему будет отличаться от
пути к исходному экземпляру только этим именем; то есть
владельцем дубликата будет тот же
клип, который является владельцем исходного
экземпляра). Третий аргумент - так называемая глубина
уровня. Впрочем, это название не полностью соответствует
смыслу параметра: дело в том, что чем больше это число,
тем выше в z-порядке
расположен клип. Заметим
также, что упорядочение по глубине относится только к
экземплярам, которыми владеет один конкретный
клип (владельцем, в
частности, может быть и корневой
клип). Если же владельцы
клипов разные, то z-порядок
будет определяться тем, как упорядочены владельцы.
Сделаем здесь важное терминологическое
замечание. Мы чуть ранее говорили о динамической
подгрузке готовых роликов,
и там тоже фигурировали номера уровней. Так вот, номера
уровней для роликов - это
совсем не то, что номера уровней для
клипов. По влиянию на
z-порядок самым "старшим"
является уровень ролика,
затем расположение клипов-владельцев
в z-порядке, и лишь затем
уже идет уровень подгрузки отдельных
клипов (который надо
учитывать, если сравниваемые по расположению в
z-порядке
клипы принадлежат одному
владельцу).
Итак, попробуем
дублирование клипов в действии. Создадим символ с
нарисованным внутри прямоугольником (без рамки; для
создания такого прямоугольника в панели рисования удобно
выбрать "пустой" цвет линий - его символизирует
зачеркнутый квадратик в правой верхней части палитры).
Сделаем один экземпляр этого символа и присвоим
экземпляру имя sourceClip.
Далее напишем в первом кадре
корневого клипа следующий
код:
// Скрываем исходный клип, чтобы он не портил нам картинку
sourceClip._visible = false;
// Делаем 10 копий исходного клипа, причем каждую
// следующую сдвигаем вниз и вправо
for (var i=1; i<=10; i++){
duplicateMovieClip("sourceClip", "duplicated" + i, i);
setProperty("duplicated" + i, _x, 50 + 20*i);
setProperty("duplicated" + i, _y, 50 + 20*i);
}
Запускаем ролик
и наблюдаем следующую приятную для глаза картину:
Мы видим, что у нас действительно
получилось 10 прямоугольников, наложенных друг на друга
со сдвигом вправо вниз.
Давайте проверим, что происходит в том
случае, когда мы указываем клипу
разместиться на уже существующем уровне. Нарисуем еще
один клип (его символ
теперь будет содержать в себе круг, а экземпляр мы
назовем anotherSourceClip).
И размножим этот клип в
пяти экземплярах, причем поместим их на уже занятые
уровни.
Вот код, который выполняет сию задачу:
// Скрываем исходный клип, чтобы он не портил нам картинку
sourceClip._visible = false;
// Делаем 10 копий исходного клипа, причем каждую
// следующую сдвигаем вниз и вправо
for (var i=1; i<=10; i++){
duplicateMovieClip("sourceClip", "duplicated" + i, i);
setProperty("duplicated" + i, _x, 50 + 20*i);
setProperty("duplicated" + i, _y, 50 + 20*i);
}
// Второй исходный клип тоже скрываем
anotherSourceClip._visible = false;
// Делаем пять копий второго клипа и убеждаемся в том,
// что они занимают место копий первого клипа
// с теми же номерами уровней
for (var i=2; i<=10; i+=2){
duplicateMovieClip("anotherSourceClip", "anotherDup" + i, i);
setProperty("anotherDup" + i, _x, 50 + 20*i);
setProperty("anotherDup" + i, _y, 50 + 20*i);
}
В результате картина изменится и будет
выглядеть следующим образом (исходный прямоугольник
здесь нарочно перекрашен в более светлый цвет для
большего контраста):
То есть все прямоугольники на четных
уровнях были просто заменены кругами! Получается, что
двух клипов на одном уровне
быть не может: разместив на занятом уровне новый
клип, мы неминуемо
выгружаем оттуда старый. Кстати, пользуясь этим
свойством, мы сможем
проверить, что метод
createEmptyMovieClip, который создает пустой
клип на заданном уровне,
действительно работает. Конечно, полноценную проверку мы
пока сделать не можем - пустой
клип невидим. Нужно либо в нем что-либо
программным образом нарисовать (способам программного
рисования посвящена десятая глава), либо программным
образом создать в нем экземпляры какого-либо символа (а
об этом мы будем говорить в главе девятой). Но зато мы
уже знаем, что появление нового
клипа (даже пустого) на некотором уровне очищает
прежнее содержимое этого уровня. Таким образом, добавив
к предыдущему коду следующую строку:
createEmptyMovieClip("empty1", 3);
(которая означает, что мы создаем пустой
клип с именем
empty1 на уровне 3), мы
получим вот какую картинку:
Один прямоугольник просто исчез! И мы,
разумеется, понимаем, почему так произошло: он был
заменен пустым клипом,
занявшим его уровень.
Теперь давайте рассмотрим пару
дополнительных полезных методов класса
MovieClip.
Первый из них - метод swapDepths.
Передается в него либо путь к некоторому
клипу, либо уровень, на
котором этот клип лежит.
При этом указываемый клип
должен обязательно иметь того же родителя, что и
клип, метод которого мы
вызываем. Результатом действия этого метода является
обмен двух наших клипов в
z-порядке.
А можно ли узнать, на каком уровне лежит
некий произвольный клип?
Оказывается, да, и для этого служит метод
getDepth. Никаких
аргументов ему передавать не надо, просто вызовите его
для нужного клипа и он
вернет вам номер уровня.
Давайте посмотрим, как действует этот
метод, и для этой цели допишем к последнему примеру
следующие четыре строчки:
trace("duplicated7.getDepth() = " + duplicated7.getDepth());
trace("anotherDup4.getDepth() = " + anotherDup4.getDepth());
trace("sourceClip.getDepth() = " + sourceClip.getDepth());
trace("anotherSourceClip.getDepth() =
" + anotherSourceClip.getDepth());
Запустив пример снова, мы увидим, что в
консоль выведен вот такой текст:
duplicated7.getDepth() = 7
anotherDup4.getDepth() = 4
sourceClip.getDepth() = -16383
anotherSourceClip.getDepth() = -16381
Таким образом, мы с вами, во-первых,
убедились в том, что getDepth
возвращает именно тот уровень, на который мы и поместили
клип. А во-вторых, увидели,
что у клипов, созданных
"вручную" в процессе редактирования, тоже есть уровни,
их номера вполне соответствуют
z-порядку - то есть отрицательны (поскольку
клипы, созданные вручную,
находятся под клипами,
созданными программно).
Давайте теперь используем наши знания о
методе swapDepths для
того, чтобы поменять местами (в
z-порядке) сначала два
клипа, созданных программно, а затем и
клип, созданный в
редакторе, с клипом,
созданным программно. Добавляем к нашему примеру
следующий код:
anotherDup10.swapDepths(anotherSourceClip.getDepth());
trace("anotherDup10.getDepth() = " + anotherDup10.getDepth());
trace("anotherSourceClip.getDepth() = " +
anotherSourceClip.getDepth());
Видим, что круг, расположенный в правом
нижнем конце цепочки фигур, переместился на задний план,
а функция getDepth
сообщает нам о том, что все перемены мест произошли
успешно. Последнее видно из того, что в консоли
появились две новые строчки:
anotherDup10.getDepth() = -16381
anotherSourceClip.getDepth() = 10
Работает с отрицательными уровнями и
функция createEmptyMovieClip:
если мы вставим перед тремя последними строками кода
вызов
createEmptyMovieClip("empty2", anotherSourceClip.getDepth());
то в консоли вместо предыдущих двух строк
увидим
anotherDup10.getDepth() = 10
anotherSourceClip.getDepth() =
то есть клип
anotherSourceClip был
замещен пустым клипом
empty2, и поэтому обмен
уровнями между клипами
anotherSourceClip и
anotherDup10 не произошел.
Интересно, что функция, предназначенная специально для
удаления клипов, -
removeMovieClip (в
качестве аргумента ей передается путь к удаляемому
клипу) в состоянии удалять
лишь клипы с положительными
значениями уровня. Наконец, давайте проверим, может ли
функция duplicateMovieClip,
создавая дубликат какого-то клипа,
расположить его на отрицательном уровне. Добавляем к
нашему примеру еще три строчки:
duplicateMovieClip("anotherSourceClip", "anotherDup__", -10);
setProperty("anotherDup__", _x, 150);
setProperty("anotherDup__", _y, 30);
и видим, что на указанном нами месте
появился еще один кружок (частично перекрытый первым из
прямоугольников, занимающий уровень 1). То есть только
что созданный клип
действительно расположился на отрицательном уровне.
Программное создание текстовых полей
Мы только что познакомились с методом
createEmptyMovieClip,
позволяющим создать пустой клип
на любом нужном нам уровне. Оказывается, можно также на
заданном уровне разместить не клип,
а текстовое поле (а затем, вызывая его методы,
разместить в нем текст, отформатировать этот текст любым
нужны образом и т.д.). Метод, который создает поле,
также размещен в классе
MovieClip и
называется, естественно,
createTextField. Однако параметров ему передается
существенно больше, чем методу
createEmptyMovieClip. Кроме первых двух
параметров - имени экземпляра и номера уровня (которые у
этих двух методов совпадают) - надо еще передать
координаты x и
y, а также ширину и высоту
текстового поля. При этом координаты
x и
у соответствуют верхнему левому краю текстового
поля.
В отличие от метода
createEmptyMovieClip
(который возвращает ссылку на только что созданный
клип), метод
createTextField ничего не
возвращает. Но, поскольку мы задали имя экземпляра,
созданное текстовое поле легко найти. Затем, записав
что-либо в свойство
text созданного объекта,
мы сможем вывести текст на экран. Например:
_root.createTextField("t_txt", 3, 100, 100, 400, 20);
t_txt.text = "RRRRRRRRRRRRRRRRRRRRRRR";
По умолчанию у созданного таким образом
текстового поля будут установлены следующие
свойства:
type = "dynamic",
border = false,
background = false,
password = false,
multiline = false,
html = false,
embedFonts = false,
variable = null,
maxChars = null
Если вам нужно, скажем, сделать вокруг
текстового поля рамку, следует написать
t_txt.border = true;
Если вы хотите отформатировать текст, вам
нужно будет вызвать у созданного объекта метод
setTextFormat и передать
ему специальный объект класса
TextFormat (в который вы запишете все, что вам
нужно). По умолчанию текст создается со следующим
форматом:
font = "Times New Roman"
size = 12
textColor = 0x000000
bold = false
italic = false
underline = false
url = ""
target = ""
align = "left"
leftMargin = 0
rightMargin = 0
indent = 0
leading = 0
bullet = false
tabStops = [] (массив пуст).
Если вы хотите, например, установить
созданной нами строке выравнивание на правый край (при
этом левый верхний угол текстового поля не сдвинется, но
строка в нем прижмется к правому краю поля), а также
сделать текст полужирным (bold), то нужно будет написать
следующее:
t_fmt = new TextFormat();
t_fmt.bold = true;
t_fmt.align = "right";
t_txt.setTextFormat(t_fmt);
При этом лучше, чтобы текст был взят в
рамку - тогда вы отчетливее увидите, что выравнивание
изменилось.
Вертикальный порядок (z-order) - выжимка
Давайте кратко суммируем все данные,
касающиеся z-порядка,
которые мы упоминали в настоящей главе. Самая главная
ступень иерархии z-порядка
- уровни, на которые подгружаются внешние
ролики. Если
клип
А находится в ролике,
подгруженном на уровень n,
а клип
B - в
ролике, подгруженном на
уровень m, и при этом
n > m, то
клип
A непременно будет показываться поверх
клипа
B, независимо от любых
других деталей, касающихся
z-порядка. Если же m = n
(то есть клипы находятся в
одном и том же ролике - два
разных ролика не могут быть
подгружены на один уровень, вновь загружаемый
ролик неминуемо выгружает с
уровня назначения то, что там было раньше), то надо
рассматривать, внутри какого клипа
находятся A и
В. Если внутри разных и
при этом владелец А лежит
выше владельца B, то и
А всегда будет перекрывать
В. Если же
А и
В лежат в одном клипе,
то нужно посмотреть на уровень (глубину) подгрузки, если
речь идет о динамически созданных
клипах, или же на расположение слоев, если речь
идет об экземплярах, созданных в редакторе Флэш. Как мы
уже говорили, большим значениям уровня соответствуют
выше расположенные клипы
(так что, хотя уровень этот и назван
depth, это не совсем
"глубина"). У клипов,
созданных статически, номер уровня отрицателен. (Здесь и
далее мы считаем, что клипы,
созданные статически и динамически мы не меняли местами
при помощи swapDepths.)
Если А и
В - оба динамические
клипы, то нам известны
номера уровней, на которые мы их подгружали, так что
если номер уровня у А
больше, то он лежит выше. Если А
- динамический клип, а
В - статический, то
А лежит выше. Если
А и
В - оба статические, но расположены в разных
слоях, то А лежит выше,
если он помещен в слой, находящийся выше слоя
В. Наконец, если
А и
В лежат в одном слое, то они расположены в том
порядке, в котором их установили при помощи команд
главного меню, расположенных в разделе Modify /
Arrange. А если эти команды не применялись, то выше
будет расположен тот клип,
чей экземпляр создан в редакторе позже.
Кстати, если мы пытаемся определить
z-порядок во время
выполнения (а не заранее при создании
клипа), то даже для
статически созданных клипов
можно просто вызвать getDepth
(мы получим отрицательную величину) и использовать тот
же алгоритм определения z-порядка,
что и для клипов, созданных
динамически.
И еще раз напомним: не путайте уровни
роликов и уровни
клипов. Уровни загрузки
внешних роликов, как мы
видели только что, гораздо "старше".
Наконец, обсудим следующий вопрос:
совпадает ли z-порядок с
порядком выполнения кода, расположенного в синхронно
показываемых кадрах
различных объектов? И да, и нет. Если речь идет о коде,
расположенном в различных клипах,
то тогда да: код выполняется согласно
z-порядку (в направлении
снизу вверх). При этом код, размещенный в
клипе-владельце, всегда
выполняется раньше, чем код в тех экземплярах
клипов, которыми он
владеет. Однако есть еще возможность разместить код так,
что место его расположения не будет однозначно привязано
к z-порядку какого-либо
изображения. Мы имеем в виду код, располагаемый в одном
и том же клипе, в одном и
том же его кадре, но в
разных слоях. При этом код относится вовсе не к слою, а
к клипу целиком, так что ни
на что, кроме порядка выполнения, помещение кода в
какой-либо слой не влияет. Так вот, код, расположенный в
верхних слоях выполняется раньше, чем код, расположенный
в нижних, - на первый взгляд, в порядке, обратном
z-порядку; но еще раз
повторим, что код из любого слоя относится к
клипу целиком и,
следовательно, к z-порядку
отношения не имеет вовсе. Как бы то ни было, во
избежание путаницы советуем размещать код всегда в самом
верхнем слое (его можно специально освободить от всякой
графики и назвать, например,
Program Code).
Реакция на события
Обсуждая ранее тему реакции на
события, мы подробно
говорили лишь о несколько устаревших способах это
делать. Ведь к тому времени мы с вами еще не знали о
том, что представляют собой объекты в ActionScript и как
во Флэше определяются функции. Теперь мы можем изучить
более удобный способ работы с
событиями - переопределение в объектах (клипах)
функций класса
MovieClip (объектом
которого каждый клип
является, если только этого нарочно не испортить). Итак,
для работы с событиями
класс
MovieClip имеет следующие методы,
подлежащие переопределению:
-
onData - в
клип загружали данные
(см. главу 14) и пришла очередная порция.
-
onDragOver - мышь
в нажатом состоянии протащили через границу нашего
клипа внутрь (после
того, как мышь в нажатом состоянии была вытащена
наружу - см. пояснения в главе 1).
-
onEnterFrame -
начинается процесс переключения
кадров.
-
onKeyDown - нажата
клавиша на клавиатуре. Узнать, какая именно клавиша
была нажата, можно при помощи статических функций
класса Key, а именно,
вызвав Key.getAscii()
или Key.getCode() для
получения символа или его ASCII-кода соответственно.
Чтобы этот механизм работал, добавьте после
определения функций реакции на мышь следующий код:
Key.addListener(clip);
(предполагаем, что clip
- это тот клип, в
котором вы определяете функции реакции на
события клавиатуры; не
забудьте, что события
клавиатуры будут приходить только если на
флэш-плеере будет фокус ввода).
-
onKeyUp - отпущена
клавиша на клавиатуре (последняя отпущенная клавиша
определяется так же, как и в предыдущем случае).
-
onKillFocus -
фокус ввода ушел с клипа.
В функцию реакции на это
событие передается один аргумент: ссылка на
тот объект, к которому перешел фокус.
-
onLoad -
клип только что
загрузился (например, сейчас мы переключились на
кадр, в котором этот
клип впервые
появляется).
-
onMouseDown -
кнопка мыши нажата в произвольной точке
ролика, не обязательно
на клипе.
-
onMouseMove - мышь
передвинута (в произвольной точке
ролика, не обязательно
на клипе).
-
onMouseUp - кнопка
мыши отпущена.
-
onPress - кнопка
мыши нажата на клипе.
-
onRelease - кнопка
мыши нажата на клипе и
на нем же отпущена
-
onReleaseOutside -
кнопка мыши нажата на клипе,
а отпущена снаружи.
-
onRollOut - мышь
выехала с территории клипа
в ненажатом состоянии.
-
onRollOver - мышь
зашла на территорию клипа
в ненажатом состоянии.
-
onSetFocus - фокус
ввода с клавиатуры поставлен на данный
клип. В функцию реакции
на это событие
передается один аргумент: ссылка на объект, с
которого перешел фокус.
-
onUnload -
клип выгружается
(только что мы ушли с последнего
кадра, где данный
экземпляр существовал).
Не забудьте, что при реакции на
события от мыши координаты
мыши (относительно произвольного
клипа) вы можете определить, опросив
свойства
клипа
_xmouse и
_ymouse. Вот пример
определения функций реакции на
события от мыши и клавиатуры:
// Определяем реакцию на события от клавиатуры
clip.onKeyDown = function(){
trace("KeyDown: " + Key.getCode());
}
clip.onKeyUp = function(){
trace("KeyUp: " + Key.getCode());
}
// Подписываем данный клип на получение этих событий
Key.addListener(clip);
// Определяем реакцию на события мыши
clip.onMouseDown = function(){
trace("MouseDown: x = " + _xmouse + " y = " + _ymouse);
}
Запустив пример, вы сможете убедиться,
что при реакции на события
клавиатуры работает автоповтор. Не забывайте только, что
фокус ввода должен находиться на окне с проигрываемым
роликом.
Обратите внимание, что писать
Mouse.addListener(clip) не
нужно: любой клип считается
по умолчанию подписанным на
события мыши.
Подписка на события
В этом параграфе мы, кроме термина "событие",
будем использовать и термин "сообщение". В нашем
контексте это будут синонимы, хотя и с немного
различными оттенками смысла: происходят
события, посылаются по
этому поводу сообщения (хотя иногда мы будем писать, что
посылаются события),
принимаются опять-таки сообщения, но подписка
производится на события.
Итак, как подписаться на события
Флэш? Во-первых, для этого надо знать, какой объект
рассылает связанные с данным типом
событий сообщения. Мы будем называть такой объект
публикатором. Предположим, что имя у этого объекта
broadcaster1 и транслирует
он всем желающим событие
event1. Тогда подписать
объект myObject на это
событие мы сможем так.
Пишем:
broadcaster1.addListener(myObject); после этого у
объекта myObject будет
вызываться метод myObject
.event1() всякий раз, когда происходит
событие
event1. В предыдущем
подпараграфе мы видели, как происходит подписка на
события
onKeyUp и
onKeyDown. Правда, в этом
случае мы заранее знали, что объект
Key (это именно объект,
хотя и определяющий собой класс; подробнее о том, что
представляют собой такие объекты, вы узнаете в главах об
объектах и наследовании) рассылает соответствующие
события. И что об этом
заботится инфраструктура Флэш. А нельзя ли нам самим
разослать какое-нибудь событие?
Оказывается можно, и для этого существует
недокументированный системный объект
ASBroadcaster. Работа у
этого объекта такая: он формирует публикаторы сообщений.
Происходит это следующим образом: пишем
ASBroadcaster.initialize(broadcaster1); и после
этой операции у объекта
broadcaster1 появляется метод
broadcastMessage, которому
в качестве аргумента нужно передать строчку с именем
события. Затем мы вызываем
этот метод, когда хотим отправить
событие.
Чтобы увидеть, как вышеописанные приемы
работают совместно, давайте напишем (и разместим в
первом кадре корневого
клипа) следующий код:
// Создаем объект, который будет работать
// публикатором сообщений
_global.myBroadcaster = new Object();
// Делаем этот объект публикатором
ASBroadcaster.initialize(myBroadcaster);
// Создаем объект, который будет посылать свое сообщение
// через только что созданный публикатор
sender = new Object();
sender.sendCustomEvent = function(){
myBroadcaster.broadcastMessage("onCustomEvent");
}
// Создаем два объекта, способных реагировать на сообщение
// по имени onCustomEvent
// Первый объект
receiver1 = new Object();
receiver1.onCustomEvent = function(){
trace("CustomEvent получен объектом receiver1");
}
// Второй объект
receiver2 = new Object();
receiver2.onCustomEvent = function(){
trace("CustomEvent получен объектом receiver2");
}
// Подписываем на сообытия, рассылаемые через
// myBroadcaster объект receiver1
myBroadcaster.addListener(receiver1);
trace("----- Посылаем сообщение первый раз -----");
sender.sendCustomEvent();
// Теперь подписываем на события от myBroadcaster
// еще и объект receiver2
myBroadcaster.addListener(receiver2);
trace("----- Посылаем сообщение второй раз -----");
sender.sendCustomEvent();
После запуска этого кода получаем в
консоли:
----- Посылаем сообщение первый раз -----
CustomEvent получен объектом receiver1
----- Посылаем сообщение второй раз -----
CustomEvent получен объектом receiver1
CustomEvent получен объектом receiver2
Видим, что второй объект начал
реагировать на событие
только после того, как его подписали на получение
сообщений от соответствующего публикатора.
Если хотите, можете посмотреть, как
устроен ASBroadcaster
внутри, вот по этой ссылке:
http://www.flash-ripper.com/articles/flash/2002_05_22_asbroadcaster.htm#notes
(Или, если вы предпочитаете англоязычный
оригинал статьи, читайте его здесь:
http://www.flashguru.co.uk/000032.php, правда, в нем
есть незначительные опечатки, исправленные при
переводе).
Впрочем, устройство
ASBroadcaster довольно
очевидно, и вы сами легко такой напишете - сейчас или
после несколько более тесного знакомства с ActionScript.
В самом деле, при подписывании объектов нужно всего лишь
добавлять в массив подписываемые объекты; а при посылке
сообщений - вызывать у этих объектов функцию с именем,
совпадающим с именем события.
Итак, вы теперь знаете, как рассылать
произвольные события
произвольным объектам. Является ли событийная модель
Флэш МХ оптимальной? Могут быть разные мнения на этот
счет. Из ее недостатков можно указать следующие:
во-первых, со встроенными
событиями Флэш МХ не передается никакая
информация (приходится "забирать" ее из определенного
заранее глобального места, а это не соответствует
объектной парадигме). Затем, чтобы подписаться на
сообщение, надо знать имя публикатора этого сообщения.
Конечно, можно стандартизировать в вашем проекте эти
имена; но в любом случае, публикатор надо еще и
заводить. Если же вы заведете один общий публикатор для
различных событий, вы
можете столкнуться с тем, что механизм подписки-отписки
не оптимизирован для такого случая (хотя вы, скорее
всего, не обнаружите замедления работы программы). Есть
еще и другие причины завести свои
события , например, добавление возможности
выводить посылаемые сообщения в лог, "ручная" проверка
типа данных, передаваемых с сообщением, и т.п. Короче
говоря, если вы точно знаете, зачем вам это надо, -
заводите свои события; во
всех остальных случаях, разумеется, лучше пользоваться
встроенными событиями Флэш
МХ. Также не возбраняется, конечно, и сочетать свои
события и встроенные:
первые, скажем, можно выводить в лог (в отладочном
режиме) и передавать с ними информацию, а вторые, не
загромождая собой лог, будут, предположительно, работать
несколько быстрее ваших.
Инструкция with и обсуждение областей видимости
Программистам часто приходится работать с
многократно вложенными объектами (да еще, бывает, с
длинными именами). Необходимость постоянно писать что-то
наподобие
mySmartObject.favoriteField.theBestMethod(justTheSameArgument)
может изрядно раздражать. А поскольку мы
уже умеем создавать вложенные
клипы (читай - вложенные объекты), то пора бы
познакомиться со способом сократить подобные записи.
Способ этот - использование инструкции
with.
Простейший пример
Инструкция
with имеет
синтаксис, похожий на синтаксис инструкции
if. То есть сначала
следует ключевое слово
with, после него в
круглых скобках - ссылка на объект, с полями которого мы
собираемся работать; после закрывающей круглой скобки
начинается блок (обрамленный фигурными скобками), внутри
которого идентификаторы в первую очередь
интерпретируются как имена полей заданного объекта. Вот
простейший пример использования этой инструкции.
x = 100;
trace("x = " + x);
obj = {x: 10, y:15};
with (obj){
trace("with (obj): x = " + x);
trace("with (obj): y = " + y);
}
trace("x = " + x);
На выходе получаем:
x = 100
with (obj): x = 10
with (obj): y = 15
x = 100
Пример посложнее
А теперь давайте рассмотрим более сложный
пример.
a = {x: 10, y:15};
a.method = function(arg){
with(this){
trace("with(this): x = " + x);
trace("with(this): eval(arg) = " + eval(arg));
}
trace("eval(arg) = " + eval(arg));
}
a.method("y");
Здесь инструкция
with используется
для того, чтобы избавиться от необходимости каждый раз
писать this внутри метода
некоторого объекта для обращения к его полям. Мы уже
говорили об этой неприятной особенности языка
ActionScript и о том, что чуть ли не половина ошибок при
написании функций-методов происходит из-за того, что
программисты написать this
все-таки забывают (и какого-либо предупреждения от
ActionScript 1.0 при компиляции не дождешься). Так вот,
написав
with (this) и обрамив относящимися к этой
инструкции фигурными скобками все тело функции, вы
избавитесь от подобных проблем. Не забудьте только, что
для создания новых полей объекта
with непригоден
(подробнее об этом - чуть дальше).
Кроме того, из этого примера мы увидим,
что использование
eval замечательно
сочетается с
with: внутри
with
(this) надпись типа
eval("x") приведет к
правильному результату (выдаче поля
х), а снаружи - нет. В
самом деле, после запуска вышеприведенного кода на
выполнение, получим в консоли следующий результат:
with(this): x = 10
with(this): eval(arg) = 15
eval(arg) =
Многоуровневые with
У инструкции
with есть удобное
свойство: внутри блока, относящегося к
with,
можно использовать еще одну инструкцию
with.
Вот пример работы с вложенными
with:
a = {x: 10, y:15};
b = {x: 100, y: 150};
a.method = function(arg){
with(this){
trace("with(this): x = " + x);
trace("with(this): eval(arg) = " + eval(arg));
with (b){
trace("with(b): x = " + x);
trace("with(b): eval(arg) = " + eval(arg));
}
this.b = {x: 1000, y: 1500};
// В качестве объекта, с которым работает следующий
// оператор with будет взят объект this.b, потому что
// мы находимся внутри блока, принадлежащего with(this)
with (b){
trace("with(b): x = " + x);
trace("with(b): eval(arg) = " + eval(arg));
}
trace("with(this): x = " + x);
trace("with(this): eval(arg) = " + eval(arg));
}
trace("eval(arg) = " + eval(arg));
}
a.method("y");
Выводит этот код вот что:
with(this): x = 10
with(this): eval(arg) = 15
with(b): x = 100
with(b): eval(arg) = 150
with(b): x = 1000
with(b): eval(arg) = 1500
with(this): x = 10
with(this): eval(arg) = 15
eval(arg) =
Интересно, что как только мы создали
объект this.b, именно он
стал в первую очередь восприниматься в качестве
b (до тех пор, пока мы
находились внутри блока
with(this)). В
следующем параграфе мы обсудим вопрос старшинства
областей видимости более
детально.
Старшинство областей видимости
К настоящему моменту мы вкратце
познакомились уже со всеми понятиями, которые имеют
отношение к областям видимости
переменных. Итак, встретив
некоторый идентификатор, Флэш ищет соответствующий ему
объект таким образом:
- Если идентификатор находится внутри блока,
относящегося к инструкции
with, то в
первую очередь поиск происходит среди полей объекта,
указанного в
with в круглых
скобках. Если несколько инструкций
with
вложены друг в друга, то поиск начинается с объекта,
относящегося к внутренней инструкции.
- Если идентификатор находится внутри тела
функции, то просматриваются локальные
переменные и аргументы
функции. Если функция сгенерирована внутри другой
функции, то далее просматриваются локальные
переменные и аргументы
внешней функции (точнее, соответствующего экземпляра
ее вызова). О том, что такое экземпляр вызова и как
осуществляется этот поиск, мы подробнее поговорим в
пятой главе, рассказывающей о функциях.
- Затем просматриваются поля
клипа, чей кадр
(или функция реакции на сообщение) содержит
рассматриваемый кусок кода.
- Наконец, просматриваются поля объекта
_global.
Поэтому еще раз повторим важное правило: если вы
хотите, чтобы какие-то функции или объекты были
доступны в любой точке кода, заведите их (то есть,
поместите ссылки на них) в
_global.
Возможные ошибки
При пользовании инструкцией
with
легко забыть, что поиск осуществляется среди уже
имеющихся переменных, и
попытка создать новую переменную
"на ходу" окончится неудачей. Вот пример такого
неправильного использования
with:
obj = {};
with (obj){
z = 20;
}
trace("obj.z = " + obj.z);
trace("_root.z = " + _root.z);
На выходе имеем:
obj.z =
_root.z = 20
В данном случае, поскольку код был
помещен в кадре объекта
_root,
то и переменная
z появилась в
_root.
А если бы этот код был вызван в произвольном месте из
какой-нибудь функции, вам пришлось бы долго разыскивать
переменную
z (она появилась бы в том
клипе, из
кадра или обработчика
событий которого был вызван
код). Так что будьте внимательны.
Свойства и слежение за переменными
Если вам знакомы языки Object Pascal
(Delphi) или C#, то вы, наверное, знаете, что такое
свойства.
Свойство
- это удобная абстрактная надстройка над понятием поля
класса с расширяемой семантикой получения значения и
присваивания (путем неявного вызова get- и set-методов).
Если вы что-нибудь поняли из этого определения, мы
снимаем перед вами шляпы и позволяем вам не читать
подпараграф "Терминология" (хотя он все равно маленький,
так что выиграли вы немного).
Сейчас мы рассмотрим
свойства во Флэш МХ вкратце
(но все равно нам придется несколько забежать вперед).
Более подробно свойства
будут рассмотрены в девятой главе.
Терминология: отличие свойства и поля
Если поле - это
переменная-член класса, то
свойство - это "как бы" поле, а на самом деле -
это нечто большее. То есть, обращаетесь вы с ним, как с
обычным полем, но при этом вместо непосредственного
чтения / записи памяти по определенному адресу
происходят вызовы get-метода (при чтении) и
set-метода (при записи).
Таким образом:
- упрощается доступ к полю класса (используется
интуитивный синтаксис;
- не нарушается инкапсуляция.
Простейшие свойства
Рассмотрим, как объявляются
свойства во Flash MX.
Для этого существует специальный метод
класса Object -
addProperty. Его синтаксис
такой:
myObject.addProperty (prop, getFunc, setFunc);
Здесь prop
- это имя создаваемого свойства
(строка), getFunc - get-метод,
setFunc - set-метод.
Давайте рассмотрим простой код, создающий
свойства:
this.addProperty("scroll", this.getScroll, this.setScroll);
this.addProperty("maxscroll", this.getMaxScroll, null);
В этом примере создаются два
свойства:
scroll (для чтения и
записи) и maxscroll (только
для чтения). Такой код логично было бы разместить в
конструкторе объекта. Впрочем, так делать необязательно
- можно в любой момент завести
свойства любому объекту. Возникает, правда,
вопрос: а нельзя ли завести
свойства не одному объекту, а сразу целому классу
(в то время, когда мы пишем определение класса)? Можно,
и сейчас мы приведем пример того, как это делается (детали
этого примера вы поймете позже, когда мы в шестой главе
обсудим создание классов во всех подробностях).
Предположим, вы только что создали класс
TriangleClass.
Свойство в него добавляется
так:
TriangleClass.prototype.addProperty("area",
TriangleClass.prototype.getArea, null);
TriangleClass.prototype.addProperty("height",
TriangleClass.prototype.getHeight,
TriangleClass.prototype.setHeight);
Приведенный код должен быть помещен после
определения методов getArea,
getHeight и
setHeight, причем,
желательно, в блоке #initclip,
соответствующем определяемому классу (об этом подробнее
см. в девятой главе). В данном примере создается два
свойства:
area (только для чтения) и
height (для чтения и
записи). Создав объект типа
TriangleClass с помощью вызова
new TriangleClass(), вы
обнаружите в нем эти свойства.
Обратите внимание, несмотря на различия в
синтаксисе, результат выполнения обоих примеров одинаков,
только в первом примере код, создающий
свойства, выполняется при
создании каждого объекта, а во втором примере - только
при определении класса. И в первом, и во втором примерах
get- и set-методам будет передаваться
корректная ссылка this,
указывающая на объект, через который обратились к
свойству.
Слежение за переменными (полями)
Повторим, что при каждом обращении к
свойству вызывается либо
get-, либо set-метод. Иными словами, если у
вас есть свойство, то вы
можете проконтролировать любое обращение к нему,
например, "довесить" на каждое обращение какой-нибудь
специальный код, просто путем внесения изменений в
get- и set-методы.
А теперь представьте себе, что вы
пользуетесь классом (вами не контролируемым), в котором
есть какое-то поле (не свойство,
а просто поле), и некоторый код (тоже вами не
контролируемый) обращается к этому полю напрямую (да,
это нарушает инкапсуляцию, именно поэтому и возникает
проблема, о которой мы здесь говорим). Что делать, если
вам зачем-то очень нужно контролировать каждое изменение
данного поля. Для этого используется
слежение за
переменными, которое можно
организовать, либо используя стандартную функцию
watch, либо написав свою
собственную. Оба способа мы детально рассмотрим в главе
"Классы и клипы" в разделе
"Слежение за
переменными".
|