Опубликован: 07.11.2006 | Уровень: специалист | Доступ: свободно
Лекция 10:

Анимация и интерактивность Drawing API

Превращения фрукта

В этом упражнении мы создадим промежуточные сцены для движения и цвета между двумя рисунками API. Откройте apples_oranges.swf (на компакт-диске) и щелкните на фрукте. Он будет превращаться из яблока в апельсин при каждом щелчке пользователя. Не впечатляет? Но, между прочим, этот объект был создан полностью с помощью рисования API, без рисунков на рабочем столе или в Library!

Для начала создадим законченные рисунки.

  1. Создайте новый фильм с именем apple_oranges.fla. В этом фильме мы будем работать только с кодом, поэтому понадобится только панель Actions. Начните с добавления следующего кода в первый кадр слоя по умолчанию.
    this.createEmptyMovieClip("drawObj", 0);
    
    apple = {};
    apple.anchorPoints = [[245,155], [180,50], [245,155], [247,70], [265,60], [262,150],   К[335,130], [354,220], [256,320], [163,263], [161,158], [262,150]];
    apple.controlPoints = [[250,50], [150,100], [270,115], [255,70], [290,110], [290,109],   К[373,153], [314,358], [211,357], [128,196], [200,118]];
    apple.colors = [[0,0x009900], [2,0x663300], [5,0x990000]];
    
    orange = {};
    orange.anchorPoints = [[232,152], [192,120], [227,153], [307,103], [227,154],     К[210,154], [260,148], [350,150], [380,338], [140,364], [133,175], [210,154]];
    orange.controlPoints = [[212,58], [130,123], [246,102], [276,154], [200,104],     К[230,124], [285,125], [450,204], [288,480], [38,263], [178,140]];
    orange.colors = [[0,0x333300], [2,0x335500], [5,0xFF9933]];
    
    shapes = [apple, orange];
    shapeNum = 0;
    Пример 9.1.

    В первой строке мы создаем фильм, который будет содержать наши рисунки. Затем создаются два новых объекта, названные "apple" и "orange", т.е. яблоко и апельсин. Оба они имеют точки контроля, фиксированные точки и присвоенные цвета (все числа были сгенерированы другим SWF). Для цветов мы сохраняем цвета и позиции индексов фиксированных точек, на которых начинается цвет. Затем мы располагаем эти два объекта в массиве с именем shapes и устанавливаем текущую позицию индекса на 0 для яблока с помощью shapeNum.

    Теперь мы создаем функцию, носящую соответствующее название drawShape и рисующую фигуры.

    drawShape = function (shape) { 
      drawObj.clear(); 
      drawObj.lineStyle(2, 0, 100); 
      currentColor = 0;
      drawObj.moveTo(shape.anchorPoints[0] [0], s
        hape.anchorPoints[0] [1]; 
      for (var i = 0; i<shape.anchorPoints.length-1; i++) { 
        if (shape.colors[currentColor] [0] == i) {
          drawObj.endFill() ;
          drawObj.beginFill(shape.colors[currentColor] [1] , 100);
          drawObj.moveTo(shape.anchorPoints[i] [0], 
            shape, anchor Points [i] [1] ) ;
          currentColor++;
        }
        drawObj.curveTo(shape.controlPoints [i] [0], 
          shape.controlPoints [i] [1], 
        Кshape.anchorPoints [i+1] [0], shape.anchorPoints [i+1] [1]); 
      }
    };

    Большая часть этого кода совершенно ясна, если вы разбираетесь в массивах, поэтому разберемся досконально, как работают ссылки. Переменная shape будет содержать ссылку на один из наших объектов - яблоко или апельсин. Следовательно, shape.anchorPoints будет ссылаться на параметр anchorPoints выделенной фигуры. anchorPoints для каждого объекта содержит двумерный массив. Каждая позиция индекса anchorPoints содержит координаты _x и _y: значение _x содержится в индексе 0, а значение _y содержится в индексе 1. Следовательно, если shape содержит наш объект apple, то shape.anchorPoints[1] [1] будет ссылаться на координату _y второго индекса массива anchorPoints объекта apple, которым является 50.

    В функции drawShape мы сначала удаляем фильм, сбрасываем переменные lineStyle и currentColor и переходим на нашу начальную позицию. Мы проходим через весь массив anchorPoints для нашего текущего объекта и вызываем метод curveTo для каждого из них. Выражение if проверяет, находимся ли мы на позиции индекса, где должен начаться наш следующий цвет. Если это так, мы заканчиваем предыдущую заливку и начинаем новую. Я выяснил, что перемещение "карандаша" на текущую позицию после вызова beginFill устранило некоторые ошибки заполнения, имевшиеся при отсутствии этой команды.

  2. Теперь введите последний фрагмент кода.
    clickCatch = function () {
      shapeNum = shapeNum+1>1 ? 0 : shapeNum+1; 
      drawShape(shapes[shapeNum]);
    };
    drawShape(apple);
    drawObj.onMouseDown = clickCatch;

    При щелчке мышью будет вызываться функция clickCatch. Она увеличивает shapeNum (мы используем здесь условный оператор на тот случай, если нам понадобится добавить дополнительные фигуры впоследствии) и вызывает нашу функцию drawShape. После этого рисуется начальная фигура, и drawObj настраивается на регистрацию щелчков мыши для изменения рисунка. Запустите фильм, чтобы отобразить яблоко, затем щелкните мышью, и появится апельсин. Все это реализовано с помощью вышеуказанного кода.



    Мы нарисовали фрукты с помощью рисования API. Хорошо было бы анимировать это изменение. Попробуем реализовать такую возможность.

  3. Добавьте эти три строки в верхнюю часть вашего текущего кода, сразу после вызова createEmptyMovieClip.
    tweenRate = 24;
    currentPos = {};
    currentPos.controlPoints = [];
    currentPos.anchorPoints = [];
    currentPos.colors = [];

    Здесь мы просто инициализируем переменные. tweenRate является длиной нашей анимации, связанной с частотой кадров. currentPos будет содержать всю информацию о том, на какую сцену в данный момент указывают наши фиксированные точки, точки контроля и цвета.

  4. Еще одна функция представлена на ваше утверждение. Введите этот код сразу под функцией drawShape.
    convertToRGB = function(c) { 
    var col = c.toString(16); 
    while (col.length < 6) { 
      col = "0" + col;
    }
    var r = parseInt(col.substr(0,2), 16); 
    var g = parseInt(col.substr(2,2), 16); 
    var b = parseInt(col.substr(4,2), 16); 
    return {r:r, g:g, b:b} 
    }

    Функция convertToRGB выполняет преобразование в цветовую систему RGB. Она будет получать значение цвета и возвращать значение для каждого из трех цветов - красного, зеленого и синего. Эти значения понадобятся нам для плавного изменения цветов объектов.

    c - это значение цвета, получаемое функцией. Мы преобразовываем его в строку с помощью метода toString, с использованием шестнадцатеричной системы. Выражение while нужно для проверки того, что в строке содержится шесть символов. Например, если мы отправляем функции значение 255 (чистый синий), строкой, возвращенной c.toString(16) будет FF. Нам необходимо, чтобы строка имела вид 0000FF для шестнадцатеричного представления цвета, поэтому в выражении while мы добавляем необходимые нули.

    Далее, мы извлекаем три подстроки из c. r будет содержать значение красного цвета, хранимое в первых двух символах строки, g будет содержать значение зеленого цвета в третьем и четвертом символах строки, а b будет содержать последние два символа, представляющие собой значение для синего цвета. parseInt преобразует строку в число, поэтому мы отправляем функции наши подстроки, а также основание системы исчисления (16). Эти конечные значения возвращаются в отдельном объекте, содержащем три параметра: r, g и b.

  5. Следующая функция является местом инициализации преобразования. Введите этот код сразу после функции convertToRGB.
    startShift = function (shape) { 
      endShape = shape; 
      delete drawObj.onMouseDown; 
      tweenCount = 0; 
      increment = {}; 
      increment.anchorPoints = []; 
      increment.controlPoints = []; 
      increment. colors = []; 
      for (var i = 0; i<shape.colors.length; i++) {
        var newColor = convertToRGB(shape.colors[i] [1] );
        var oldColor = convertToRGB(currentPos.colors[i] [1] ) ;
        var r = (oldColor.r - newColor.r)/tweenRate;
        var g = (oldColor.g - newColor.g)/tweenRate;
        var b = (oldColor.b - newColor.b)/tweenRate;
        increment.colors.push({r:r, g: g, b:b});
      }
      for (var i = 0; i < shape.anchorPoints.length; i++) { 
      var anchor1 = (shape.anchorPoints[i] [0] - 
      КcurrentPos.anchorPoints [i] [0])/tweenRate; 
      var anchor2 = (shape.anchorPoints[i] [1] -
      КcurrentPos .anchorPoints [i] [1])/tweenRate; 
      var controll = (shape.controlPoints[i] [0] -
      КcurrentPos.controlPoints[i] [0])/tweenRate; 
      var control2 = (shape.controlPoints[i] [1] -
      КcurrentPos.controlPoints [i] [1])/tweenRate; 
      increment.anchorPoints.push([anchor1, anchor2]); 
      increment.controlPoints.push([control1, control2]);
      }
      drawObj.onEnterFrame = moveLines;
    };
    Пример 9.2.

    Подготовительные действия завершены. Собственно вся подготовка и заключается в этой функции. startShift отсылается при любой конечной фигуре, а это значение хранится в endShape. После этого удаляется mouseDown, чтобы пользователь не мог щелкать мышью во время трансформации (не беспокойтесь, позднее мы вернем все на свои места).

    Далее: increment необходим для хранения значений, которые понадобятся нам для настройки текущей фигуры в каждом кадре. Мы проходим через все цвета, точки контроля и фиксированные точки currentPos (сейчас мы опишем currentPos ), находим разность между этими значениями и конечными значениями, затем делим эту разность на tweenRate. Это даст нам значение increment, нужное для настройки currentPos в каждом кадре для преобразования одной фигуры в другую. Эти значения помещаются в соответствующие массивы объекта increment.

    Наконец, нужно настроить наш объект на вызов в каждом кадре функции moveLines. Разумеется, нам понадобится написать эту функцию.

  6. Введите этот код сразу после предыдущей функции.
    moveLines = function () {
      for (var i = 0; i<endShape.anchorPoints.length; i++) {
        currentPos.controlPoints[i] [0] += 
          increment.controlPoints [i] [0];
        currentPos.controlPoints[i] [1] += 
          increment.controlPoints [i] [1] ;
        currentPos.anchorPoints[i] [0] += 
          increment.anchorPoints[i] [0];
        currentPos.anchorPoints[i] [1] += 
          increment.anchorPoints[i] [1];  
      }
      for (var i = 0; i < endShape.colors.length; i++) {
        var col = convertToRGB(currentPos.colors[i] [1]); 
        col.r = Math.round(col.r-increment.colors[i] .r); 
        col.g = Math.round(col.g-increment.colors[i] .g); 
        col.b = Math.round(col.b-increment.colors[i] .b); 
        currentPos.colors [i] [1] = col.r << 16 | col.g << 8 | col.b;
      }
      drawShape(currentPos);
      tweenCount++;
      if (tweenCount>tweenRate) {
        delete drawObj.onEnterFrame;
        drawobj.onMouseDown = clickCatch;
        drawShape(endShape); 
      }
    };

    Эта функция непосредственно обеспечивает трансформацию, хотя она и не настолько сложна, чтобы обсуждать ее. Она просто настраивает числа в currentPos в каждом кадре c использованием соответствующих значений increment. После этого она вызывает drawShape (функция рисования, написанная нами в самом начале кода) и проверяет, является ли данный кадр последним кадром трансформации. Если это так, она сбрасывает все значения, останавливает анимацию и подготавливается к следующему щелчку мыши.

  7. Мы почти закончили. Настроим нашу функцию drawShape для работы с новым объектом currentPos. Обновите функцию drawShape следующими новыми строками (выделены жирным шрифтом).
    drawShape = function (shape) { 
      drawObj.clear(); 
      drawObj.lineStyle(2, 0, 100); 
      currentColor = 0;
      drawObj.moveTo(shape.anchorPoints[0] [0] ,
        shape.anchorPoints [0] [1]);
      for (var i = 0; i<shape.anchorPoints.length-1; i++) { 
        if (shape.colors[currentColor] [0] == i) {
          drawObj.endFill();
          drawObj.beginFill(shape.colors[currentColor] [1], 100);
          currentPos.colors[currentColor] = 
          К[i, shape.colors[currentColor][1]];
          drawObj.moveTo(shape.anchorPoints[i] [0], 
            shape.anchorPoints[i] [1] ) ;
          currentColor++;
        }
        drawObj.curveTo(shape.controlPoints[i] [0],
        Кshape.controlPoints [i] [1], shape.anchorPoints [i+1] [0],
        Кshape.anchorPoints[i+1] [1]);
        currentPos.controlPoints[i] = [shape.controlPoints[i] [0],
        Кshape.controlPoints[i] [1]];
        currentPos.anchorPoints[i] = [shape.anchorPoints[i][0], 
        Кshape.anchorPoints [i] [1]]; 
      }
      currentPos.anchorPoints[i] = [shape.anchorPoints[i][0], 
      Кshape.anchorPoints[i][1]];
    };

    Эти дополнительные строки сохраняют номера текущих позиций точек, чтобы можно было использовать их в следующем кадре нашей функции moveLines. Проследите за превращением и вы увидите, как эти значения влияют на фильм.

  8. Наконец, измените функцию clickCatch так, чтобы при щелчке пользователем начиналась трансформация. Это реализуется вызовом функции startShift.
    clickCatch = function () {
      shapeNum = shapeNum+1>1 ? 0 : shapeNum+1;
      startShift(shapes[shapeNum]); 
    };
  9. Теперь запустите фильм, чтобы увидеть превращение одного фрукта в другой. Здорово!

Игорь Хан
Игорь Хан
След не остается
Александр Коргапольцев
Александр Коргапольцев
Вопрос по содержанию лекции №2, курс Flash MX Studio
Евгения Новоселецкая
Евгения Новоселецкая
Россия
Станислав Бакулин
Станислав Бакулин
Эстония, Нарва