Столкновения

То, что астероиды свободно пролетают друг сквозь друга и понятия не имеют, когда столкнутся с землей – неправильно. Необходимо проверять объекты на столкновение и заставить астероиды отскакивать друг от друга, а при «столкновении» с землей падать на нее и взрываться.

Можно банально брать каждый астероид и по списку проверять, не столкнулся ли он с другими. Этот подход возможен только при малом количестве проверяемых объектов. Если объектов много (как планируется в нашем случае), то подобная проверка загрузить процессор на полную и все будет тормозить.  Более правильный подход проверять на столкновение не со всеми существующими объектами, а только с теми, что находятся «рядом». Как выяснить кто «рядом»? Один из простых вариантов «Виртуальная сетка», нужно разлинеить игровое поле и для каждого сектора (клеточки) создать список объектов, которые сейчас в нем находятся. Для проверки столкновения мы будем перебирать только те объекты, которые находятся в том же секторе, что и проверяемый объект.
Пример:

Астероид под номером 1 находится в клетке A1, и проверки с кем он столкнулся, не будет вообще, т.к. больше в этом секторе никого нет. Для астероида 2, будет проверка всех, кто пересекает сектор B1, это астероиды 3 и 4.

Таким образом, количество проверок на столкновение уменьшается в сотни раз (особенно для большого поля, как у нас, и при большом количестве объектов). При создании объекта, определяем, какому сектору он принадлежит, и регистрируем его там, а при передвижении переписываем. Размер сектора выбрать можно любой, и у слишком маленького и у слишком большого есть свои преимущества и недостатки. Для простоты расчетов, я решил взять такой, чтобы самый крупный объект не пересекал более 4-х секторов. У нас самая крупная земля, ширина 197, она неподвижна, если ее разместить в вершине одного сектора, то чтобы перекрыть 4 сектора, один сектор будет в 100 точек.

Для определения, какие сектора пересекает объект нужно представить его в виде квадрата, и проверить в каком секторе находится каждый из его углов. Добавляем в базовый класс basic_object хеш

public var sectors:Object// какие сектора пересекает объект

Лучше делать Object а не массив, чтобы не пробегаться по нему проверяя не добавили ли мы один и тот же сектор дважды.

И пишем функцию пересчета секторов

// Пересчитывает, какие сектора пересекает объект
public function CalcSectors():void {
     var dx:Number, dy:Number;
     var x1:Number, y1:Number;
     var s:String;
     sectors = new Object();      // сбрасываем список секторов
     for (dx=-1; dx<=1; dx+=2){
           for (dy=-1; dy<=1; dy+=2){
                // вычисляем координату угла (центр+радиус)
                x1 = x + radius*dx;
                y1 = y + radius*dy;
                // название сектора будет в виде "x_y"
                s = Math.floor(x1/100)+"_"+Math.floor(y1/100);
                sectors[s]=true;   // запоминаем этот сектор
           }
     }
}

Маленькое отступление. Мне стало интересно, почему при добавлении числа к строке “строка”+Number, его не нужно преобразовывать в строку toString() ? Написал маленькую проверку:

var s:String="1";
var num:Number=4;
s+=num;
num+=s;
trace("s="+s+", num="+num); // s=14, num=414

Ни каких ошибок компилятор не выдал. Это что же получается, все как раньше? Переменная num по пути из Number стала String и в результате получили “414” вместо ожидаемых “18” или хотя бы ошибки компиляции?! Нафига тогда вообще типы переменных нужны? Что-то я не понял.  

Вернемся к астероидам. В главном классе sky нужно создать хеш всех секторов, в которых побывали наши объекты, и прописать в них ссылки на сами объекты.

// Объявляем переменную для хранения секторов
     var all_sectors:Object;      // сектора со ссылками на объекты в них
. . .
// в конструкторе инициализируем
     all_sectors = new Object();
. . .
// В функции вброса новых астероидов
     // Просим пересчитать в какие сектора попал объект
     new_asteroid.CalcSectors();
     // добавим в эти сектора ссылку на объект
     for each (s in new_asteroid.sectors){
           if (!all_sectors[s]){ // такого сектора еще не было, создадим
                all_sectors[s] = new Object();
           }
           all_sectors[s][new_asteroid.name] = new_asteroid;
     }

А в функции Update, где мы перемещаем все объекты, нужно вставлять пересчет секторов и проверку на столкновения.

public function Update(e : Event):void {
     var s:String, obj2:basic_object;
     // Проходим по всему массиву созданных объектов
     // и заставляем каждого сдвинуться в своем направлении
     for each (var obj:basic_object in all_moving){
           // перед тем, как сдвинуться удалим запись об этом объекте из секторов
           for (s in obj.sectors){
                delete all_sectors[s][obj.name];
           }
           // смещаемся
           obj.move();
           // Просим пересчитать в какие сектора попал объект
           obj.CalcSectors();
           // Проверяем столкновения со всеми объектами, которые есть в новых секторах
           for (s in obj.sectors){
                if (all_sectors[s]){  // такой сектор есть
                     for each (obj2 in all_sectors[s]) { // проверяем на столкновение
                            if (obj.CheckCollision(obj2)){ // столкнулись
                                   // делаем отскок
                                   resolve(obj, obj2);
                            }
                     }
                } else { // нет такого сектора
                     all_sectors[s] = new Object(); // теперь будет
                }
                // регистрируемся в этом секторе
                all_sectors[s][obj.name]=obj;
           }
     }
}
 

Представим наши астероиды в виде упругих шаров с массой пропорциональной объему шара (т.е. с одинаковой плотностью). Необходимо рассчитать результирующий вектор после столкновения у каждого астероида. Задача тривиальная, ее легко сможет решить часа за два ученик 7-го класса… но не программист со стажем. «Зачем изобретать велосипед, когда есть Интернет?» решил я для себя и пристал к гуглу с вопросом:
 

Расчет результирующих векторов после столкновения двух шаров с разной массой

…всемирный поисковик задумался на долю секунды и выдал мне несколько сотен сайтов… После трех часов исследования паутины и приуныл, готового кода не попадалось. Было много сайтов по физике, были сайты, где все наглядно изображалось, как это происходит, были многочисленные примеры кода сталкивающихся шариков с одинаковой массой, но у меня же они разные! К вечеру попался один сайт, даже не сайт, а просто папка и исходниками на Java какой-то физической модели, где был расчет столкновения двух шаров с разной массой. Я оттуда скопировал только нужное и перевел на Flash. Для проверки сделал маленькую флешку, где катаются 9 шаров, отскакивая от стенок, и друг от друга. Вот что получилось.

В наш класс вектора нужно добавить функцию вычисления проекции на другой вектор:

public class Vector {
. . .

// returns the vector projection of this vector onto v
function vectorProjectionOnto(v:Vector):Vector {
     var res:Vector = v.getUnitVector();
     res.mulScalar(scalarProjectionOnto(v));
     return res;
}
function getUnitVector():Vector {
     var len:Number = magnitude();
     var res:Vector = new Vector(x,y);
     if (len) {
           res.x /= len;
           res.y /= len;
     }
     return res;
}
// returns the scalar projection of this vector onto v
function scalarProjectionOnto(v:Vector):Number {
     return (x*v.x + y*v.y)/v.magnitude();
}

Делаем объект «шарик», его основные функции и движение

dynamic public class Ball extends MovieClip {
     var Velocity:Vector;   // направление и скорость

     function Ball() {
           // зададим случайное движение
           Velocity = new Vector(4*(Math.random()-0.5),4*(Math.random()-0.5));
           // Движение шарика
           addEventListener(Event.ENTER_FRAME, Update);
     }

     public function Update(e : Event):void {
           var new_x:Number = x+Velocity.x;
           var new_y:Number = y+Velocity.y;
           var r = radius;
           // отскоки от краев экрана
           if (new_x-r<0 || new_x+r>stage.width) Velocity.x=-Velocity.x;
           if (new_y-r<0 || new_y+r>stage.height) Velocity.y=-Velocity.y;

           move();
     }

     function get radius():Number {
           return width/2;
     }
    
     function getMassa():Number {
           var r:Number = radius;
           return (4/3*Math.PI*r*r*r)/100;       // масса шара
     }
     // Переместиться
     function move():void {
           x += Velocity.x;
           y += Velocity.y;
     }

     // Вычисляет расстояние до другого объекта в квадрате
     // Функция sqrt довольно медленная и быстрее оперировать расстоянием в квадрате
     public function distance2(obj2:Ball):Number {
           var dx:Number = x - obj2.x;
           var dy:Number = y - obj2.y;
           return dx*dx + dy*dy;
     }
     // Определяет, столкнулись два объекта или нет
     public function CheckCollision(obj2:Ball):Boolean {
           var d2:Number = distance2(obj2); // квадрат расстояния
           var dr:Number = radius + obj2.radius; // сумма радиусов - минимальное возможное расстояние
                // если расстояние между объектами меньше суммы их радиусов - столкновение
                return (d2 < dr*dr);
     }
}

Расчет столкновения шаров

function resolve(ball1:Ball, ball2:Ball):void {
     var b1Velocity:Vector = ball1.Velocity;
     var b2Velocity:Vector = ball2.Velocity;
     var b1Mass:Number     = ball1.getMassa();
     var b2Mass:Number     = ball2.getMassa();

     var lineOfSight:Vector = new Vector(ball1.x-ball2.x, ball1.y-ball2.y);
     var v1Prime:Vector = b1Velocity.vectorProjectionOnto(lineOfSight);
     var v2Prime:Vector = b2Velocity.vectorProjectionOnto(lineOfSight);

     var v1Prime2:Vector = new Vector();
     v1Prime2.copyVector(v2Prime);
     v1Prime2.mulScalar(2*b2Mass);
     v1Prime2.addVector(v1Prime.getMulScalar(b1Mass - b2Mass));
     v1Prime2.mulScalar(1.0/(b1Mass + b2Mass));

     var v2Prime2:Vector = new Vector();
     v2Prime2.copyVector(v1Prime);
     v2Prime2.mulScalar(2*b1Mass);
     v2Prime2.subVector(v2Prime.getMulScalar(b1Mass - b2Mass));
     v2Prime2.mulScalar(1.0/(b1Mass + b2Mass));

     v1Prime2.subVector(v1Prime);
     v2Prime2.subVector(v2Prime);

     b1Velocity.addVector(v1Prime2);
     b2Velocity.addVector(v2Prime2);
}

Если только изменить вектора движения шаров после того, как они столкнулись (уже пересекаются), то иногда получается, что шары «застревают» друг в друге. Это происходит если после первого столкновения быстродвижущийся маленький шар глубоко входит в большой, так что после изменения вектора движения, они все равно пересекаются и происходит повторное изменение векторов… и так по кругу, маленький шар становится спутником большого. Чтобы избежать такой ситуации, нужно перед изменением векторов движения шаров раздвинуть их в стороны, чтобы они не пересекались.

// Отодвигает шарик 1 от шарика 2, чтобы они не пересекались
function PullBalls(ball1:Ball, ball2:Ball):void {
     var v:Vector = new Vector(ball1.x-ball2.x, ball1.y-ball2.y);
     var distance:Number = v.magnitude();
     var min_distance:Number = ball1.radius + ball2.radius;
     if (distance > min_distance) return; // не пересекаются
     v.mulScalar((0.1+min_distance-distance)/distance);
     ball1.x += v.x;
     ball1.y += v.y;
}

А вот и наглядный результат.

Скачать исходные коды ( ball_collision.zip 8 кб.)  

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

Уже позже наткнулся на сайт (http://www.silin.fatal.ru/page/index.html) где есть совершенно аналогичный пример с отскакивающими шариками. Правда, не опубликованы его исходники…  

Уровень жизни объектов

В нашей игре все объекты уничтожаемы, значит, все они имеют «уровень жизни», следует его отобразить как полоску над объектом. Если нарисовать ее сразу над всеми объектами, то будет некрасиво… множество летящих астероидов с полосками… жуть. Лучше сделать показ полоски только при наведении мышки на объект, а критическое состояние объекта, когда он уже вот-вот сломается/взорвется, отображать покраснением самого объекта или ареола вокруг него.
Уровень жизни имеют все объекты, поэтому добавляем отображение в базовый класс basic_object.

Сначала рисуем мувиклип HPline, в котором 100 кадров – уровень жизни в процентах. Ставим ему «Export for ActionScript», флеш предупреждает, что класс HPline не найден и потому он его создаст самостоятельно. Как я понимаю, это чистая формальность для того, чтобы можно было этот мувиклип аттачить куда-либо.
И в класс basic_object добавляем примерно такой код:

var tkHP:Number, mHP:Number; // текущий и максимальный уровни жизни
var HP_mc:HPline;    // мувик полоски уровня жизни

public function basic_object() {
     addEventListener(MouseEvent.MOUSE_OVER, ShowInfo);// при наведении мышки - показать
     addEventListener(MouseEvent.MOUSE_OUT, HideInfo); // при уходе мышки - убрать
}

// Отображает информацию об объекте
public function ShowInfo(e:Event=undefined) :void {
     if (!mHP){ // нет уровня жизни, нечего показывать
           HideInfo(); // спрятать полоску, если была
           return;
     }
     if (!HP_mc){ // плоска еще не создавалась
           HP_mc = new HPline(); // создаем мувик полоски жизни
           addChild(HP_mc); // добавим
     }
     HP_mc.visible=true;
     // зная радиус объекта, располагаем полоску сверху
     HP_mc.width=radius*2;
     HP_mc.x=-radius;
     HP_mc.y=-radius-HP_mc.height;
     HP_mc.gotoAndStop(Math.floor(tkHP/mHP*100)+1);    // уровень жизни в процентах
}
// Убирает информацию об объекте
public function HideInfo(e:Event=undefined) :void {
     if (!(HP_mc && HP_mc.visible)){ // полоска не создана или не видна
           return;
     }
     HP_mc.visible=false; // прячем
}
// set/get для текущего уровня жизни
public function set HP(newHP:Number) :void {
     tkHP=Math.max(0, newHP); // уровень жизни не может быть меньше нуля
     if (HP_mc && HP_mc.visible){ // нарисована полоска уровня жизни
           ShowInfo(); // нужно обновить информацию
     }
}
public function get HP():Number {
    return tkHP;
}
// set/get для максимального уровня жизни
public function set maxHP(new_maxHP:Number) :void {
     mHP=new_maxHP;
     if (HP_mc && HP_mc.visible){ // нарисована полоска уровня жизни
           ShowInfo(); // нужно обновить информацию
     }
}
public function get maxHP():Number {
    return mHP;
}

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

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

import flash.utils.*;
. . .
var hideTimeout:Number; // id таймера на удаление полоски уровня жизни
 . . .

// Убирает информацию об объекте с задержкой в секунду
public function HideInfo(e:Event=undefined):void {
     if (!(HP_mc && HP_mc.visible)){ // полоска не создана или не видна
           return;
     }
     if (!hideTimeout){
           hideTimeout=setTimeout(HideInfoNow, 1000);
     }
}
// Немедленно убирает полоску уровня жизни
public function HideInfoNow():void {
     if (HP_mc){
           HP_mc.visible=false; // прячем
     }
     hideTimeout=0;
}

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

public function ShowInfo(e:Event=undefined):void {
     . . .
     if (hideTimeout){ // выключить таймер на удаление полоски
           clearTimeout(hideTimeout);
           hideTimeout=0;
     }
     . . .

Итак, смотрим, что получилось. Для наглядности, я пустил два пересекающихся потока астероидов и поставил всем астероидам maxHP=100, а при столкновении HP-=10.  

Второй результат: при столкновении астероиды отскакивают друг от друга с уменьшением уровни жизни. При наведении мышки на объекты видны полоски с уровнем жизни. Небо можно таскать мышкой.

Важно знать, когда объект вот-вот уже будет уничтожен. Сделаем в случае, если уровень жизни критический, красную подсветку  вокруг объекта (фильтр glow). Делаем это для всех объектов, добавляем в basic_object:

import flash.filters.GlowFilter;
. . .

// Включает красную подсветку, если уровень жизни критический <33%
function showCriticalHP():void {
     var percent:Number;
     if (!mHP){ // не установлен maxHP
           return;
     }
     percent = HP/mHP; // процент уровня жизни 0..1
     if (percent>=0.33){ // уровень жизни не критический
           return;
     }
     // уровень жизни критический
     // создаем свечение, яркость которого (alpha) зависит от процента HP
     var myFilters:Array = new Array();
     myFilters.push(new GlowFilter(0xFF0000, 1-percent*3, 18,18));
     if (this["bg"]){ // если есть фоновая картинка, то подсвечиваем ее
           this.bg.filters = myFilters;
     } else { // иначе объект целиком
           filters = myFilters;
     }
}
. . .
// при изменении HP вызываем функцию showCriticalHP
public function set HP(newHP:Number):void {
     tkHP=Math.max(0, newHP); // уровень жизни не может быть меньше нуля
     if (HP_mc && HP_mc.visible){ // нарисована полоска уровня жизни
           ShowInfo(); // нужно обновить информацию
     }
     showCriticalHP();
}

Наши астероиды, даже достигнув нуля HP, продолжают летать, как ни в чем не бывало, пора делать смерть объектов. Видов «смерти» будет несколько:
1. Для уничтоженного астероида это должно быть просто рассыпание в прах
2. Для астероида достигшего земли, нужно нарисовать падение на землю со взрывом
3. Для искусственных объектов (космических оборонительных турелей) - сильный взрыв
4. Для крупных комет – разбиение на мелкие астероиды

Для любого взрыва нужно разлетающееся дымное облако. Ищем в инете png картинку дыма и делаем простенький мувиклип разлетающегося облака. Что-то вроде этого:

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

// взрыв
package main {
     import flash.display.MovieClip;
     import flash.events.*;

     dynamic public class explosion extends MovieClip {
           public var Velocity:Vector; // вектор движения

           public function init(obj:basic_object) { // ссылка на взорвавшийся объект
                Velocity = new Vector();
                // вектор движения взрыва тот же, что и у астероида
                Velocity.copyVector(obj.Velocity);
                Velocity.mulScalar(1/2); // только чуть медленней
                // позиция из центра астероида
                x = obj.x;
                y = obj.y;
                // случайно повернем мувик взрыва, чтобы внести разннобразие
                rotation = 360*(Math.random()-0.5);
                // изменим масштаб, чтобы взрыв соответствовал по размерам
                // взорвавшемуся объекту
                scaleX=scaleY=2*obj.radius/40;
                // двигаемся самостоятельно
                addEventListener(Event.ENTER_FRAME, move);
           }
           // Переместиться
           public function move(e:Event):void {
                x += Velocity.x;
                y += Velocity.y;
           }
           // Самоудалиться. Вызов этой функции прописан в последнем кадре взрыва
           public function remove():void {
                removeEventListener(Event.ENTER_FRAME, move);
                if (parent){ parent.removeChild(this) }
           }
     }
}

После того, как взрыв отрисовался, дымное облако исчезло созданный мувик взрыва нужно удалить. За это отвечает функция remove(). Раньше (в AS2.0) для удаления мувика достаточно было написать

this.removeMovieClip();

В AS3.0 функцию removeMovieClip убрали и мне пока не понятно, как мувику просто удалить самого себя, здесь я делаю таким образом, прошу родителя (parent) удалить своего ребенка, т.е. меня (this).

parent.removeChild(this)

Но только в том случае, если родитель существует

if (parent){ parent.removeChild(this) }

На самом деле его может и не быть, например, если мувик создали, но ни к кому не добавили (не было addChild). Что делать в этом случае не ясно. В общем, с самоудалением как-то все сложно, наверное, есть более простой способ, но он пока мне не известен.

Далее создаем объект «осколок», который должен отлетать от взорвавшегося объекта по заданному вектору и исчезать. Рисуем совсем маленький астероид, лучше в векторе что-то похожее изобразить, все равно разглядеть его будет очень сложно. И пишем класс, где осколок должен сам двигаться в направлении из центра взорвавшегося астероида (преимущественно в том же направлении, в котором летел астероид), проворачиваться вокруг своей оси и через какое-то небольшое время (секунда-полторы) полностью исчезнуть.

// осколок взорвавшегося астероида
package main {
     import flash.display.MovieClip;
     import flash.events.*;

     dynamic public class small_asteroid extends MovieClip {
           const MIN_SPEED:Number = 3;    // раброс начальной скорости
           const MAX_SPEED:Number = 10;
           public var Velocity:Vector; // вектор движения
           public var rot:Number; // направление вращения
           public var cnt:Number; // счетчик до полного изчезновения осколка

           public function init(obj:basic_object) { // ссылка на взорвавшийся объект
                //случайный вектор
                Velocity = new Vector(100*(Math.random()-0.5),100*(Math.random()-0.5));
                // скорость
                var spd:Number = MIN_SPEED + (MAX_SPEED-MIN_SPEED)*Math.random();
                // приведем длину вектора к выбранной скорости
                Velocity.mulScalar( spd / Velocity.magnitude() );
                // складываем с вектором взрывающегося объекта
                // чтобы основная масса осколков летела в том же направлении,
                // что и астероид до этого
                Velocity.addVector(obj.Velocity);

                // позиция из центра астероида
                x = obj.x;
                y = obj.y;
                // зададим случайное направление вращения
                rot = 5*(Math.random()-0.5);

                // сделаем небольшой рандом в размере, чтобы осколки не были похожи друг на друга
                scaleX=scaleY=0.3+Math.random()*1.7;

                // зададим, через сколько "шагов" полностью исчезнуть
                cnt = 10+Math.floor(10*Math.random());

                // двигаемся самостоятельно
                addEventListener(Event.ENTER_FRAME, move);
           }

           // Переместиться
           public function move(e:Event):void {
                x += Velocity.x;
                y += Velocity.y;
                rotation += rot; // и немного повернуться
                if (cnt<5){ // осталось жить очень мало, постепенно исчезаем
                     alpha = cnt/5;
                }
                if (cnt-- <= 0){ // срок жизни вышел. самоудаляемся
                     removeEventListener(Event.ENTER_FRAME, move);
                     if (parent){ parent.removeChild(this) }
                }
           }
     }
}

Обратите внимание, раньше (AS2.0) прозрачность _alpha и масштаб _xscale,_yscale измерялись в процентах 0..100, теперь все переменные, измеряемые в процентах (alpha, scaleX, scaleY, …) имеют диапазон от нуля до единицы 0…1

Осколки никак не влияют на другие астероиды, они не «толкают», отлетают чисто для эффекта, поэтому каждый осколок двигается самостоятельно, и мы их не прописываем в сектора и не просчитываем столкновения.

А вот взрыв создает ударную волну, которая должна оттолкнуть другие астероиды расположенные рядом. Поскольку список астероидов и сектора прописаны в главном классе sky, функцию «оттолкнуть» объекты в радиусе добавим туда же:

// Оттолкнуть объекты находящиееся в радиусе взрыва
// сила взрыва эквивалентна массе астероида
function addExplosion(x:Number, y:Number, massa:Number) {
     var i:Number, j:Number;
     var obj:basic_object, s:String;
     var v:Vector;
     // т.к. один астероид может пересекать несколько секторов
     // для того, чтобы его несколько раз не просчитать
     // записываем уже просчитанные астероиды в "one"
     var one:Object = new Object();
     // считаем, что радиус влияния равен массе взорвавшегося объекта
     // пробегаемся по всем секторам, которые пересекает радиус влияния взрыва
     for (i=Math.floor((x-massa)/100); i<=Math.floor((x+massa)/100); i++){
           for (j=Math.floor((y-massa)/100); j<=Math.floor((y+massa)/100); j++){
                // название сектора
                s = i+"_"+j;
                for each (obj in all_sectors[s]){ // все объекты в секторе
                     if (! one[obj.name]){ // этот объект еще не просчитывали
                            // создаем вектор до объекта
                            v = new Vector(obj.x - x, obj.y - y);
                            // Высчитываем длину вектора воздействия
                            // Уменьшение с расстоянием и нужно учесть разность масс
                            v.mulScalar(100/v.magnitude2() * (massa/obj.getMassa())/2);
                            // прибавляем вектор воздействия взрыва к вектору движения объекта
                            obj.Velocity.addVector(v);
                            // запомним, что этот объект просчитали
                            one[obj.name]=true;
                     }
                }
           }
     }
}

Все готово, теперь в функцию движения всех астероидов sky.Update добавляем проверку, если HP астероида ноль, то удаляем его, создаем эффекты взрыва и отлетающих осколков, а так же вызываем функцию влияния взрывной волны.

if (obj.HP<=0) { // объект мертв, удаляем его
     // удаляем объект из списка живых
     for (i=0; i<all_moving.length; i++){
           if (all_moving[i]==obj){
                all_moving.splice(i,1);
                break;
           }
     }
     // аттачим несколько случайных осколков (5-15)
     i = Math.max(5, Math.floor(Math.random()*15));
     while (i--){
           fragment = new small_asteroid(); // создаем новый осколок
           fragment.init(obj);     // инициализация параметров
           addChild(fragment);     // добавляем его на наш мувиклип
     }
     // аттачим мувик взрыва
     ex_mc = new explosion();
     ex_mc.init(obj);
     addChild(ex_mc);
     // удаляем сам мувик астероида
     removeChild(obj);
     // учитываем влияние ударной волны взрыва на другие объекты
     addExplosion(obj.x, obj.y, obj.getMassa());
}

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

if (obj.x<-400 || obj.x>this.bg.width+400 || obj.y<-400 || obj.y>this.bg.height+400){
     obj.HP=0; // просто взрываем
}

Вторая проблема, если скорость астероида станет слишком низкой, то он практически встанет на месте. Ставим вторую заглушку, если скорость есть, но очень низкая, то увеличим ее, пусть хоть чуть-чуть, но движется.

if (obj.Velocity.x!=0 && obj.Velocity.y!=0 &&
     Math.abs(obj.Velocity.x)<0.4 && Math.abs(obj.Velocity.y)<0.4)
{
     obj.Velocity.mulScalar(1.5); // увеличиваем скорость
}

Давайте посмотрим, что получилось: влетают два встречных потока астероидов, при столкновениях уменьшается уровень жизни, в критическом стостоянии объекты светятся красным, при нулевом уровне жизни взрываются с пылевым облаком. Для теста я добавил, что по клику на астероид он взрывается.

По гибели астероидов почти все, осталось добавить обработку падения астероида на землю. Здесь не нужно рассчитывать столкновения и изменение орбиты вращения планеты. Достаточно нарисовать уменьшающийся астероид, летящий к планете, и после круговой взрыв на самой планете. Фактически это будет уже не обычный астероид, а только анимация падения его на землю. Здесь не нужны столкновения, сектора и т.п., делаем по аналогии с осколком после взрыва. Рисуем на первом кадре тот же мувик с астероидами, который стоит в астероидах, а на втором и далее взрыв… далеко на земле. Пока астероид падает, отображается 1-й кадр и мувик уменьшается, после достижения земли показываем взрыв gotoAndPlay(2). В конце стоит вызов функции remove() чтобы удалить самого себя, аналогично, как и у осколков.

// падение астероида на землю
package main {
     import flash.display.MovieClip;
     import flash.events.*;

     dynamic public class asteroid_fall extends MovieClip {
           public var Velocity:Vector; // вектор движения
           public var rot:Number; // направление вращения
           var earth_obj:basic_object; // ссылка на планету

           // ссылка на астероид и на планету
           public function init(obj:moving_object, earth:basic_object) {
                // копируем вектор движения
                Velocity = new Vector();
                Velocity.copyVector(obj.Velocity);
                // копируем изображение астероида
                this.bg.gotoAndStop(obj.bg.currentFrame);
                this.bg.rotation = obj.bg.rotation;
                // позиция та же
                x = obj.x;
                y = obj.y;
                // вращение в ту же сторону
                rot = obj.rot;
                // радиус такой же
                radius=obj.radius;
                // запоминаем ссылку на объект планеты
                earth_obj = earth;
                // двигаемся самостоятельно
                addEventListener(Event.ENTER_FRAME, move);
                // следующими кадрами у нас нарисован взрыв, а пока отображаем астероид
                stop();
           }

           // Переместиться
           public function move(e:Event):void {
                // при падении на землю, вектор движения должен плавно меняться к центру планеты
                var v:Vector = new Vector(earth_obj.x-x, earth_obj.y-y);
                v.mulScalar(1/earth_obj.radius);
                // а скорость движения должна уменьшаться
                var len = Velocity.magnitude()*0.95;
                Velocity.addVector(v);
                Velocity.mulScalar(len/Velocity.magnitude());
                // сдвигаемся
                x += Velocity.x;
                y += Velocity.y;
                this.bg.rotation += rot; // и немного повернуть изображение
                // уменьшаемся в размере
                scaleX-=0.01;
                scaleY-=0.01;
                if (scaleX < 0.1){ // совсем маленький, пора прекращать движение и рисовать взрыв
                     earth_obj.HP-=2*radius; // уменьшим HP земли, дамаг пропорционален нашему размеру
                     removeEventListener(Event.ENTER_FRAME, move);
                     gotoAndPlay(2);
                     scaleX=scaleY=1;
                }
           }
           // удалить свой мувик
           function remove() {
                stop();
                if (parent){ parent.removeChild(this) }
           }
     }
}

Из добавления красоты, можно у мувика земля (Earth) сделать 100 кадров по степени разрушения планеты и переходить на нужный кадр в зависимости от текущего состояния уровня жизни:

// при изменении уровня жизни земли переходим на соотв. кадр
override function showCriticalHP():void {
     if (!mHP){ // не установлен maxHP
           return;
     }
     gotoAndStop(100-Math.floor(HP/mHP*100));
     super.showCriticalHP();
}


Страницы: 1 2 [3] 4 5