Опубликован: 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. Теперь запустите фильм, чтобы увидеть превращение одного фрукта в другой. Здорово!

Игорь Хан
Игорь Хан

у меня аналогичная ситуация. Однако, если взять пример из приложения (ball_motion_04_click for trial.fla) то след остается. при этом заметил, что в моем проекте в поле "One item in library" виден кружок, в то время как в приложенном примере такого кружка нет.

Вопрос знатокам, что не так?

Александр Коргапольцев
Александр Коргапольцев

объект созданый мной упорно не желает оставлять след(единственное что добился, так это то что шарик резво гоняется за курсором) функция duplicateMovieClip остаётся не активной, т.е. следа от объекта не остаётся, но если я тоже самый код вбиваю в учебный файл всё работает, не могу понять где я ошибаюсь и почему в документе созданном заново, не работает код начиная от функции duplicateMovieClip? 

Тамара Ионова
Тамара Ионова
Россия, Нижний Новгород, НГПУ, 2009
Магомед Алисултанов
Магомед Алисултанов
Россия, Волгоград, лицей 2