Внимание!
Для работы с этой лекцией необходимы учебные файлы,
которые Вы можете загрузить
здесь.
В этой
лекции мы будем рассматривать прорисовку API - новую
возможность Flash MX, позволяющую создавать новое
графическое содержимое "на лету". Мы начнем с описания
основных принципов и методов работы, а затем рассмотрим,
как можно расширить возможности рисования API, например,
написав дополнительные методы для рисования квадратов.
Прежде всего,
мы рассмотрим следующие случаи применения API-рисования.
- Рисование прямых
и кривых линий.
- Рисование кривых
линий через определенную точку.
- Заливка области
сплошным цветом или градиентом.
- Использование
общих объектов для создания альбома, в котором можно
локально сохранить изображения.
Рисование прямой линии
Для
рисования прямой линии с помощью API-рисования
необходимо знать следующее.
- У каждого фильма
есть текущая позиция рисования, которая может быть
установлена с помощью функции
moveTo. Каждый фильм инициализируется в
позиции рисования по умолчанию (0,0).
- Каждый фильм
имеет параметр lineStyle,
который определяет внешний вид всех линий, имеющихся
в фильме. Если не настраивать параметр
lineStyle, ни одна
нарисованная нами линия не будет отображаться. (Иногда
это бывает полезным, как мы убедимся далее при
добавлении заливки.)
Итак,
чтобы нарисовать прямую линию, откройте панель
Actions. Введите следующий код в кадр 1 вашего
нового фильма и проверьте его работу.
this.lineStyle (3,
0x000000);
this.lineTo (200, 100);
Это
действие нарисует линию из левого верхнего угла
рабочего места до позиции (200, 100). С помощью
функции lineStyle мы
настраиваем линию на ширину в три пикселя и черный
цвет. Вы также можете добавить необязательный третий
аргумент для установки интенсивности линии. По
умолчанию это значение равно 100. Если не указать
цвет линии, она будет нарисована черным цветом.
Если
линия должна начинаться из какого-либо другого места,
нужно использовать функцию
moveTo:
this.lineStyle (3,
0x000000);
this.moveTo (200, 0);
this.lineTo (200, 100);
moveTo перемещает
точку начала рисования без прорисовки линии, а
lineTo рисует линию и
перемещает точку рисования.
this.lineStyle (3,
0x000000);
this.moveTo (200, 0);
this.lineTo (200, 100);
this.lineTo (400, 100);
Это все,
что нужно для рисования прямой линии. Функцию
lineTo мы позже будем
использовать для определения края заполненной
области.
Рисование кривой линии
Кривая
линия рисуется аналогичным образом, однако здесь уже
требуется указывать два набора точек.
this.curveTo (controlX,
controlY, anchorX, anchorY);
Линия будет
нарисована из текущей точки рисования в точку
anchorX,
anchorY со значением
controlX и
controlY, определяющим
изгиб линии. Чтобы разобраться, каким образом значения
controlX и
controlY влияют на
кривизну, нужно представить себе третью точку как своего
рода магнит, "сгибающий" линию по направлению к себе.
Попробуйте выполнить следующий код.
this.lineStyle (3, 0x000000);
this.moveTo (50, 50);
this.curveTo (100, 0, 250, 250);
Затем
сравните его с результатом работы этой программы.
this.lineStyle (3, 0x000000);
this.moveTo (50, 50);
this.curveTo (200, 300, 250, 250);
Для большей
наглядности того, как точка контроля влияет на кривизну
линии, мы объединим фильм с тремя перетаскиваемыми
управляющими элементами - по одному на каждый конец
линии и третий для представления точки контроля.
- Откройте новый
файл Flash и нарисуйте простую фигуру, например,
окружность. Затем преобразуйте ее в фильм с
центральной точкой закрепления и назовите его point.
- Выберите фильм в
библиотеке Library и щелкните на белом значке меню.
В меню выберите команду
Linkage:, чтобы открыть диалоговое окно
Linkage Properties. Отметьте опцию Export for
ActionScript и в поле Identifier введите
point.
- Удалите ваш
инстанс point с рабочего места и добавьте в главный
слой следующий код.
_quality = "LOW";
this.attachMovie("point", "start_mc", 1);
this.attachMovie("point", "end_mc", 2);
this.attachMovie("point", "control_mc", 3);
Вы
создали три точки на рабочем месте с именами
start_mc,
end_mc и
control_mc. Теперь нам
нужно настроить каждую точку таким образом, чтобы их
можно было перетаскивать в любое место и
перерисовывать кривую согласно их новым позициям.
- Нам нужна функция
onPress для начала
перетаскивания, функция
onRelease для завершения перетаскивания и
функция onMouseMove
для выполнения перетаскивания и перерисовки кривой
линии. Добавьте следующий ActionScript под только
что введенным кодом.
function dragOn() {
// sets the mousemove handler of the movieclip
to 'dragMe'
this.onMouseMove = dragMe;
}
function dragOff() {
// clears the mousemove handler of the movieclip.
delete this.onMouseMove;
}
function dragMe() {
// moves the movieclip to the position of the
mouse
this._x = _root._xmouse;
this._y = _root._ymouse;
// calls the redraw function in the parent clip
ie the _root
this._parent.reDraw();
}
- Есть два варианта
присвоения этих функций управляющим элементам наших
фильмов. Вот самый очевидный выбор.
start_mc.onPress = dragOn;
end_mc.onPress = dragOn;
control_mc.onPress = dragOn;
Однако
существует способ, который в данном случае подойдет
нам больше. Во Flash MX метод
attachMovie имеет полезное свойство, которое
позволяет нам передавать объект при добавлении
нового фильма. При добавлении фильма все параметры
этого объекта копируются в новый фильм.
- Создадим объект,
содержащий всю информацию для
onPress, onRelease
и onMouseMove, которую
мы будем передавать в функцию
attachMovie, после чего все присвоения будут
производиться без нашего участия. Мы передаем объект
в attachMovie в виде
четвертого аргумента. Ниже жирным цветом выделен
добавленный код.
_quality = "LOW";
// set up an init object
initObj = {onPress:dragOn, onReiease:dragOff,
onReleaseOutside:dragOff,
useHandCursor:false};
// pass the init object when we call attachMovie
this.attachMovie("point" , "start_mc", 1,
initObj);
this.attachMovie("point", "end_mc", 2, initObj);
this.attachMovie("point", "control_mc", 3,
initObj);
Выполнив этот код, вы сможете перетаскивать ваши
фильмы.
- Теперь надо
определить функцию для рисования кривой. Прежде
всего, нужно вызвать
this.clear () для удаления уже имеющихся в
текущем месте линий или заполнений. Далее нужно
установить lineStyle
на черный цвет и нарисовать кривую из точки
start_mc в точку
end_mc через точку
control_mc, т.е.
контрольную точку кривой. Наконец, мы нарисуем
красную линию с интенсивностью 30 из точки
end_mc в точку
control_mc, и затем из
control_mc в
start_mc.
function redraw() {
this.clear();
this.lineStyle(2, 0x000000);
this.moveTo(start_mc._x, start_mc._y);
this.curveTo(control_mc._x, control_mc._y, end_mc._x, end_mc._y);
this.lineStyle(2, 0xFF5555, 30);
this.lineTo(control_mc._x, control_mc._y);
this.lineTo(start_mc._x, start_mc._y);
}
- Сохраните ваш
фильм в файле Curve with
anchor.fla и запустите его.
- Теперь вы можете
наблюдать эффект влияния контрольной точки на
траекторию кривой. Мы вернемся к точным
характеристикам кривой после знакомсвтва с заливками
и способами провести кривую через определенную точку.
Добавление заливки
Нарисовав
набор линий, создающий путь, мы тем самым создадим
заливку. Основным способом создания заливки является
вызов функции beginFill и
последующее рисование линий. Имейте в виду, что
lineStyle может быть не
указан. Тогда заливка будет прорисована без рамки. Ниже
приведен ActionScript для заливки квадрата.
this.moveTo(100, 100);
this.beginFill(0xFF0000);
this.lineTo(200, 100);
this.lineTo(200, 200);
this.lineTo(100, 200);
this.lineTo(100, 100);
this.endFill();
Мы можем
применить заливку, добавив вызов
lineStyle:
this.moveTo(100, 100);
this.lineStyle(2, 0x000000);
this.beginFill(0xFF0000);
this.lineTo(200, 100);
this.lineTo(200, 200);
this.lineTo(100, 200);
this.lineTo(100, 100);
this.endFill();
В словаре
ActionScript такая возможность не оговорена, но вы
можете обнулить все установки
lineStyle, один раз вызвав
lineStyle без параметров. Итак, мы можем залить
половину рамки.
this.moveTo(100, 100);
this.lineStyle(2, 0x000000);
this.beginFill(0xFF0000);
this.lineTo(200, 100);
this.lineTo(200, 200);
this.lineStyle();
this.lineTo(100, 200);
this.lineTo(100, 100);
this.endFill();
Таким
образом, функции lineTo
возвращают точки начала рисования на их исходные позиции.
Здесь endFill в любом
случае закрывает путь и проводит прямую линию обратно в
начальную точку. Можно убрать строку кода, и будет
по-прежнему выводиться красный квадрат.
this.moveTo(100, 100);
this.beginFill(0xFF0000);
this.lineTo(200, 100);
this.lineTo(200, 200);
this.lineTo(100, 200);
this.endFill();
Применив
заливку к curveTo, можно
рисовать квадрат со сглаженными углами, как показано
ниже:
this.moveTo(100, 100);
this.beginFill(0xFF0000);
this.curveTo(150, 50, 200, 100);
this.curveTo(250, 150, 200, 200);
this.curveTo(150, 250, 100, 200);
this.curveTo(50, 150, 100, 100);
this.endFill();
Градиентная заливка
Прежде чем
рассматривать градиентную заливку, мы вкратце рассмотрим
один из способов использования функции
beginGradientFill. Функция
beginGradientFill должна
иметь ряд параметров: необходимо передавать ей массивы,
содержащие различные используемые цвета, интенсивности
цветов, степени цветов, а также массив, содержащий
значения для координат X и
Y, наряду с шириной и
высотой градиента. Вот как выглядит функция
beginGradientFill в
словаре ActionScript:
myMovieClip.beginGradientFill(fillType, colors,
alphas, ratios, matrix);
С помощью
первого метода использования функции, аргумент
matrix обращается к
матрице 3x3, используемой для управления внешним видом
градиента, изгибами, размером, поворотом и переходом
градиента. На данный момент мы закончим рассмотрение
этого типа функции, так как она подробно описывается
далее в этой книге.
При более
простом способе передаваемый нами матричный объект
содержит ширину, высоту, начальные координаты
X и
Y и значение поворота. В нем также содержится
конечный параметр matrixType,
который должен быть всегда установлен на значение
box. При определении
ширины и высоты градиента указывается область, к которой
применяется градиент. Например, если мы определим
линейный градиент с X=100,
Y=100, шириной, равной 100
и высотой 100, градиент будет применен к области с 100
до 200 по оси X. Левая и
правая области рядом с градиентом будут отображаться
просто сплошным цветом.
Если вы
посмотрите на следующий код, позиция X градиента равна
120, а его ширина равна 60. Рисуемая фигура отображается
с позиции 100 по 200 по оси X,
при этом со 100 по 120 имеет место сплошной розовый цвет,
а с позиции 180 по 200 - сплошной оранжевый.
this.moveTo(100, 100);
colors = [0xFFE4E1, 0xFF6600];
alphas = [100, 100];
ratios = [23, 255];
matrix = {matrixType:"box", x:120, y:30, w:60, h:10,
r:0};
beginGradientFill("linear", colors, alphas, ratios,
matrix);
this.curveTo(150, 50, 200, 100);
this.curveTo(250, 150, 200, 200);
this.curveTo(150, 250, 100, 200);
this.curveTo(50, 150, 100, 100);
this.endFill() ;
Чтобы
лучше разобраться, как градиент заполняет область, можно
поэкспериментировать с различными значениями и
посмотреть, как они влияют на результат. Например, в
строке matrix в
ActionScript выше приравняйте r
к Math.PI. Вы увидите, что
градиент развернется на 180 градусов (pi радиан).
Также
интересно поменять местами X
и Y. Здесь мы переместили
радиальное заполнение на край нашей фигуры.
this.moveTo(100, 100);
colors = [0xFFE4E1, 0xff6600];
alphas = [100, 100];
ratios - [23, 255];
matrix = {matrixType:"box", x:100, y:30, w:100,
h:100, r:1};
beginGradientFill("radial", colors, alphas, ratios,
matrix);
this.curveTo(150, 50, 200, 100);
this.curveTo(250, 150, 200, 200);
this.curveTo(150, 250, 100, 200);
this.curveTo(50, 150, 100, 100);
this.endFill();
На первый
взгляд этот метод кажется сложным, но если разобраться в
том, как влияют числа на конечный результат, все
становится понятно. Вы можете создать функцию
enterFrame, которая будет
изменять эти значения. Немного поэкспериментировав,
можно добиваться довольно необычных эффектов.
Функция drawSquare
Представленные средства рисования очень полезны, но вам
наверняка потребуется создание функций, которые будут
комбинировать их возможности. Например, если нужно
нарисовать квадрат, вовсе не обязательно выполнять
каждый раз одну и ту же процедуру, заключающуюся в
установке цвета заливки и
lineStyle, перемещении точки рисования, рисовании
линий и заливки. Проще создать функцию, которой будут
передаваться все параметры для рисования квадрата, после
чего мы будем получать уже готовый результат.
При
создании таких функций первое, что нужно сделать - это
выделить все параметры, которые нам могут понадобиться.
Нам нужны координаты X и
Y, ширина, высота, цвет
заливки или ее интенсивность, цвет линий, ширина и
интенсивность.
MovieClip.prototype.drawSquare
= function(x, y, w, h, fillCol,
fillAlpha, lineCol, lineWeight, lineAlpha) {
};
Порядок
расположения параметров в функции очень важен. Например,
нам нужно просто отобразить квадрат без линии вокруг его
края. Вместо создания отдельной функции для рисования
квадрата без рамки мы можем передать все параметры
нового стиля в конце определения нашей функции и
пропускать их при вызове функции. В этом случае значения
lineCol,
lineWeight и
lineAlpha будут
отображаться как "undefined", и рамка не будет
прорисовываться.
Внутри
функции мы, во-первых, должны установить
lineStyle и цвет заливки,
а также переместить точку рисования на позицию (X, Y),
которую мы указали.
MovieClip.prototype.drawSquare
= function(x, y, w, h, fillCol,
fillAlpha, lineCol, lineWeight, lineAlpha) {
this.moveTo(x, y);
// check if a line colour has been specified
if (lineCol) {
this.lineStyle(lineWeight, lineCol, lineAlpha);
} else {
this.lineStyle();
}
this.beginFill(fillCol, fillAlpha);
};
Мы
проверяем, был ли передан аргумент
lineCol. Если это так, то
настраиваем lineStyle
соответствующим образом. Если нет, вызываем
lineStyle без параметров
для очистки lineStyle,
которая уже может быть настроена для данного фильма.
Другим
моментом, на котором основывается наше решение, является
то, передает ли пользователь аргумент
lineAlpha (будет ли
lineAlpha по умолчанию
нулем или нет). Мы, конечно, мы могли бы оградиться от
этого и изменить функцию по умолчанию на 100, если
lineCol передается, а lineAlpha
- нет. Осталось проверить, определена ли
lineAlpha, и предпринять
соответствующие действия.
MovieClip.prototype.drawSquare
= function(x, y, w, h, fillCol,
КfillAlpha, lineCol, lineWeight, lineAlpha) {
this.moveTo(x, y) ;
if (lineCol != undefined){
if (lineAlpha == undefined) {
lineAlpha = 100;
}
this.lineStyle(lineWeight, lineCol, lineAlpha);
} else {
this.lineStyle ();
}
this.beginFill(fillCol, fillAlpha);
};
Теперь нам
нужно прорисовать линии, чтобы отгородить область для
заливки. Мы начнем с левого верхнего угла и будем
двигаться вокруг квадрата по часовой стрелке,
прорисовывая линию каждого угла по очереди. Полезно
разобраться в том, как здесь указываются координаты.
- Левая верхняя:
(X, Y)
- Правая верхняя:
(X + ширина, Y)
- Правая нижняя: (X
+ ширина, Y + высота)
- Левая нижняя: (X,
Y + высота)
Теперь нам
просто нужно нарисовать линию для каждой из этих точек
по очереди и затем завершить заполнение.
MovieClip.prototype.drawSquare
= function(x, y, w, h, fillCol,
КfillAlpha, lineCol, lineWeight, lineAlpha) {
this.moveTo(x, y);
if (lineCol != undefined) {
if (lineAlpha == undefined) {
lineAlpha = 100;
}
this.lineStyle(lineWeight, lineCol, lineAlpha);
} else {
this.lineStyle();
}
this.beginFill(fillCol, fillAlpha);
// top right
this.lineTo(x+w, y);
// bottom right
this.lineTo(x+w, y+h);
// bottom left
this.lineTo(x, y+h);
// top left.
this.lineTo(x, y);
// end the fill
this.endFill();
};
Наконец,
нам нужно вызвать
_root.drawSquare (100, 100,
200, 100, 0xFF0000, 100, 0xdddddd, 20, 50) ;
Вы
заметите, что рамка, созданная с помощью
lineTo, выровнена по
центру края квадрата, и если бы у нашей рамки была
толщина в 20 пикселей, квадрат был бы на десять пикселей
шире с каждой стороны. Углы рамки закруглены, что может
быть излишним. Чтобы избежать этого, можно изменить
метод так, чтобы сначала прорисовывался квадрат для
рамки, а затем уже квадрат поверх него для заливки.
В конечном
счете, настройка метода определяется тем, как вы
собираетесь его использовать. Например, вы можете
указывать значения для левого, правого, нижнего и
верхнего краев вместо X,
Y, ширины и высоты (если
бы нужно было использовать его со значениями,
возвращаемыми функцией
Movieclip.getBounds). Это несложно.
MovieClip.prototype.drawSquare
= function(left, right, top, bottom,
КfillCol, fillAlpha, lineCol, lineWeight, lineAlpha)
{
this.moveTo(left, top);
if (lineCol != undefined) {
if (lineAlpha == undefined) {
lineAlpha = 100;
}
this.lineStyle(lineWeight, lineCol, lineAlpha);
} else {
this.lineStyle();
}
this.beginFill(fillCol, fillAlpha);
this.lineTo(right, top);
// top right
this.lineTo(right, bottom);
// bottom right
this.lineTo(left, bottom);
// bottom left
this.lineTo(left, top);
// top left
this.endFill();
};
И далее
вызов.
// a square with left edge at
100, right edge at 200, top at
// 200 and bottom at 300:
this.drawSquare(100,200,200,300,0xff0000,100)
Можно
применять другие цвета, интенсивность заполнения и т.д.
- все зависит от того, как вы планируете использовать
данный метод. Здесь нет правильного или неправильного
пути, вы просто конструируете его под ваши нужды.
Функция рисования
окружности
Теперь мы
рассмотрим метод, который позволяет рисовать окружности
с помощью API- рисования. Он более "хитрый". Многие
методы рисования окружностей зависят от использования
curveTo для формирования круга посредством рисования дуг.
Этот же метод основан на принципе закругления концов
линии. Если нарисовать линию, имеющую ширину в 100
пикселей, она может послужить основой для круга радиусом
100; функция Дэна рисует 0,15 пикселей от текущей точки
рисования, что формирует прекрасный круг.
Параметры,
передаваемые функции, являются координатами
X и
Y центра круга, а cWidth
- ширина окружности.
function drawCircle(x, y,
radius, cWidth) {
me = _root.createEmptyMovieClip("circle"+cnt,++cnt);
me.lineStyle(radius, 0x000000, 100);
me.moveTo(x, y);
me.lineTo(x, y+.15);
me.lineStyle(radius-cWidth, 0xffffff, 100);
me.moveTo(x, y);
me.lineTo(x, y+.15);
}
drawCircle(100, 100, 100, 20);
В этом
методе окружность расширена во внешнюю сторону от круга
и включена в общий радиус. Мы также создаем фильм, в
котором и рисуем круг.
Преобразуем эту функцию так, чтобы она работала как наши
предыдущие функции, и затем добавим возможность указания
цветов:
MovieClip.prototype.drawCircle
= function (x, y, radius, cWidth) {
this.lineStyle(radius, 0x000000, 100);
this.moveTo(x, y);
this.lineTo(x, y+.15);
this.lineStyle(radius-cWidth, 0xffffff, 100);
this.moveTo(x, y);
this.lineTo(x, y+.15);
};
drawCircle(100, 100, 100, 20);
Теперь
добавим цвета:
MovieClip.prototype.drawCircle
= function(x, y, radius,
КcWidth, innerCol, outerCol) {
this.lineStyle(radius, outerCol, 100);
this.moveTo(x, y);
this.lineTo(x, y+.15);
this.lineStyle(radius-cWidth, innerCol, 100);
this.moveTo(x, y);
this.lineTo(x, y+.15);
};
drawCircle(100, 100, 100, 10, 0xff0000, 0x000000);
Здесь
можно поэкспериментировать с порядком параметров, указав
в первую очередь цвет заливки и радиус, а потом уже
ширину окружности и ее цвет. Вы также можете изменить
функцию так, чтобы можно было изменять цвет заполнения и
рамки. Тогда можно нарисовать окружность без заливки.
Рисование кривой через
указываемую точку
Мы
настроили работу функции curveTo,
указали начальную точку, конечную точку и точку
управления. Но иногда полезнее проводить кривую через
указываемую точку, поэтому мы создадим функцию для
достижения этого результата.
Функция
curveTo во Flash создает то, что называется квадратным
уравнением для кривой Безье. Раньше кривые линии
рисовали, проставляя серию точек на кривой, а затем
воспризводя линии фильмов между этими точками, что
достигалось с помощью уравнения кривой Безье.
С помощью
этого уравнения указывается конечная точка, точка
контроля и начальная точка. Переменной в этом уравнении
является t, которая изменяет значения от 0 до 1 от
начала кривой и до ее конца. Мы можем вставлять
различные значения t для выяснения координат любой
отдельной точки на кривой.
Квадрат Безье
Таблица
3.1.
x |
Положение по оси X, через
которое проходит кривая за в t |
x0 |
Начальная точка кривой по оси
X |
x1 |
Контрольная точка прохождения
кривой по оси X |
X2 |
Конечная точка кривой по оси
X |
y |
Положение по оси Y, через
которое проходит кривая в t |
Y0 |
Начальная точка кривой по оси
Y |
Y1 |
Контрольная точка прохождения
кривой по оси Y |
Y2 |
Конечная точка кривой по оси
Y |
t |
Время |
Приведем
наше уравнение.
x = x0*t*t + x1*2*t*(1-t) +
x2*(1-t)*(1-t)
y = y0*t*t + y1*2*t*(1-t) + y2*(1-t)*(1-t)
Это
уравнение полезно для поиска отдельной точки, через
которую проходит кривая, в любой точке
t, от 0 до 1, т.е. от
начала и до конца кривой. Мы указываем точку начала,
точку контроля, конечную точку и
t для перехода к позиции (X, Y) (в которой кривая
проходит через эту точку).
Нам нужно
изменить функцию таким образом, чтобы можно было
указывать значения для точки начала (x0,y0), конца
(x2,y2) и отдельной точки кривой (X, Y) и перейти в
нужную точку контроля (x1,y1). Другими словами, нужно
переработать функцию так, чтобы она выглядела примерно
следующим образом.
x1= умноженное на x0, x2, x и
t
y1= умноженное на y0, y2, y и t
Это
разумное упрощение. Мы рассмотрим его для уравнения для
X и затем продублируем
наши результаты для Y, так
как уравнения идентичны. Прежде всего, нам нужно
получить значения x1 и
y1 в левой части уравнения.
Это мы сделаем взаимным уничтожением с обеих сторон:
x - x1*2*t*(1-t) = x0*t*t +
x1*2*t*(1-t) + x2*(1-t)*(1-t) - x1*2*t*(1-t)
Значения
плюс и минус x1*2*t*(1-t)
в правой части взаимно компенсируют друг друга:
x - x1*2*t*(1-t) = x0*t*t +
x1*2*t*(1-t) + x2*(1-t)*(1-t) х1*2*t(1-t)
x - x1*2*t*(1-t) = x0*t*t + x2*(1-t)*(1-t)
Теперь
вычтем x из обеих сторон:
x - x1*2*t*(1-t) - x = x0*t*t
+ x2*(1-t)*(1-t) - x
x - x1*2*t*(1-t) -x = x0*t*t + x2*(1-t)*(1-t)
- x
-x1*2*t*(1-t) = x0*t*t + X2*(1-t)*(1-t) - x
-1*x1*2*t*(1-t) = x0*t*t + x2*(1-t)*(1-t) - x
Умножим -1
на 2 в левой части уравнения:
x1* - 2*t*(1-t) = x0*t*t +
x2*(1-t)*(1-t) - x
Далее,
делим обе части уравнения на
(-2*t*(1-t) ):
x1*(-2*t*(1-t)) / (-2*t*(1-t))
=
=(x0*t*t + x2*(1-t)*(1-t) - x) / (-2*t*(1-t))
С левой
стороны имеем (-2*t*(1-t)) /
(-2*t*(1-t)), что дает 1, поэтому можно
произвести сокращение:
x1 (- 2 * t * (1 - t)) / ( - 2
* t * (1 - t) ) =
=(x0*t*t + x2*(1-t)*(1-t) - x) / (-2*t*(1-t))
x1 = (x0*t*t + x2*(1-t)*(1-t)-x) / (-2*t*(1-t))
Мы
получили уравнение для нахождения
x1, а уравнением для y1
будет следующее:
y1 = (y0*t*t +
y2*(1-t)*(1-t) - y) / (-2*t*(1-t))
Теперь
можно использовать полученные результаты для создания
функции, которая будет передавать значения
x,
y, x0,
y0,
x2, y2 и
t для рисования кривой из
(x0,y0) в (x2,y2), проходящей через точку (x,y) в точке
на прямой, указываемой t.
Истинная
роль t станет вам понятнее,
когда вы начнете вставлять числа, которые будут влиять
на форму кривой. Переименуем значения на a, b и c в
нашей функции, для простоты. Мы будем вызывать функцию
aToBThroughC чтобы
выяснить значение каждой из величин. Приведем уравнение
с добавленными a,
b и
c:
controlX = (ax*t*t + bx*(1-t)*(1-t)
- cx) / (-2*t*(1-t))
controlY = (ay*t*t + by*(1-t)*(1-t) - cy) /
(-2*t*(1-t))
Теперь
перейдем к самой функции. Для простоты предположим, что
мы настроили lineStyle.
MovieClip.prototype.aToBThroughC =
function (ax, ay, bx, by, ex, cy,t) {
var controlX = (ax*t*t+bx*(1-t)*(1-t)-cx)/(-2*t*(1-t));
var controlY = (ay*t*t+by*(1-t)*(1-t)-cy)/(-2*t*(1-t));
this.moveTo(ax, ay);
// for security
this.curveTo(controlx, controly, bx, by);
};
Для
проверки работы введите следующий код.
this.lineStyle (27, 0xff0000);
this.aToBThroughC (100, 100, 250, 100, 200, 350,
0.5);
Сохраните
ваш файл под именем
AtoBthroughC.fla и запустите его. Мы создали
сильно изогнутую кривую
Чтобы
показать корректность работы нашей функции, мы откроем
созданный нами ранее файл curve
with anchor.fla, и изменим его так, чтобы третья,
перетаскиваемая, точка была точкой, через которую
проходит кривая, а не точкой контроля. Измененный код
выделен жирным шрифтом:
_quality = "LOW";
MovieClip. prototype. aToBThroughC = function (ax,
ay, bx, by, ex, cy, t) {
var controlX = (ax*t*t+bx*(1-t)*(1-t)-cx)/(-2*t*(1-t));
var controlY = (ay*t*t+by*(1-t)*(1-t)-cy)/(-2*t*(1-t));
this.moveTo(ax, ay);
// for security
this.curveTo(controlx, controly, bx, by);
};
initObj = {onPress:dragOn,
onRelease:dragOff,onReleaseOutside:dragOff,
КuseHandCursor: false};
this.attachMovie("point", "start_mc", 1, initObj);
this.attachMovie("point", "end_mc" , 2, initObj);
this.attachMovie("point", "control_mc", 3, initObj);
function dragOn() {
this.onMouseMove = dragMe;
}
function dragOff() {
delete this.onMouseMove;
}
function dragMe() {
this._x = _root._xmouse;
this._y = _root ._ymouse;
this._parent.reDraw();
}
function reDraw() {
this.clear();
this.lineStyle(2, 0xFF5555, 100);
this.aToBThroughC(start_mc._x, start_mc._y, end_mc._x, end_mc._y,
Кcontrol_mc._x, control_mc._y, 0.5);
}
Пример
3.1.
Вы увидите,
что при перетаскивании точек кривая теперь всегда
проходит через третью точку.
Задав
другое значение t, вы
увидите, что кривая изменила форму.
this.aToBThroughC(start_mc._x,
start_mc._y, end_mc._x, end_mc._y,
Кcontrol_mc._x, control_mc._y, 0.1);
}
Переменная
t может иметь любое
значение между 0 и 1, поэтому здесь может быть огромное
количество кривых, которые будут проходить через нашу
точку c - по одной для каждого значения между 0 и 1.
Создание простого
приложения
В этом
параграфе мы создадим несложное приложение, в котором
можно будет рисовать изображения и сохранять результаты
для дальнейшего просмотра. Задача разбивается на 3 этапа.
- Обеспечение
пользователю возможности рисования с помощью мыши.
- Изучение работы
общих объектов для хранения информации.
- Комбинирование
первого и второго для возможности сохранения
рисунков и открытия их в дальнейшем.
Вы не
найдете ничего нового в этом приложении, однако оно
поможет разобраться вам в методологии и поможет изучить
работу общих объектов. Это новая мощная возможность
Flash MX.
Рисование
Простое
приложение для рисования состоит из тридцати строк кода.
Вот принцип его работы:
- При щелчке
пользователем текущая точка рисования перемещается
на позицию мыши. Записываются начальная и текущая
точка движения мыши. Далее рисуется линия из точки в
первом кадре в текущую точку.
- Когда
пользователь отпускает кнопку мыши, линия
завершается, прекращается запись позиции мыши и
рисование.
Для этого
нам нужны три функции: одна для обработки щелчка
пользователя, другая для отпускания пользователем кнопки
мыши, и третья для записи позиции указателя мыши при
нажатой кнопке.
function drawOn() {
this.lineStyled (0x000000);
this.moveTo(this._xmouse, this._ymouse);
this.onMouseMove = addPoint;
}
function addPoint() {
this.lineTo(this._xmouse, this._ymouse);
}
function drawOff() {
delete this.onMouseMove;
}
this.onMouseDown = drawOn;
this.onMouseUp = drawOff;
С помощью
onMouseDown
устанавливается lineStyle
и точка рисования перемещается туда, где находится мышь,
а также onMouseMove
устанавливается в addPoint.
В тот
момент, когда пользователь двигает мышь, рисуется линия
к новой позиции указателя мыши. Когда пользователь
отпускает кнопку мыши, управляющий элемент
onMouseMove удаляется.
Введите
код в панель Actions нового фильма, сохраните его в
файле doDrawing.fla и
запустите его.
Мы
получили простой интерфейс рисования, состоящий всего
лишь из тридцати строк.
Теперь
рассмотрим возможность сохранения и воспроизведения
создаваемых рисунков. Для этого нам нужно помещать
координаты, соответствующие рисунку, в массив. Нам также
нужно будет записывать передвижение пользователем
указателя мыши без рисования линии. С этой целью мы
введем специальное значение "break", которое будет
означать, что карандаш не задействован и перемещен в
какое-либо место. Добавленный код выделен жирным шрифтом.
function drawOn() {
this.lineStyle(1, 0x000000);
this.moveTo(this._xmouse, this._ymouse);
// add the new point to the array
tempArr.push((x:_root._xmouse, у:_root._ymouse});
this.onMouseMove = addPoint;
}
function addPoint() {
// add the new point to the array
tempArr.push ({x:_root._xmouse, у:_root._ymouse});
this.lineTo(_root._xmouse, _root._ymouse);
}
function drawOff() {
// the pen is lifted so we add the value "break" to the array
tempArr.push("break");
delete this.onMouseMove;
}
tempArr = [];
this.onMouseDown = drawOn;
this.onMouseUp = drawOff;
Мы создали
массив tempArr для
хранения всех координат в объектах с параметрами
X и
Y.
Сохраните
ваш фильм в файле
doAndStoreDrawing.fla. Если вы запустите фильм и
выберете команду меню Debug > List Variables, вы увидите
все координаты нарисованных вами точек в окне Output.
Перед тем,
как рассматривать восстановление рисунка из этих
координат, изучим использование общих объектов для
хранения переменных.
Общие объекты
Общие
объекты позволяют хранить данные локально, на жестком
диске пользователя.
Грубо
говоря, общие объекты являются своего рода cookies для
Flash. Они позволяют нам локально хранить любые данные
наших Flash-фильмов, и работают в любых браузерах, равно
как и локальный Flash Player.
Итак,
общие объекты могут быть использованы для хранения
данных, представляющих собой рисунок, созданный в
приложении при помощи рисования API. Общие объекты могут
также использоваться в онлайн-формах для запоминания
предыдущих записей пользователей и автозавершения их
имен и адресов, и т.д., так можно сделать, например,
отдельную страницу на сайте интернет-магазина,
основанную на предыдущих покупках пользователя.
Использование общего
объекта
Мы не
будем разбираться в сложных деталях использования общих
объектов, мы изучим создание общего объекта и
обеспечение его работы. Если вы хотите узнать больше об
общих объектах и их применении, вам стоит обратиться к
сайту по этой ссылке: www.macromedia.com/support/flash/action_scripts/local_shared_object.
Мы начнем
с очень простого приложения.
- Создайте фильм,
добавьте на рабочее место статическое текстовое поле
и введите в него текст visit
number.
- Теперь добавьте
динамическое текстовое поле и назовите его инстанс
именем visited.
Мы
используем общий объект для хранения числа
просмотров страницы. Это элементарное приложение, но
оно содержит все необходимое для продолжения работы
с приложением рисования.
- Теперь создайте
новый слой script для ввода кода в наше элементарное
приложение.
- Прежде всего, мы
получаем sharedObject
с помощью
SharedObjec.getLocal. Это действие вернет
объект, если он уже был создан, а если он еще не
создавался, он будет создан. Введите следующий код
на панели Actions:
mySharedObj =
SharedObject.getLocal ("visits");
Теперь
мы имеем копию общего объекта в переменной
mySharedObj. mySharedObj
- это объект, которому вы можете присваивать
параметры, как любому другому объекту. Это могут
быть переменные, массивы и т.д. Переданный параметр
"visits" является именем общего объекта, что
позволяет иметь несколько общих объектов для каждого
проекта.
- Теперь мы будем
увеличивать переменную visitCount и вставлять ее
значение в созданное нами текстовое поле. Добавьте
следующие две строки ActionScript.
mySharedObj =
SharedObject.getLocal("visits");
//we increment a variable "visitCount" inside
"data" where
//all the data resides in a shared object
mySharedObj.data.visitCount++;
visited.text = mySharedObj.data.visitCount;
- Наконец, мы
обновляем объект sharedObject,
синхронизируя его, если нужно, с использованием
метода flush, который
возвращает все из mySharedObj
обратно в текущий общий объект. Добавьте последнюю
строку сценария в панель Actions.
mySharedObj =
SharedObject.getLocal("visits");
mySharedObj.data.visitCount++;
visited.text = mySharedObj.data.visitCount;
mySharedObj.flush();
- Сохраните ваш
фильм в файле sharedObj
basics.fla. Если вы запустите фильм несколько
раз, вы увидите, что число будет увеличиваться (если
вы не игнорировали запрос безопасности, который мы
будем рассматривать и обрабатывать в реальных
условиях).
Сохранение данных
рисунка
На примере
рассмотренного приложения становится ясно, что
сохранение рисунков в приложении заключается просто в
сохранении массива данных в общем объекте.
Мы
создадим интерфейс, содержащий все локально сохраненные
изображения, и затем обеспечим возможность либо
рисования нового рисунка, либо просмотра старого.
Приведем
структуру того, что мы хотим сделать:
Главная
страница:
Возможности:
Панель,
содержащая список рисунков из
sharedObject с полосой прокрутки.
Опции:
Выбрать
имеющийся рисунок - перейти к странице перерисовки.
Создать
новый рисунок - перейти к странице создания.
Страница перерисовки:
Возможности:
Перерисовка прежнего рисунка.
Опции:
Вернуться
на главную страницу.
Страница создания:
Возможности:
Рисование
мышью.
Опции:
Сохранить
текущий рисунок - сохранить рисунок в общем объекте -
перейти на главную страницу.
Вернуться
- возврат на главную страницу.
У нас есть
три основных задачи: меню, рисование и перерисовка. Мы
можем предположить, какие функции нам потребуются для
создания приложения:
Таблица
3.2.
Имя
функции |
Цель |
init |
Установка некоторых основных
управляющих элементов кнопок и вызов
drawPanel. |
drawPanel |
Получение данных из объекта
sharedObject и
отключение кнопки возврата и сохранения.
Вызов listDrawings
и initScrollers.
|
listDrawings |
Добавление текстового поля в
меню. |
initScrollers |
Установка полос прокрутки для
текстового поля. |
initDraw |
Выключение меню, включение
кнопки возврата и сохранения и установка
управляющих элементов событий
onMouseDown и
onMouseUp для
реализации рисования. Настройка
lineStyle и
создание временного массива для хранения
точек. |
drawOn |
Начало рисования линии,
установка
onEnterFrame и
addPoint. |
addPoint |
Рисование линии и добавление
текущей точки в массив. |
drawOff |
Выключение
addPoint. |
save |
Добавление текущего массива
точек в sharedObject
и вызов backToMain. |
backToMain |
Удаление всех событий кадра и
мыши на _root.
Вызов drawPanel. |
redraw |
Отключение меню и включение
кнопки возврата. Установка
onEnterFrame. |
drawNextPoint |
Рисование следующей точки в
текущем рисунке. |
Полнофункциональное
приложение рисования
Перейдем к
работе над полнофункциональным приложением рисования и
сохранения рисунков.
- Прежде всего
займемся основой будущего приложения. Создайте два
символа кнопок, которые будут использоваться для
сохранения наших рисунков и для возврата к временно
сохраненным рисункам. Вы сами теперь можете создать
кнопки. На наших кнопках мы просто написали "save" и
"return", причем настроили текстовое поле на
использование системных шрифтов. Эти кнопки
расположены в правом нижнем углу главного рабочего
места.
- Инстанс кнопки
Save назовите saverButton
с помощью Property inspector, а инстанс кнопки
Return - backButton.
- Создайте символ
фильма и назовите его
listingsPanel. В фильме создайте новый слой с
именем textfield и
добавьте динамическое текстовое поле на этот слой. С
помощью Property inspector назовите текстовое поле
lista.
- Создайте новый
слой с именем background
и нарисуйте прямоугольник (светло-серого цвета,
#F5F5F5) значительно больший динамического
текстового поля, которое вы только что создали. Под
ним я нарисовал другой прямоугольник, который
намного темнее первого (#E8E8E8), чтобы выделить его.
Затем перетащите слой
background под слой
textfield.
- Все еще находясь
в фильме listingsPanel,
создайте слой с именем new
под слоем textField.
Внутри слоя new создайте символ кнопки и присвойте
ему имя инстанса newButton
с помощью Property inspector. Как видите, мы просто
добавили необходимый текст в нашу кнопку и поместили
его внизу динамического текстового поля.
- Создайте два
новых слоя с именами upscroll
и downscroll. Создайте
символ кнопок прокрутки вверх и вниз. Я просто
добавил текстовые поля с символами "больше" и "меньше".
Разместите символ прокрутки вверх на слое
upscroll и с помощью
Property inspector дайте ему имя инстанса
upScroll. Расположите
второй символ кнопки на слое
downscroll и назовите его инстанс именем
downScroll. Здесь
вполне понятно, какие действия выполняют эти кнопки.
Мы расположили наши кнопки под текстовым полем слева
от кнопки new.
Можно
было бы использовать для этого программный компонент
прокрутки, но компоненты мы будем рассматривать в
следующей лекции и пока не будем забегать вперед.
Создание фильма почти завершено, осталось только
добавить в него заголовок. Мы создали новый слой с
именем title и
добавили статическое текстовое поле со словами "List
of drawings".
- Мы закончили
создание фильма. Теперь перетащите его на рабочее
место и назовите инстанс
listingsPanel.
Сейчас
мы поочередно разберем функции, использованные нами
выше, с объяснением некоторых моментов.
function drawPanel() {
// turn off the back button and the save button
backButton._visible = false;
saverButton._visible = false;
// make the menu panel visible
this.listingsPanel._visible = true;
// retrieve the shared object
myPictureObj = SharedObject.getLocal("pictures");
// list any drawings
listDrawings(myPictureObj.data.arrays);
// check if we need to turn on the scrollers
initScrollers();
}
Здесь
мы инициализировали общий объект и сохранили его в
myPictureObj, а также
настроили отображение некоторых элементов рабочего
места. Далее мы вызовем
listDrawings для просмотра
myPictureObj и
выяснения количества находящихся там рисунков. Мы
запишем наши массивы точек в массив с именем
arrays объекта
sharedObject и затем
передадим этот массив функции
listDrawings. Внутри массива
arrays будет
находиться один массив для каждого рисунка. Вместо
создания кнопки для каждого рисунка мы используем
функцию asfunction,
позволяющую представлять фрагмент текста HTML в виде
ссылки на функцию ActionScript. Вот как это
делается.
asfunction:functionName,
parameter;
- Мы будем
использовать эту функцию для вызова
_root.redraw и
передачи номера записи массива.
function
listDrawings(pictureArray) {
listingsPanel.lista.htmlText = "";
// loop through the picture array
for (var i = 0; i<pictureArray.length; i++) {
// for each entry in the picture array add another line
// to the text in the textfield
var nexta = "<A HREF='asfunction:_root.redraw,"+i+"'>"+"
Drawing Number "+(i+1)+"</A><b/>";
listingsPanel.lista.htmlText += nexta;
}
}
- Теперь создадим
вторую функцию, которая будет вызываться
drawPanel. Она просто
настроит отображение кнопок прокрутки в зависимости
от того, где мы находимся - вверху или внизу
текстового поля, а также от того, может ли поле
вместить все содержащиеся в нем строки текста. С
помощью with мы
перейдем внутрь фильма
listingsPanel. Параметр
visible инстанса
кнопки downScroll
устанавливаем на значение on,
если значение прокрутки меньше, чем максимально
возможное значение прокрутки. Этот же параметр
устанавливается на значение
on, если текстовое поле было полностью
прокручено.
function initScrollers() {
with (this.listingsPanel) {
downScroll._visible = lista.scroll<lista.maxscroll;
upScroll._visible = lista.scroll>l;
}
}
Наш
список будет выглядеть примерно так.
- Теперь добавим
уже знакомые вам функции для рисования. Сначала
добавим initDraw -
функцию, которая будет вызываться при щелчке
пользователя на кнопке new:
function initDraw() {
// turn on the back and save buttons
backButton._visible = true;
saverButton._visible = true;
// turn off the menu
_root.listingsPanel._visible = false;
// create an array for the points
tempArr = [];
_root.lineStyle(1, 0x000000);
// set up out mouse event handlers
_root.onMouseDown = drawOn;
_root.onMouseUp = drawOff;
}
Все
это вам уже знакомо: сначала настраивается
отображение элементов рабочего места так, чтобы были
видны кнопки back и
save, затем
настраивается lineStyle
и создается массив для наших точек.
function drawOn() {
this.moveTo(this._xmouse, this._ymouse);
tempArr.push({x:this._xmouse, y:this._ymouse});
this.onMouseMove = addPoint;
}
function addPoint() {
tempArr.push((x:this._xmouse, y:this._ymouse});
this.lineTo(this._xmouse, this._ymouse);
}
function drawOff() {
tempArr.push("break");
delete this.onMouseMove;
}
- Затем создадим
функцию, позволяющую сохранять рисунок. Она будет
вызываться при нажатии кнопки
save и будет сохранять
tempArr в общем объекте. Перед сохранением
массива мы удаляем последний объект, так как объект
точки записывается в массив, когда пользователь
нажимает кнопку для ее сохранения (когда происходит
onMouseDown). Если не
делать этого, можно столкнуться с проблемами
потерянной строки в конце рисунка.
function save() {
// remove final element
tempArr.pop();
if (!myPictureObj.data.arrays) {
// if our array for pictures has not yet been created,
// create it
myPictureObj.data.arrays = [];
}
// put the current picture array into the array
// containing all pictures
myPictureObj.data.arrays.push(tempArr);
// update the shared object
myPictureObj.flush();
// return to the menu screen
_root.backToMain();
}
Обратите внимание, что здесь сначала проверяется
существование массива arrays,
и если массив не существует, то он создается. Затем
tempArr помещается в
конец этого массива и вызывается
flush для сохранения
его в объекте. После этого вызывается
backToMain для
удаления всех управляющих элементов событий, а также
весь рисунок в фильме, перед возвращением к началу.
function backToMain() {
delete _root.onEnterFrame;
delete _root.onMouseMove;
delete _root.onMouseDown;
delete _root.onMouseUp;
_root.clear();
_root.drawPanel();
}
- Теперь приступим
к созданию последней части - функции перерисовки.
Здесь нам нужны две функции:
redraw для начала перерисовки и
drawNextPoint для
покадрового продолжения. Перерисовка осуществляется
посредством прохода через массив точек и рисования
линии от предыдущей точки к текущей в каждом кадре.
С помощью break
осуществляется переход к точке без рисования линии.
function redraw(num) {
backButton._visible = true;
this.listingsPanel._visible = false;
// set count to zero, used to keep track of current point
this.count = 0;
// the array which we are redrawing
this.currentArr = myPictureObj.data.arrays[num];
this.lineStyled (0x000000);
this.moveTo( this, currentArr [0] .x, this .currentArr [0] .y);
// every frame we draw the next point
this.onEnterFrame = drawNextPoint;
}
- Это начальная
функция, получающая массив нужного рисунка и
перемещающая точку рисования на первую позицию в
массиве. Она сбрасывает переменную count, которая
будет использоваться для учета точки выхода из
массива, и затем настраивает функцию
enterFrame на
drawNextPoint.
function drawNextPoint() {
this.count++;
// nextEntry is the next point object in the array
var nextEntry = this.currentArr[this.count];
if (nextEntry != "break") {
this.lineTo(nextEntry.x, nextEntry.y);
} else {
// if the nextEntry is "break", we increment count
// and move to the next point.
this.count++;
var nextEntry = this.currentArr[this.count];
this.moveTo(nextEntry.x, nextEntry.y);
}
if (this.count>this.currentArr.length) {
// we have drawn all available points
delete this.onEnterFrame;
}
}
Таким
образом, с помощью
drawNextPoint мы увеличиваем значение нашего
счетчика и устанавливаем переменную
nextEntry для указания
на следующую запись в массиве. Мы проверяем, равно
ли это значение "break" и, если это так, увеличиваем
count и переходим к следующей точке в массиве с
помощью moveTo. Если
оно не равно "break", рисуем линию к новой точке.
Наконец, проверяем, достигнут ли конец массива; если
да, удаляем функцию
onEnterFrame и завершаем выполнение действия.
- Последняя - это
функция инициализации, которая будет просто
устанавливать управляющие элементы для кнопок,
настраивать textField
в listingsPanel на
HTML-текст и рисовать панель в первый раз.
function init() {
this.saverButton.onPress = this.save;
this.backButton.onPress = this.backToMain;
with (this.listingsPanel) {
newButton.onRelease = this.initDraw;
// enable html text in the textfield
lista.html = true;
downScroll.onPress = function() {
// scroll the textfield down
lista.scroll++;
// check if the scrollers should still appear
_root.initScrollers();
};
upScroll.onPress = function() {
lista.scroll--;
_root.initScrollers();
};
}
drawPanel();
}
- Нам также нужен
вызов этой функции для инициализации приложения
init ()
Весь код
программы находится в файле
drawingapp.fla.
Безусловно,
можно реализовать все это с помощью готового кода, но
знать, как все это выглядит в деталях в среде разработки,
очень полезно.
|