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

Изучение SphereCage

Изучение наследственности

С этого момента ракетки, шарик и цели будут иметь различные функциональности, с которыми мы будем иметь дело с помощью наследственности. Не страшно, если вы не работали с наследственностью ранее. Если вы разберетесь с данной темой, вы будете знать почти все, что вам понадобится.

  1. Под всем предыдущим кодом ActionScript введите следующий код, и затем я объясню, что в нем реализуется (сейчас вы уже должны разбираться в структуре кода!).
    BallModel = function() {
    };
    BallModel.prototype = new Model(); 
    BallModel.prototype.move = function() {
      var x = this.direction.x*this.velocity; 
      var y = this.direction.y*this.velocity; 
      var z = this.direction.z*this.velocity; 
      this.translate(x, y, z); 
      with (this.vertexList[0]) {
        var mag = Math.sqrt(x*x+y*y+z*z); 
        if (mag+this.rad>radAdj) {
          if (this.checkPaddle() && !ok2Hit) {
            playBlip(this.velocity); 
            ok2Hit = 1; 
            updateScore(); 
            if (this.velocity<6) {
              this.velocity += .1;
            }
            this.setDirection(-x, -y, -z); 
          } else if (!ok2Hit) { 
            miss.start(); 
            killBall();
          }
        } else if (mag-this.rad<10 && ok2Hit) {
          delete ok2Hit; 
          playBlip(this.velocity); 
          this.checkAngle(); 
        } 
      } 
    };
    Пример 11.6.

    Итак, что же такое BallModel? Мы только что создавали Model и говорили о том, как шарик будет использовать его методы. Однако сейчас нам нужно, чтобы шарик стал самостоятельно обрабатывать свои действия. Для этого надо создать другой класс Class специально для шарика, причем так, чтобы этот класс наследовал параметры и методы класса Model Class.

    Если вы раньше не слышали об аналогии наследственности на примерах с собаками, сочту за честь ознакомить вас с ней. У меня есть йоркширский терьер по кличке Кейси. Кейси имеет свои собственные повадки, привычки, но у нее также есть определенные методы, которые наследуются у всех собак. Она любит бегать за своим собственным хвостом. Она гоняет кошек и птиц и защищает свою территорию. Когда ей жарко, она часто дышит, высунув язык. Переходя к программированию, можно сказать, что Кейси - это объект инстанса класса Dog. Однако это не все. Кейси также покрыта шерстью. Она родит щенков (если мы захотим свести ее) и будет кормить их молоком. Она теплокровное животное. Все эти особенности она унаследовала из класса Mammal. Итак, она имеет методы и параметры Dog, которые прибавляются к ее изначальным методам и параметрам Mammal. Как вы можете себе представить, отсюда можно перейти вверх по цепочке к классу Animal, затем к CarbonBasedLifeForm (форма жизни, основанная на углероде), и.т.д.

    Мы будем работать над шариком, т.е. инстансом класса BallModel, который сам по себе является подклассом Model. Мы создали наш первый метод BallModel - move - и для движения шарика используем следующее выражение.

    ball.move ();

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

    ball.render ();

    При использовании приведенной выше строки Flash будет отслеживать инстанс ball для выяснения того, содержит ли он локальный метод с именем render. Если это не так, Flash будет искать метод в классе объекта ball, которым является BallModel. Если поиск не даст результатов, Flash продолжит его выше по наследственной цепочке, осуществляя поиск метода render в Model.

    Так почему же мы провели такое разделение? Это было сделано потому, что у ракеток и шарика должны быть полностью различные методы, связанные с ними, но также и некоторые общие 3D-методы преобразования. Мы помещаем методы преобразования в Class и создаем два подкласса для ракеток и шарика (больше всего это похоже на то, что все кошки и собаки принадлежат одному классу млекопитающих). Для достижения этого, мы устанавливаем параметр prototype в BallModel на новый инстанс класса Model. Вот и все, практически ничего сложного здесь нет.

    Разобравшись в этом, нам следует перейти к методу move, написанному в последнем параграфе кода. Здесь мы снчала перемещаем нашу модель с использованием четырех переменных - трех измерений пространства (представлены векторами) и скорость шарика (velocity). Так как шарик двигается с постоянной скоростью, эти значения не обрабатываются ни одним методом. Умножаем текущее направление (которое будет единичным вектором) на текущую скорость и вызываем метод translate (определен для класса Model ).

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

    setDirection - это метод для установки нового направления нашего шарика, которое в данном случае будет противоположным текущему направлению. Здесь особенно удобно то, что мы перемещаем шарик обратно точно в центр, так как наш новый вектор направления (значение, хранимое в ball.direction ) берется прямо из наших текущих координат. Представьте себе шарик с координатами (5,-10,0). Чтобы переместиться в центр пространства, шарику нужно перейти на -5 вдоль оси x и на 10 по оси y. Это и есть направление - коротко и понятно. Оно получается прямо из текущих координат. setDirection будет брать это направление и превращать его в единичный вектор, о котором мы сейчас поговорим.


    Теперь, если ракетка не отбивает шарик, вызывается функция killBall и проигрывается звуковой эффект miss.

    В последнем выражении if мы проверяем, достиг ли шарик центра. Если да, мы проигрываем звук и проверяем текущий угол диаметральной плоскости (ракетки компьютера) с использованием метода checkAngle. В этом методе мы получаем угол столкновения между шариком и плоскостью и отражаем шарик в соответствующем направлении.

  2. Теперь будем добавлять другие методы BallModel (этот объект не является ядром функциональности нашей игры).
    BallModel.prototype.setDirection = function(x, y, z) { 
      var mag = Math, sqrt (x*x+y*y+z*z); 
      var nD = [x/mag, y/mag, z/mag]; 
      var dest = [nD [0]*radAdj-this.vertexList[0].x, nD[1]*radAdj-
      Кthis.vertexList [0].y, nD[2]*radAdj-this.vertexList[0].z]; 
      mag = Math.sqrt(dest[0]*dest[0]+dest[1]*dest[1]+dest[2]*dest[2]);
      this.direction = {x:dest[0]/mag, y:dest[1]/mag, z:dest[2]/mag}; 
    };
    
    BallModel.prototype.checkPaddle = function() { 
      var x; 
      var y; 
      var z;
      var numVertices = paddle.vertexList.length 
      for (var i = 0; i< numVertices; i++) {
        x += paddle.vertexList[i].x;
        y += paddle.vertexList[i].y;
        z += paddle.vertexList[i].z;
      }
      x /= numVertices; 
      y /= numVertices; 
      z /= numVertices; 
      x -= this.vertexList[0].x; 
      y -= this.vertexList[0].y; 
      z -= this.vertexList[0].z; 
      var dist = Math.sqrt(x*x+y*y+z*z); 
      if (dist<planeSize+this.rad) {
        return true;
      }
      return false;
    };
    Пример 11.7.

    Настало время рассмотреть математику трехмерного пространства - давно пора! Метод setDirection вызывается каждый раз при изменении шариком направления, как в центре, так и на периметре. Имя является неточным, так как направление на самом деле отправляется этому методу при вызове (аргументы x, y и z ). setDirection переводит эти числа в форму, в которой мы сможем их использовать. Первые две строки превращают вектор направления в единичный вектор. Вектор длины магнитуды равен квадратному корню из суммы квадратов каждого компонента. Чтобы сделать его единичным вектором, необходимо поделить каждый компонент вектора на магнитуду вектора. С помощью направления единичного вектора мы можем управлять скоростью шарика, изменяя его скорость по направлению, и вектор направления будет просто указывать нам нужное направление, не оказывая влияния на скорость. Это nD, сокращение от "нормализованное направление" (normalized direction).

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

    Следующий метод checkPaddle вызывается каждый раз при достижении шариком периметра сферы, когда нам необходимо проверять, блокирует ли ракетка шарик. Метод находит центральную точку ракетки как среднее всех ее вершин, затем находит расстояние между этим центром и положением шарика. Если оно меньше, чем paddleWidth (это значение может быть половиной длины ракетки) плюс радиус шарика, регистрируется попадание.

    Это не совсем аккуратное обнаружение коллизий, но оно достаточно хорошо служит нашим целям.

  3. Добавьте следующий код.
    BallModel.prototype.checkAngle = function() {
      var vertices = [plane.vertexList[0], 
       plane.vertexList[1], plane.vertexList [2]];
      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]));
      p = [p[0]/magP, p[1]/magP, p[2]/magP];
      var b = [-this.direction.x, -this.direction.y, -this.direction.z];
      var dP = p[0]*b[0]+p[1]*b[1]+p[2]*b[2];
      var dPxN-[p[0]*dP, p[1]*dP, p[2]*dP];
      b = [dPxN[0]+dPxN[0]-b[0], dPxN[1]+dPxN[1]-b[1], 
        dPxN[2]+dPxN [2]-b[2]];
      this.setDirection(b[0], b[1], b[2]);
      setTarget();
    };

    Основной принцин checkAngle - то, что угол падения (угол, под которым шарик ударяется о ракетку) равен углу отражения (это один из законов отражения).

    Сначала мы используем вершины центральной плоскости для определения нормали диаметральной плоскости, который мы превращаем в единичный вектор делением его на его магнитуду. Мы все это уже делали и получили p. Для b мы обращаем направление шарика так, что можно использовать его в формулах. dP является скалярным произведением вектора настроенного направления шарика и нормали плоскости. Вспомните, что скалярное произведение - это расстояние между двумя векторами, которое, в этом случае, будет расстоянием от точки на пути шарика к нашей нормали плоскости. Мы можем использовать это расстояние для определения угла и направления, которые будут на другой стороне нормали.

    Затем мы умножаем нормаль вектора на результат скалярного произведения. Это дает нам проекцию нашего вектора на нормаль. Новое направление шарика будет в два раза больше расстояния этой проекции от исходного вектора нашего шарика, которое мы определяем в конечном присвоении b. Мы отправляем эти новые значения функции setDirection, чтобы найти нормаль к вектору и затем нарисовать цели в соответствующих позициях (мы вскоре добавим реализующую это функцию).


    Мы рассмотрели довольно сложные концепции, однако весь наш проект основан именно на них. Оставшаяся часть кода - это интерфейс и инициализация игры, но сначала нужно закончить создание класса для шарика.

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

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

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

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

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

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