Опубликован: 07.11.2006 | Доступ: свободный | Студентов: 3400 / 338 | Оценка: 3.94 / 3.71 | Длительность: 37:11:00
Лекция 12:

Изучение SphereCage

Классы

Теперь мы перейдем к рассмотрению трехмерных объектов. В этом параграфе мы создадим новые классы, которые будут определять эти объекты в игре SphereCage. Это будет первым шагом в изучении ООП-программирования.

  1. Первый добавляемый объект уже знаком вам, так как мы использовали его в предыдущей лекции.

    Введите следующий код.

    LightSource = function(x, y, z, brightness) {
      this.x = x;
      this.y = y;
      this.z = z;
      this.brightness = brightness;
      this.calcMag(); 
    };
    
    LightSource.prototype.calcMag = function() {
      this.magnitude = Math.sqrt
        (this.x*this.x+this.y*this.y+this.z*this.z) 
    };

    Это вам знакомо из предыдущей лекции. Мы создаем отдельный класс Class для содержания параметров и методов для всех источников света, добавляемых на рабочее место. Добавляемый метод calcMag вычисляет расстояние от источника света до центра пространства.

  2. Следующий фрагмент кода вам знаком, однако у него будет одно значительное отличие. Попробуйте найти его.
    Model = function() {
    };
    Model.prototype.applyTransform = function() { 
      if (this.transformMatrix) {
        for (var 1=0; i<this.vertexList.length; i++) {
          var vert = this.vertexList[i];
          var x = this.transformMatrix.a*vert.x+
          Кthis.transformMatrix.b*vert.y+this.transformMatrix.c*
          vert.z+this.transformMatrix.d*vert. w;
          var y = this.transformMatrix.e*vert.x+
          Кthis.transformMatrix.f*vert.y+this.transformMatrix.g*
          vert.z+this.transformMatrix.h*vert. w;
          var z = this.transformMatrix.i*vert.x+
          Кthis.transformMatrix.j *vert.y+this.transformMatrix.k*
          vert.z+this.transformMatrix.l*vert.w;
          vert.x = x;
          vert.y = y;
          vert.z = z;
        }
        delete this.transformMatrix;
      }
    };
    
    Model.prototype.getSideColor = function(side) {
      var verts = [this.vertexList[side.vertices[0]], this.vertexList[side.vertices[1]],
      Кthis.vertexList [side.vertices [2]]];
      var lightFactor = this.factorLightAngle(verts);
      var r = side.sideColor.substr(0, 2);
      var g = side.sideColor.substr(2, 2);
      var b = side.sideColor.substr(4, 2);
      r = parselnt(r, 16)*lightFactor;
      g = parselnt(g, 16)*lightFactor;
      b = parselnt(b, 16)*lightFactor;
      return r << 16 | g << 8_b;
    };
    
    Model.prototype.factorLightAngle = function(vertices) {
      var U = [(vertices[0].x-vertices[1].x), (vertices[0].y- 
            vertices [1].y), (vertices [0].z-vertices [1].z)]; 
      var V = [(vertices[1].x-vertices[2].x), (vertices[1].y-
      Кvertices[2].y), (vertices [1].z-vertices[2].z)];
      var p = [((U[1]*V[2]) - (U[2]*V[1])), - ((U[0]*V[2]) - (U[2]*V[0])), 
      К((U[0]*V[1])-(U[1]*V[0]))] ;
      var magP - Math.sqrt ((p[0]*p[0]) + (p[1]*p[1]) + (p[2]*p[2])); 
      var dP = ((p[0]*light.x) + (p[l]*light .y) + (p[2]*light.z)); 
      if (dP>0) { 
        dP*= -1;
      }
      return ((Math.acos(dP/(magP*light.magnitude))/Math.PI)* 
      Кlight.brightness/100);
    };
    Пример 11.4.

    Этот код - начало нашей собственной модели Model Class. В предыдущей лекции мы использовали почти в точности тот же самый код для factorLightAngle и getSideColor, а функция applyTransform была непосредственной частью большей функции render, поэтому здесь вам все должно быть понятно.

    Теперь мы, вместо создания общих функций вызова в нашем фильме, создаем отдельный класс Class, в котором будут находиться все наши основные 3D-методы. Когда нам потребуется новая модель, мы просто используем созданный выше конструктор.

    plane = new Model ();

    plane будет использовать все методы Model, так как этот объект сам является моделью, инстансом объекта класса Model. Это облегчит работу, когда на рабочем месте будет несколько моделей, если сравнивать с простым трехмерным кубом из предыдущей лекции.

    Для создания методов объекта Model мы используем его параметр prototype, который является свойством каждого объекта, содержащего все параметры и методы данного объекта. Сделав factorLightAngle частью параметра prototype объекта Model, мы сделали его доступным для каждой создаваемой нами модели.

    Существует другой способ добавления параметров и методов всем инстансам Class, что будет вкратце обсуждаться, чтобы вы имели представление об отличиях и преимуществах использования параметра prototype. Попробуйте поэкспериментировать с этим в новом фильме.

    Выделите кадр 1 слоя по умолчанию и введите следующий код в панели Actions:

    MyClass = function() {
      this.myMethod = function() {};
    };
    instance1 = new MyClass(); 
    instance2 = new MyClass(); 
    trace(instance1.myMethod);

    Отладьте фильм (Control > Debug Movie или комбинацией клавиш (Ctrl)+(Shift)+(Enter)) и нажмите зеленую кнопку проигрывания в отладчике Debugger. В области слева вверху выберите level0 и щелкните на вкладке Variables под этой областью. Вы увидите instance1 и instance2 со значками "+" с левой стороны. Развернув эти инстансы, вы увидите, что они оба содержат копии myMethod. Окно Output подтверждает это возвращая [type function].


    Теперь вернитесь в редактор сценариев и измените строки кода следующим образом.

    MyClass = function() {}; 
    MyClass.prototype.myMethod = function();
    instance1 = new MyClass(); 
    instance2 = new MyClass(); 
    trace(instance1.myMethod);

    Отладив код второй раз, вы увидите, что, несмотря на успешное создание двух инстансов MyClass, а также на то, что окно Output возвратило ожидаемое [type function], instance1 и instance2, расположенные на вкладке Variables, НЕ ИМЕЮТ знаков плюса рядом с собой. Что случилось?

    В первом способе создания метода мы непосредственно применяем функцию myMethod к каждому отдельному инстансу MyClass. Мы присваиваем не указатель, а САМУ ФУНКЦИЮ. Это означает, что у нас будет несколько копий одной и той же функции, занимающих память.

    Во втором способе создания методов они применяются к параметру prototype. Вследствие этого создается только одна копия функции, к которой могут обращаться ВСЕ инстансы. При вызове метода (например, instance1.myMethod(); ), Flash будет сначала отслеживать сам инстанс и все локальные методы, которые к нему были применены (методы, добавленные к одному инстансу). Если не будет найдена ссылка на myMethod, то Flash будет отслеживать параметр prototype класса Class. Поэтому, для любого метода или параметра, которые необходимо повторять для инстансов, имеет смысл добавлять их параметру prototype.

    Вы увидите пример добавления локальных параметров инстансу в нашем предыдущем классе LightSource. В конструкторе мы добавляем параметры x, y, z и яркость вновь созданному инстансу. Несмотря на то, что каждый инстанс LightSource будет содержать эти параметры, значения будут различными для каждого из них, поэтому нам нужно сделать их локальными для инстанса.

    По большому счету, этот подход не является настоящим объектно-ориентированным программированием. Это ООП для Flash, являющееся скорее программно-графической средой Flash, описанной мной в начале лекции. Я надеюсь, что вы сможете увидеть, насколько могут быть полезны подходы в стиле ООП при программировании. Здесь код содержится внутри объектов, которым он нужен и которые используют его, что делает структуру взаимодействия четче. Объект сам управляет своими собственными действиями и параметрами. В нашей игре движение будет контролировать сам шарик, а не ракетка. Если ракетке будет необходимо узнать магнитуду шарика, она отправит запрос шарику (например: ball.getMagnitude(); ), вместо того, чтобы вычислять значение самостоятельно. Таким образом, я знаю, что любой код, напрямую изменяющий или управляющий шариком, может быть найден в самом объекте шарика. По этой причине я сделал вывод, что намного легче выяснить, где в сценариях содержатся ошибки (ведь их далеко не всегда можно легко исправить), когда вы знаете, что объекты управляют сами собой.

    В качестве последнего пояснения к вышеизложенному коду - вы увидите, что метод applyTransform (который применяет матрицу преобразования модели) непосредственно связан с параметрами каждой вершины: x, y, z и w. Но подождите: что такое w? В этой игре мы используем матрицы размером 4х4 для преобразования наших моделей, в отличие от предыдущей лекции, где использовались матрицы 3x3. С помощью матриц преобразования размером 4х4 мы можем включать метод translate для наших моделей, который можно добавить в матрицы преобразования, что было бы невозможно сделать в случае с матрицами размером 3х3. Для этого нужно добавить дополнительный параметр каждой из вершин (нельзя умножить матрицу размером 4х4 на матрицу 3х1 функции applyTransform, так как это просто заполнитель). Значение w будет всегда равно 1, однако это очень полезное дополнение, так как оно позволит использовать матрицы размером 4х4.

  3. Завершим написание кода класса Model.
    Model.prototype.rotateX = function(degree) { 
      var rad = degree*Math.PI/180; 
      var sin = Math.sin(rad); 
      var cos = Math.cos(rad);
      var matrix = {a:1, b:0, c:0, d:0, e:0, f:cos, g:sin, h:0, i:0, 
      Кj:-sin, k:cos, 1:0, m:0, n:0, O:0, p:1}; 
      this.transform(matrix);
    };
    
    Model.prototype.rotateY = function(degree) { 
      var rad = degree*Math.PI/180; 
      var sin = Math.sin(rad); 
      var cos = Math.cos(rad);
      var matrix = {a:cos, b:0, c:-sin, d:0, e:0, f:1, g:0, h:0, i:sin, 
      Кj:0, k:cos, 1:0, m:0, n:0, O:0, p:1}; this.transform(matrix);
    };
    
    Model.prototype.translate = function(x, y, z) {
      var matrix = {a:1, b:0, c:0, d:x, e:0, f:1, g:0, h:y, i:0, j:0, 
      Кk:1, 1:z, m:0, n: 0, O:0, p:1}; 
      this.transform(matrix);
    };
    
    Model.prototype.transform = function(matrix) { 
      if (this.transformMatrix) {
        var a = matrix.a*this.transformMatrix.a+matrix.b*
        Кthis.transformMatrix.e+matrix.c*this.transformMatrix.i+
        Кmatrix.d*this.transformMatrix.m;
        var b = matrix.a*this.transformMatrix.b+matrix.b*
           Кthis.transformMatrix.f+matrix.c*this.transformMatrix.j +
        Кmatrix.d*this.transformMatrix.n; 
        var c = matrix.a*this.transformMatrix.c+matrix.b*
                    Кthis.transformMatrix.g+matrix.c*this.transformMatrix.k+
        matrix.d*this.transformMatrix.o; 
        var d = matrix.a*this.transformMatrix.d+matrix.b*
                    Кthis.transformMatrix.h+matrix.c*this.transformMatrix.1+
        Кmatrix.d*this . transformMatrix.p; 
        var e = matrix.e*this.transformMatrix.a+matrix.f*
                    Кthis.transformMatrix.e+matrix.g*this.transformMatrix.i+
        Кmatrix.h*this.transformMatrix.m; 
        var f = matrix.e*this.transformMatrix.b+matrix.f*
                    Кthis.transformMatrix.f+matrix.g*this.transformMatrix.j +
        Кmatrix.h*this.transformMatrix.n; 
        var g = matrix.e*this.transformMatrix.c+matrix.f*
                    Кthis.transformMatrix.g+matrix.g*this.transformMatrix.k+
        Кmatrix.h*this.transformMatrix.o; 
        var h = matrix.e*this.transformMatrix.d+matrix.f*
                    Кthis.transformMatrix.h+matrix.g*this.transformMatrix.1+
        Кmatrix.h* this.transformMatrix.p; 
        var i = matrix.i*this.transformMatrix.a+matrix.j*
                    Кthis.transformMatrix.e+matrix.k*this.transformMatrix.i+
                    Кmatrix.l*this.transformMatrix.m;
        var j = matrix.i*this.transformMatrix.b+matrix.j*
        Кthis.transformMatrix.f+matrix.k*this.transformMatrix.j +
                    Кmatrix.l*this.transformMatrix.n;
        var k = matrix.i*this.transformMatrix.c+matrix.j*
                    Кthis.transformMatrix.g+matrix.k*this.transformMatrix.k+
                    Кmatrix.l*this.transformMatrix.o;
        var 1 = matrix.i*this.transformMatrix.d+matrix.j*
                    Кthis.transformMatrix.h+matrix.k*this.transformMatrix.1+
                    Кmatrix.l*this.transformMatrix.p;
        this. transf ormMatrix = {a:a, b:b, c:c, d:d, e:e, f:f, g:g, 
        Кh:h, i:i, j:j, k:k, l:l, m:0, n:0, o:0, p:1}; 
      } else {
        this.transformMatrix = matrix; 
      }
    };
      Model.prototype.render = function() { 
        this.applyTransform(); 
        for (var i = 0; i<this.vertexList.length; i++) {
        var scale = focalLength/(focalLength-this.vertexList[i].z); 
        this.clip[i]._xscale = this.clip[i]._yscale=(scale/4)* this.vertexList[i].z+50;
        this.clip[i]._x = this.vertexList[i].x*scale; 
        this.clip[i]._y = this.vertexList[i].y*scale; 
      }
    };
    Пример 11.5.

    rotateX и rotateY являются тем же, чем и в предыдущей лекции, хотя вы можете видеть, что матрицы имеют дополнительные значения. Функция translate - новая, однако станет совершенно понятной для вас, как только вы разберетесь в использовании матриц. Это метод, который будет перемещать наш шарик через сферу. Здесь мы устанавливаем параметры d, h и l нашей матрицы со сдвигом координат (x, y, z). Функция transform (мы ее также использовали в предыдущей лекции) стала несколько больше из-за дополнительных параметров матрицы. При детальном рассмотрении, вы заметите, что нам не нужно вычислять последнюю строку матрицы, так она всегда будет равна (0,0,0,1).

    С помощью метода render мы сначала применяем текущее преобразование модели и затем обрабатываем циклом ее vertexList для установки экранных координат. Эта функция нужна для этой игры и ее структуры, однако она была бы бесполезна при применении в этом же виде к другим объектам, поскольку только две из наших моделей непосредственно используют этот метод - шарик и цель. Шарик имеет только одну вершину, тогда как у цели их две (две мини-цели, каждая с одной вершиной). Поэтому нам не нужно беспокоиться о сторонах, цвете и т.д., и мы проходим циклом вершины только из-за того, что у нас есть две цели.

    В остальном, этот код в точности повторяет программу для куба в предыдущей лекции (рассматриваемый параметр clip содержит ссылку на один из объектов innerBall, topTarget или bottomTarget ), с добавленной строкой для непосредственного изменения размеров фильмов по мере их движения около сферы. В случае с кубом мы этого не делали, но при работе с моделями с единственными вершинами физический фильм должен увеличиваться для моделирования глубины.

    Создание Model завершено, однако этот код не обеспечивает все, что необходимо для трехмерного пространства. На самом деле, это лишь методы, необходимые для всех моделей на нашем рабочем месте.

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

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

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

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

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