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

Математика и физика Flash

Проект №1: Asteroids

Сначала определим, что мы хотим реализовать.

  • С помощью двух клавиш можно поворачивать корабль по или против часовой стрелки.
  • При нажатии клавиши двигателя корабль начинает движение в направлении, по которому он расположен.
  • Чем дольше вы держите клавишу двигателя, тем выше скорость корабля.
  • Если развернуть корабль и начать ускорение в обратную сторону, его скорость уменьшится, затем корабль остановится и начнет движение в противоположном направлении.
  • При нажатии другой клавиши, корабль выпустит снаряд в направлении, которое он указывает.
  • По всему экрану летают астероиды, и в случае столкновения вы проигрываете и погибаете.
  • Если снаряд попадает в астероид, последний раскалывается пополам на достаточно мелкие части, после чего они просто исчезают.

Хотите - верьте, хотите - нет, мы изучили почти весь материал для реализации всех этих возможностей. Нам просто нужно связать вместе все то, что мы изучили до сих пор.

  1. Сначала создадим стереотипный корабль для расстрела астероидов в виде фильма, экспортируем его в ActionScript с использованием идентификатора ship. Здесь важно помнить, что объект должен указывать вправо.


    Я также создал второй кадр в этом фильме, изображающий пламя из сопел корабля. Он будет использоваться для визуального обозначения того, что у корабля работают двигатели. Убедитесь в том, что в обоих кадрах есть действие stop, чтобы фильм не начал их проигрывание вперед и назад.

  2. После этого создайте астероид, являющийся просто фигурой с заливкой, величина которой в несколько раз больше, чем сам корабль. Также экспортируйте его с использованием имени asteroid.

  3. Наконец, создайте снаряд с именем shot.
  4. Убедитесь, что слева на рабочем месте нет никаких объектов, так как данные фильмы будут добавляться динамически.
  5. В кадре 1 мы начнем с функции init. Не забудьте первым делом вызвать функцию.
    init();
      function init() {
        // set constants for stage borders
        if (!stagelsSet) {
          LEFT = 0;
          TOP = 0;
          BOTTOM = Stage.height;
          RIGHT = Stage.width;
          stageIsSet = true;
        }
        // how many ships do we start with?
        livesLeft = 5;
        //  set how many asteroids to begin with
        maxAst = 3;
        ast_array = new Array() ;
        //  asteroids  and shots will be in their own mc's 
          to prevent depth conflicts
        createEmptyMovieClip("astField_mc", 0) ;
        createEmptyMovieClip("shots_mc", 1);
        //  create asteroids and ship
        astInit();
        shipInit();
        stop();
      }

    В данном коде достаточно много комментариев, однако мы все равно его разберем. Сначала устанавливаем константы для размера рабочего места (RIGHT, LEFT, TOP, BOTTOM). После этого устанавливаем переменную stageIsSet на значение true, что предотвратит повторный запуск кода при перезапуске игры.

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

    Следующие две строки являются альтернативным способом решения проблем дубликатной глубины, с которыми мы сталкивались ранее. Здесь мы будем держать все астероиды внутри одного фильма, а все снаряды - в другом фильме. Так как несущие фильмы сами по себе находятся на разных глубинах (0 и 1), глубины астероидов не будут пересекаться с глубинами снарядов и наоборот. Мы просто проверяем, что при добавлении фильмов со снарядами или астероидами они помещаются в соответствующий несущий фильм.

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

    Наконец, останавливаем фильм на данном кадре.

  6. Теперь давайте посмотрим на функцию astInit.
    function astInit( ) {
        //  create a number of asteroids
        astIndex = 0;
        while (astIndex<maxAst) {
          createAst();
        }
      }

    Все, что в ней реализуется - это цикл от нуля до количества созданных астероидов, а также повторное выполнение функции createAst, что создает один астероид. Рассмотрим данную функцию.

  7. Функция createAst такова.
    function createAst() {
        // put one asteroid on stage and initialize 
           its properties and methods
        ast_mc = astField_mc.attachMovie("asteroid", 
           "ast"+astIndex, astIndex++) ;
        ast_mc._rotation = Math.random()*360;
        ast_mc.velX = Math.random()*6-3;
        ast_mc.velY = Math.random()*6-3;
        ast_mc.onEnterFrame = astMove;
        // push this asteroid onto the asteroid array
        ast_array.push(ast_mc);
        // return a reference to this asteroid
        return ast_mc;
      }

    Здесь инстанс астероида добавляется в фильм astField_mc. (Помните, все они должны оставаться в этом фильме для предотвращения проблем с глубинами.) С помощью временной переменной ast_mc мы вращаем его случайным образом, присваиваем ему скорость по направлению и управляющий элемент onEnterFrame и помещаем в массив ast_array. Полезно хранить ссылки на все астероиды в массиве, чтобы можно было обрабатывать массив циклом для проверки на попадание.

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

  8. Следующим шагом является функция shipInit ().
    function shipInit() {
        //  put a ship on stage and initialize 
            its properties and methods
        attachMovie("ship", "ship_mc", 2);
        ship_mc._x = RIGHT/2;
        ship_mc._y = BOTTOM/2;
        ship_mc.velX = 0;
        ship_mc.velY = 0;
        ship_mc.onKeyDown = control;
        ship_mc.onKeyUp = decontrol;
        ship_mc.onEnterFrame = move;
        Key.addListener(ship_mc);
        rotate = 0;
        thrust = 0;
      }

    Она добавляет инстанс фильма корабля, присваивая ему имя ship_mc. После этого он позиционируется, присваивается его скорость и несколько функций поддержки событий, фильм становится приемником событий Key, а также устанавливаются на 0 две другие переменные - rotate и thrust - для обеспечения неподвижности корабля при его появлении.

    Теперь рассмотрим функции, присвоенные нами управляющим элементам onKeyDown и onKeyUp - control и decontrol.

  9. Функция control.
    function control() {
        //  check which key was pressed and 
            control ship accordingly
        switch (Key.getCode()) {
        case  Key.LEFT :
          rotate  = -1;
          break;
        case Key.RIGHT :
          rotate = 1;
          break;
        case Key.UP :
          thrust = .3;
          ship_mc.gotoAndStop(2);
          break;
        case Key.SPACE :
          shoot();
          break;
        }
      }

    Она представляет собой просто выражение switch. Если нажаты клавиши LEFT или RIGHT, она устанавливает переменную rotate на значения -1 или +1.

    При нажатой клавише UP переменная thrust устанавливается на значение .3 (При желании вы можете изменить это значение. Я пришел к нему методом проб и ошибок - оно обеспечивает хорошую динамику корабля). Здесь мы также отправляем корабль в кадр 2 и отображаем выпускаемые им ракеты. Далее мы увидим, как переменные rotate и thrust используются для управления движением корабля.

    Наконец, если нажата клавиша SPACE, выполняем функцию shoot.

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

    Все довольно просто.

    function decontrol() {
        // no key is pressed, so no thrust or rotation
        rotate = 0;
        thrust = 0;
        ship_mc.gotoAndStop(1); }
  11. Продолжая работать над функциями корабля, мы установили управляющий элемент onEnterFrame для функции движения. Давайте разберем ее.
    function move() {
        // rotate  ship
        if (rotate) {
          this._rotation  +=  rotate*5;
        }
        // move ship
        if (thrust)  {
          // convert rotation to radians
          rad = this._rotation*Math.PI/180;
          // get x and у components of thrust
          this.thrustX = Math.cos(rad)*thrust;
          this.thrustY = Math.sin(rad)*thrust;
          // add thrust to velocity
          this.velX += this.thrustX;
          this.velY += this.thrustY;
        }
        // add velocity to position
        this._x += this.velX;
        this._y += this.velY;
        // perform screen wrapping if (this._x>RIGHT) { this._x = LEFT;
        if  (this._x<LEFT)  {
          this._x  =   RIGHT;
        }
        if   (this._y>BOTTOM)  {
          this._y  =   TOP;
        }
        if   (this._y<TOP)  {
          this._y =  BOTTOM;
        }
      }
        Да, мы используем одну секцию за раз.
      //  rotate  ship
      if  (rotate)   {
        this._rotation  +=  rotate* 5;
      }
    Пример 8.1.

    Довольно просто. Берем переменную поворота, которая может иметь значения -1 или +1 в зависимости от того, какая клавиша была нажата, или 0, если клавиша не была нажата. Умножаем ее на 5, чтобы ее значениями стали -5, 0 или +5? и добавляем результат к повороту корабля. 5 - просто число, хорошо подходящее по результатам тестирования. Вы можете настраивать это значение по вашему вкусу. В идеале нужно было бы записывать его в другую константу и определять его в функции init, например, ROTATESPEED = 5. Я просто выделил это здесь, чтобы сохранять функцию init как можно более простой.

    Далее следует самая сложная часть функции move.

    // move ship
      if (thrust) {
        // convert rotation to radians
        rad = this._rotation*Math.PI/180;
        // get x and у components of thrust
        this.thrustX = Math.cos(rad)*thrust;
        this.thrustY = Math.sin(rad)*thrust;
        // add thrust to velocity
        this.velX += this.thrustX;
        this.velY += this.thrustY;
      }

    Если thrust равна 0 (нажата клавиша UP), этот фрагмент будет полностью пропущен. Если переменная имеет какое-то значение, этот код выполняется. Сначала выполняется преобразование поворота корабля (в градусах) в радианы. Затем берется значение thrust, которое соответствует углу наклона корабля, и раскладывается на компоненты X и Y - thrustX и thrustY - с использованием основ тригонометрии, изученных ранее в данной лекции. Рисунок показывает, какие действия выполняются.


    После этого мы просто прибавляем значения ускорения к скоростям по направлению осей X и Y. Это дает новую скорость, с которой корабль будет двигаться после применения ускорения в этом направлении. Имейте в виду, что на рисунке выше, если бы корабль двигался в обратную сторону, и вы применили ускорение, оно было бы направлено в обратную сторону от направления движения корабля, поэтому корабль замедлил бы ход, остановился и начал движение в противоположном направлении - в точности, как мы хотели.

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

    //  add velocity to position
      this._x +=  this.velX;
      this._y +=  this.velY;
      //  perform  screen wrapping
      if   (this._x>RIGHT)  {
      this._x  =   LEFT;
      }
      if   (this._x<LEFT)  {
        this._x  =  RIGHT;
      }
      if  (this._y>BOTTOM)  {
        this._y =  TOP;
      }
      if   (this._y<TOP)  {
        this._y  =  BOTTOM;
      }
    }

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

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

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

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

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

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

Екатерина Косарева
Екатерина Косарева
Россия, г. Пушкино