Статьи

Становление теплее: умное наведение на ракеты, ищущие тепло

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

Если вы не читали первое руководство по Homing Missile , вы можете скачать этот файл .zip , который содержит исходный код, с которого мы начнем в этом руководстве.


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


Единственный мувиклип в Библиотеке, который нам нужно изменить, — это « Пушка» , так как мы нацелим ее на ближайшую цель перед стрельбой. Помните, что поворот на 0 ° означает направление вправо, поэтому сделайте изображение соответствующим образом.

Модифицировать Пушку

Я собираюсь повторно использовать переменные targetX и targetY для вычисления расстояния от цели до пушки, поэтому я объявлю их в начале класса, а не внутри функции playGame , а также новую переменную для хранения Расчетное расстояние:

01
02
03
04
05
06
07
08
09
10
11
12
private var missile:Missile = new Missile();
private var speed:int = 15;
private var cannon:Cannon = new Cannon();
private var missileOut:Boolean = false;
private var ease:int = 10;
private var target:Target = new Target();
private var floor:int = 385;
private var gravity:Number = 0.5;
private var targetVY:Number = 0;//Current vertical velocity of the target
private var distance:int;
private var targetX:int;
private var targetY:int;

Теперь переменные targetX и targetY будут объявлены для функции playGame :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private function playGame(event:Event):void
{
    if (missileOut)
    {
        if (missile.hitTestObject(target))
        {
            var explosion:Explosion = new Explosion();
            addChild(explosion);
            explosion.x = missile.x;
            explosion.y = missile.y;
            removeChild(missile);
            missileOut = false;
        }
        else
        {
            targetX = target.x — missile.x;
            targetY = target.y — missile.y;
            var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI;
            if (Math.abs(rotation — missile.rotation) > 180)
            {
                if (rotation > 0 && missile.rotation < 0)
                    missile.rotation -= (360 — rotation + missile.rotation) / ease;
                else if (missile.rotation > 0 && rotation < 0)
                    missile.rotation += (360 — rotation + missile.rotation) / ease;
            }
            else if (rotation < missile.rotation)
                missile.rotation -= Math.abs(missile.rotation — rotation) / ease;
            else
                missile.rotation += Math.abs(rotation — missile.rotation) / ease;
             
            var vx:Number = speed * (90 — Math.abs(missile.rotation)) / 90;
            var vy:Number;
            if (missile.rotation < 0)
                vy = -speed + Math.abs(vx);
            else
                vy = speed — Math.abs(vx);
             
            missile.x += vx;
            missile.y += vy;
        }
    }
    targetVY += gravity;
    target.y += targetVY;
    if (target.y > floor)
    {
        target.y = floor;
        targetVY = -18;
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private function playGame(event:Event):void
{
    if (!missileOut)
    {
        targetX = target.x — cannon.x;
        targetY = target.y — cannon.y;
        cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI;
    }
    else
    {
        if (missile.hitTestObject(target))
        {
            var explosion:Explosion = new Explosion();
            addChild(explosion);
            explosion.x = missile.x;
            explosion.y = missile.y;
            removeChild(missile);
            missileOut = false;
        }
        else
        {
            targetX = target.x — missile.x;
            targetY = target.y — missile.y;
            var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI;
            if (Math.abs(rotation — missile.rotation) > 180)
            {
                if (rotation > 0 && missile.rotation < 0)
                    missile.rotation -= (360 — rotation + missile.rotation) / ease;
                else if (missile.rotation > 0 && rotation < 0)
                    missile.rotation += (360 — rotation + missile.rotation) / ease;
            }
            else if (rotation < missile.rotation)
                missile.rotation -= Math.abs(missile.rotation — rotation) / ease;
            else
                missile.rotation += Math.abs(rotation — missile.rotation) / ease;
             
            var vx:Number = speed * (90 — Math.abs(missile.rotation)) / 90;
            var vy:Number;
            if (missile.rotation < 0)
                vy = -speed + Math.abs(vx);
            else
                vy = speed — Math.abs(vx);
             
            missile.x += vx;
            missile.y += vy;
        }
    }
    targetVY += gravity;
    target.y += targetVY;
    if (target.y > floor)
    {
        target.y = floor;
        targetVY = -18;
    }
}

Теперь пушка вращается относительно позиции цели.


Пушка вращается, но ракета продолжает стрелять вверх. Замените жестко закодированное вращение текущим местоположением пушки в момент выстрела ракеты.

01
02
03
04
05
06
07
08
09
10
11
12
private function shoot(event:MouseEvent):void
{
    if (!missileOut)
    {
        addChild(missile);
        swapChildren(missile, cannon);//missile will come out from behind cannon
        missileOut = true;
        missile.x = cannon.x;
        missile.y = cannon.y;
        missile.rotation = cannon.rotation;
    }
}

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


Прямо сейчас самонаводящаяся ракета — это программа, преследующая одну цель, но что, если у нас будет больше целей? Как это решит, какой идти после?

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private var target:Target;
private var floor:int = 385;
private var gravity:Number = 0.5;
private var targetVY:Number = 0;//Current vertical velocity of the target
private var distance:int;
private var targetX:int;
private var targetY:int;
private var numTargets:int = 2;
private var targets:Array = [];
 
public function Main()
{
    addChild(cannon);
    cannon.x = 50;
    cannon.y = 380;
    addEventListener(Event.ENTER_FRAME, playGame);
    stage.addEventListener(MouseEvent.CLICK, shoot);
    for (var i:int = 0; i < numTargets; i++)
    {
        target = new Target();
        addChild(target);
        target.x = Math.random() * 600;
        target.y = Math.random() * 400;
        targets.push(target);
    }
}

Теперь у нас есть несколько целей на экране.

Установите количество целей, чтобы появиться.

Ракета все еще только подтверждает существование одной цели. Мы исправим это дальше.


У нас есть ракета, ищущая переменную цели , поэтому давайте проверим массив targets и посмотрим, какая из них ближе. Целевая переменная будет ссылаться на ближайшую в начале функции playGame .

01
02
03
04
05
06
07
08
09
10
11
12
13
private function playGame(event:Event):void
{
    for (var i:int = 0; i < targets.length; i++)
    {
        targetX = targets[i].x — missile.x;
        targetY = targets[i].y — missile.y;
        var dist:int = Math.sqrt(targetX * targetX + targetY * targetY);//the distance from one point to another in a 2D space.
        if (i == 0 || dist < distance)
        {
            distance = dist;
            target = targets[i];
        }
    }

На данный момент ближайшая цель — единственная движущаяся, но ракета признает существование обоих:


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
private function playGame(event:Event):void
{
    for (var i:int = 0; i < targets.length; i++)
    {
        targetX = targets[i].x — missile.x;
        targetY = targets[i].y — missile.y;
        var dist:int = Math.sqrt(targetX * targetX + targetY * targetY);
        if (i == 0 || dist < distance)
        {
            distance = dist;
            target = targets[i];
        }
    }
    if (!missileOut)
    {
        missile.x = cannon.x;
        missile.y = cannon.y;
        targetX = target.x — cannon.x;
        targetY = target.y — cannon.y;
        cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI;
    }

Теперь пушка всегда будет стремиться к ближайшей цели.


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

1
2
3
4
private function playGame(event:Event):void
{
    cannon.x = mouseX;
    cannon.y = mouseY;

Теперь вы можете свободно перемещать пушку.


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
if (missile.hitTestObject(target))
{
    var explosion:Explosion = new Explosion();
    addChild(explosion);
    explosion.x = missile.x;
    explosion.y = missile.y;
    removeChild(missile);
    missileOut = false;
    explosion= new Explosion();
    addChild(explosion);
    explosion.x = target.x;
    explosion.y = target.y;
    explosion.scaleX = explosion.scaleY = 1.5;
    target.x = Math.random() * 600;
}

Вот что вы получите:


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

1
private var missiles:Array = [];

Затем нам нужно убедиться, что все ракеты ведут себя правильно:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
private function playGame(event:Event):void
{
    cannon.x = mouseX;
    cannon.y = mouseY;
    for (var i:int = 0; i < targets.length; i++)
    {
        targetX = targets[i].x — missile.x;
        targetY = targets[i].y — missile.y;
        var dist:int = Math.sqrt(targetX * targetX + targetY * targetY);
        if (i == 0 || dist < distance)
        {
            distance = dist;
            target = targets[i];
        }
    }
    if (!missileOut)
    {
        missile.x = cannon.x;
        missile.y = cannon.y;
        targetX = target.x — cannon.x;
        targetY = target.y — cannon.y;
        cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI;
    }
    else
    {
        for (i = 0; i < missiles.length; i++)//each missile must keep moving
        {
            missile = missiles[i];
            if (missile.hitTestObject(target))
            {
                var explosion:Explosion = new Explosion();
                addChild(explosion);
                explosion.x = missile.x;
                explosion.y = missile.y;
                removeChild(missile);
                missiles.splice(i, 1);//out of the Array
                if (missiles.length < 1)//only if no missiles are out at all
                    missileOut = false;
                explosion= new Explosion();
                addChild(explosion);
                explosion.x = target.x;
                explosion.y = target.y;
                explosion.scaleX = explosion.scaleY = 1.5;
                target.x = Math.random() * 600;
            }
            else
            {
                targetX = target.x — missile.x;
                targetY = target.y — missile.y;
                var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI;
                if (Math.abs(rotation — missile.rotation) > 180)
                {
                    if (rotation > 0 && missile.rotation < 0)
                        missile.rotation -= (360 — rotation + missile.rotation) / ease;
                    else if (missile.rotation > 0 && rotation < 0)
                        missile.rotation += (360 — rotation + missile.rotation) / ease;
                }
                else if (rotation < missile.rotation)
                    missile.rotation -= Math.abs(missile.rotation — rotation) / ease;
                else
                    missile.rotation += Math.abs(rotation — missile.rotation) / ease;
                 
                var vx:Number = speed * (90 — Math.abs(missile.rotation)) / 90;
                var vy:Number;
                if (missile.rotation < 0)
                    vy = -speed + Math.abs(vx);
                else
                    vy = speed — Math.abs(vx);
                 
                missile.x += vx;
                missile.y += vy;
            }
        }
    }
    targetVY += gravity;
    target.y += targetVY;
    if (target.y > floor)
    {
        target.y = floor;
        targetVY = -18;
    }
}
 
private function shoot(event:MouseEvent):void
{
    missile = new Missile();
    missiles.push(missile);//into the Array
    addChild(missile);
    swapChildren(missile, cannon);//missile will come out from behind cannon
    missileOut = true;
    missile.x = cannon.x;
    missile.y = cannon.y;
    missile.rotation = cannon.rotation;
}

Теперь, когда цель уничтожена, ракеты будут искать следующую цель.

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


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

Crosshair Movie Clip.

Теперь всем станет очевидно, на кого нацелена цель. Просто добавьте экземпляр Crosshair Movie Clip.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private var crosshair:Crosshair = new Crosshair();
 
public function Main()
{
    addChild(cannon);
    cannon.x = 50;
    cannon.y = 380;
    addEventListener(Event.ENTER_FRAME, playGame);
    stage.addEventListener(MouseEvent.CLICK, shoot);
    for (var i:int = 0; i < numTargets; i++)
    {
        target = new Target();
        addChild(target);
        target.x = Math.random() * 600;
        target.y = Math.random() * 400;
        targets.push(target);
    }
    addChild(crosshair);
}

Затем поместите его на позицию цели в качестве последней инструкции в функции playGame .

01
02
03
04
05
06
07
08
09
10
    targetVY += gravity;
    target.y += targetVY;
    if (target.y > floor)
    {
        target.y = floor;
        targetVY = -18;
    }
    crosshair.x = target.x;
    crosshair.y = target.y;
}

Вы получите перекрестие, отмечающее положение ближайшей цели.

Ближайшая цель отмечена перекрестием.

Помните, что я сказал о ракетах? То же самое относится и к целям: они будут выглядеть лучше в своем классе с набором собственных инструкций. Это только быстрый пример, но в вашей игре я не рекомендую кодировать все объекты в классе Main . Чем сложнее ваша игра, тем меньше кода вы будете кодировать в классе Main .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
private function playGame(event:Event):void
{
    cannon.x = mouseX;
    cannon.y = mouseY;
    targetVY += gravity;
    for (var i:int = 0; i < targets.length; i++)
    {
        targetX = targets[i].x — missile.x;
        targetY = targets[i].y — missile.y;
        var dist:int = Math.sqrt(targetX * targetX + targetY * targetY);
        if (i == 0 || dist < distance)
        {
            distance = dist;
            target = targets[i];
        }
        targets[i].y += targetVY;
        if (targets[i].y > floor)
            targets[i].y = floor;
    }
    if (target.y >= floor)
        targetVY = -18;
    if (!missileOut)
    {
        missile.x = cannon.x;
        missile.y = cannon.y;
        targetX = target.x — cannon.x;
        targetY = target.y — cannon.y;
        cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI;
    }
    else
    {
        for (i = 0; i < missiles.length; i++)
        {
            missile = missiles[i];
            if (missile.hitTestObject(target))
            {
                var explosion:Explosion = new Explosion();
                addChild(explosion);
                explosion.x = missile.x;
                explosion.y = missile.y;
                removeChild(missile);
                missiles.splice(i, 1);
                if (missiles.length < 1)
                    missileOut = false;
                explosion= new Explosion();
                addChild(explosion);
                explosion.x = target.x;
                explosion.y = target.y;
                explosion.scaleX = explosion.scaleY = 1.5;
                target.x = Math.random() * 600;
            }
            else
            {
                targetX = target.x — missile.x;
                targetY = target.y — missile.y;
                var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI;
                if (Math.abs(rotation — missile.rotation) > 180)
                {
                    if (rotation > 0 && missile.rotation < 0)
                        missile.rotation -= (360 — rotation + missile.rotation) / ease;
                    else if (missile.rotation > 0 && rotation < 0)
                        missile.rotation += (360 — rotation + missile.rotation) / ease;
                }
                else if (rotation < missile.rotation)
                    missile.rotation -= Math.abs(missile.rotation — rotation) / ease;
                else
                    missile.rotation += Math.abs(rotation — missile.rotation) / ease;
                 
                var vx:Number = speed * (90 — Math.abs(missile.rotation)) / 90;
                var vy:Number;
                if (missile.rotation < 0)
                    vy = -speed + Math.abs(vx);
                else
                    vy = speed — Math.abs(vx);
                 
                missile.x += vx;
                missile.y += vy;
            }
        }
    }
    crosshair.x = target.x;
    crosshair.y = target.y;
}

Взглянем:


Самонаводящиеся ракеты, ракеты, ищущие тепло, и то, и другое — забавное и полезное оружие, которое можно использовать в стрельбе или, возможно, в каком-либо другом приложении. В этом руководстве показан пример его использования и алгоритм его создания, но для передовой практики рекомендуется иметь отдельные классы для ракет и целей, если ваше приложение не такое простое и короткое, как это.

Надеюсь, вы нашли этот урок полезным. Спасибо за прочтение!