Статьи

Введение в FlashPunk: создание космического корабля Shoot-‘Em-Up

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


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


Как всегда, сначала нам нужен чистый проект. Возьмите последнюю версию FlashPunk с официального сайта . Создайте новый проект AS3 в FlashDevelop и поместите исходный код FlashPunk в исходную папку проекта. Игра будет иметь следующие размеры: 400х500 пикселей.

Мы начнем нашу игру с добавления корабля — корабля игрока — на экран. Для этого нам понадобится новый мир под названием GameWorld и изображение:

Изображение корабля игрока

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package
{
    import net.flashpunk.Entity;
    import net.flashpunk.graphics.Image;
     
    public class PlayerShip extends Entity
    {
        [Embed(source = ‘../img/PlayerShipImage.png’)]
        private const IMAGE:Class;
         
        public function PlayerShip()
        {
            graphic = new Image(IMAGE);
             
            graphic.x = -27.5;
            graphic.y = -30;
             
            x = 200;
            y = 400;
        }
         
    }
 
}

Добавьте корабль игрока в мир и сделайте его текущим, когда запустится Engine FlashPunk. Вы получите следующее:

Корабль игрока в игре

С игроком корабль на экране, мы должны заставить его двигаться. Точно так же, как и в любой другой игре-стрелялке, корабль-игрок сможет перемещаться по всему экрану. Осталось решить одну вещь, прежде чем кодировать движение: мы будем использовать основанное на кадре движение (причина этого в следующем шаге). Это означает изменение (если необходимо) вызова super() к движку FlashPunk, а НЕ использование свойства FP.elapsed .

Код для движения корабля игрока ниже. Движение основано на мышах:

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 _currentDistanceX:Number;
private var _currentDistanceY:Number;
 
private const SPEED:int = 3;
 
override public function update():void
{
    calculateDistances();
     
    if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED)
    {
        x = Input.mouseX;
        y = Input.mouseY;
    }
    else
    {
        x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
        y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
    }
}
 
private function calculateDistances():void
{
    _currentDistanceX = Input.mouseX — x;
    _currentDistanceY = Input.mouseY — y;
}

Вам нужно будет импортировать net.flashpunk.utils.Input в класс корабля игрока. Также не забудьте включить консоль FlashPunk. После компиляции проекта вы увидите корабль игрока, следующий за мышью:


Время добавить что-то веселое: враги. Враги должны появляться случайным образом (на основе волн) и иметь контролируемое движение. У них также есть другое изображение:

Образ врага

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

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

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

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
package
{
    import flash.geom.Point;
    import net.flashpunk.Entity;
    import net.flashpunk.FP;
    import net.flashpunk.graphics.Image;
    import net.flashpunk.World;
     
    public class Enemy extends Entity
    {
        [Embed(source = ‘../img/EnemyImage.png’)]
        private const IMAGE:Class;
         
        private var _timeToAct:uint;
        private var _pathToFollow:Vector.<Point>;
         
        private var _currentPoint:uint;
         
        private var _myWorld:World;
        private var _added:Boolean;
         
        public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World)
        {
            graphic = new Image(IMAGE);
             
            graphic.x = -15;
            graphic.y = -8;
             
            _timeToAct = timeToAct;
             
            _pathToFollow = pathToFollow;
             
            _currentPoint = 0;
             
            _myWorld = worldToBeAdded;
            _added = false;
        }
         
        override public function update():void
        {
            if (_timeToAct > 0)
            {
                _timeToAct—;
            }
            else
            {
                if (!_added)
                {
                    _myWorld.add(this);
                     
                    _added = true;
                }
                 
                x = _pathToFollow[_currentPoint].x;
                y = _pathToFollow[_currentPoint].y;
                 
                _currentPoint++;
                 
                if (_currentPoint == _pathToFollow.length)
                {
                    _myWorld.remove(this);
                     
                    _added = false;
                     
                    destroy();
                }
            }
        }
         
        public function destroy():void
        {
            graphic = null;
        }
         
    }
 
}

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


Наш базовый класс Enemy готов. Теперь пришло время немного изменить GameWorld чтобы добавить врагов. Первая задача — создать пути для врагов. Для целей данного урока мы будем создавать только прямую линию, но не стесняйтесь пытаться создать любой тип волнового пути. Это функция, которая создает прямую линию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function generateEnemyPath(distanceBetweenPoints:Number):Vector.<Point>
{
    var i:Number;
     
    var vec:Vector.<Point> = new Vector.<Point>();
     
    var xPos:Number = Math.random() * 360 + 20;
     
    for (i = -20; i < 520; i += distanceBetweenPoints)
    {
        vec.push(new Point(xPos, i));
    }
     
    return vec;
}

При этом мы уже можем дать врагу путь, которым нужно следовать. Следующий шаг — создать врага:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private var _enemy:Enemy;
 
public function GameWorld()
{
    _playerShip = new PlayerShip();
     
    add(_playerShip);
     
    _enemy = new Enemy(0, generateEnemyPath(1), this);
}
 
override public function update():void
{
    super.update();
     
    if (_enemy)
        _enemy.update();
}

Скомпилируйте и запустите игру. Вы, вероятно, получите следующую ошибку:

1
[Fault] exception, information=RangeError: Error #1125: The index 540 is out of range 540.

Это происходит потому, что даже после того, как враг удаляет себя из мира, мы все еще вызываем его функцию update() , потому что наш код не обнаружил, когда враг удалил себя. Давайте исправим это, переопределив текущий метод remove() :

1
2
3
4
5
6
7
8
9
override public function remove(e:Entity):Entity
{
    if (e is Enemy)
    {
        _enemy = new Enemy(0, generateEnemyPath(1), this);
    }
     
    return super.remove(e);
}

Теперь скомпилируйте проект, и вы увидите это:

Это то, что делает эта функция: каждый раз, когда враг удаляется от мира, мы обнаруживаем это с помощью нашей переопределенной функции, а затем просто создаем другого врага, чтобы «заменить» старого. Это оно! Теперь у нас есть враги, движущиеся по экрану!


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

Стратегия здесь состоит в том, чтобы сгенерировать паттерн пули вокруг фиксированной оси X и Y, а затем суммировать позицию корабля игрока с точками из паттерна, таким образом перемещая ось в новую позицию, создавая впечатление, что пули идут из корабля игрока. Изображение пули, которое мы собираемся использовать, таково:

Пуля игрока

Все становится проще, когда вы смотрите на код. Мы в основном делаем что-то очень похожее на вражеский путь:

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
package
{
    import flash.geom.Point;
    import net.flashpunk.Entity;
    import net.flashpunk.graphics.Image;
 
    public class PlayerBullet extends Entity
    {
        [Embed(source = ‘../img/BulletImage.png’)]
        private const IMAGE:Class;
         
        private var _pathToFollow:Vector.<Point>;
         
        private var _xPos:Number;
        private var _yPos:Number;
         
        public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number)
        {
            graphic = new Image(IMAGE);
             
            graphic.x = graphic.y = -3.5;
             
            _pathToFollow = pathToFollow;
             
            _xPos = xPos;
            _yPos = yPos;
        }
         
        override public function update():void
        {
            x = _xPos + _pathToFollow[0].x;
            y = _yPos + _pathToFollow[0].y;
             
            _pathToFollow.shift();
             
            if (_pathToFollow.length == 0)
            {
                world.remove(this);
                 
                destroy();
            }
        }
         
        public function destroy():void
        {
            _pathToFollow = null;
             
            graphic = null;
        }
         
    }
 
}

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

Давайте попробуем добавить пулю, когда игрок щелкает мышью. В PlayerShip.as :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
override public function update():void
{
    calculateDistances();
     
    if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED)
    {
        x = Input.mouseX;
        y = Input.mouseY;
    }
    else
    {
        x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
        y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
    }
     
    if (Input.mousePressed)
    {
        world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y));
    }
}

Теперь нам нужно создать путь пули. Мы собираемся создать прямую линию, как для врага, но вы можете пройти любой путь! В GameWorld.as давайте создадим функцию generateBulletPath() :

01
02
03
04
05
06
07
08
09
10
11
12
13
public function generateBulletPath(distanceBetweenPoints:Number):Vector.<Point>
{
    var i:Number;
     
    var vec:Vector.<Point> = new Vector.<Point>();
     
    for (i = 0; i > -500; i -= distanceBetweenPoints)
    {
        vec.push(new Point(0, i));
    }
     
    return vec;
}

После этого нажмите кнопку «Скомпилировать и запустить», и вот что вы получите:


Теперь у нас есть основы игры: корабль игрока, который стреляет, и врагов, которые спускаются по экрану. Время добавить обнаружение столкновений!

Первый шаг для добавления обнаружения коллизий — дать каждому объекту тип. Я оставлю это вам: передайте тип «Player» PlayerShip , «Enemy» Enemy и «PlayerBullet» PlayerBullet .

В этой игре мы будем использовать идеальное столкновение пикселей, поэтому может быть полезно поговорить о масках. Маски — это элементы, используемые FlashPunk для обнаружения столкновений. Они в основном похожи на хитбоксы, но могут иметь различную форму (уровень пикселей). Нам нужно настроить маски для игрока корабля, врагов и пуль. Изображение, используемое маской, является тем же изображением сущности. Посмотрите на код для PlayerShip , Enemy и PlayerBullet , соответственно:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public function PlayerShip()
{
    graphic = new Image(IMAGE);
     
    graphic.x = -27.5;
    graphic.y = -30;
     
    mask = new Pixelmask(IMAGE, -27.5, -30);
     
    x = 200;
    y = 400;
     
    type = «Player»;
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World)
{
    graphic = new Image(IMAGE);
     
    graphic.x = -15;
    graphic.y = -8;
     
    mask = new Pixelmask(IMAGE, -15, -8);
     
    _timeToAct = timeToAct;
     
    _pathToFollow = pathToFollow;
     
    _currentPoint = 0;
     
    _myWorld = worldToBeAdded;
    _added = false;
     
    type = «Enemy»;
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number)
{
    graphic = new Image(IMAGE);
     
    graphic.x = graphic.y = -3.5;
     
    mask = new Pixelmask(IMAGE, -3.5, -3.5);
     
    _pathToFollow = pathToFollow;
     
    _xPos = xPos;
    _yPos = yPos;
     
    type = «PlayerBullet»;
}

Как видите, это очень просто: мы создаем новую Pixelmask , передаем источник для использования в качестве маски (как с графикой), а затем передаем оба смещения по x и y (на случай, если вы хотите Pixelmask маску где-нибудь) , Теперь в GameWorld.as :

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
private var _bulletList:Vector.<PlayerBullet>;
 
override public function update():void
{
    super.update();
     
    if (_enemy)
        _enemy.update();
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        if (bullet.collideWith(_enemy, bullet.x, bullet.y))
        {
            _enemy.takeDamage();
             
            remove(bullet);
             
            bullet.destroy();
        }
    }
}

Обратите внимание, что мы могли бы просто использовать _enemy.collide("PlayerBullet", _enemy.x, _enemy.y) для проверки столкновений, но описанный выше метод лучше, когда у нас на экране много пуль, и есть вероятность, что две пули ударить того же врага в то же время. Мы вызвали takeDamage() класса Enemy , но на данный момент ее нет. (Создайте пока пустую функцию. На следующем шаге мы заставим врага нанести урон и взорваться при необходимости.) Скомпилируйте проект, и вы получите следующее:


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

Взрыв после смерти врага

Подход, который мы предпримем для этого, заключается в уменьшении здоровья противника в функции takeDamage() , и если здоровье станет меньше нуля, мы уничтожим его и поместим анимацию на экран. Код для уменьшения здоровья ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private var _health:int = 100;
 
public function takeDamage():void
{
    _health -= 50;
     
    if (_health <= 0)
    {
        addExplosion();
         
        _myWorld.remove(this);
         
        _added = false;
         
        destroy();
    }
}

Код очень прост. Есть только одна вещь, неизвестная об этом: addExplosion() . Эта функция создаст экземпляр Explosion и добавит его в мир. После этого класс « Explosion » будет просто играть и удаляться из мира. Это простой класс:

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
package
{
    import net.flashpunk.Entity;
    import net.flashpunk.graphics.Spritemap;
 
    public class Explosion extends Entity
    {
        [Embed(source = ‘../img/ExplosionAnimation.png’)]
        private const ANIMATION:Class;
         
        public function Explosion(xPos:Number, yPos:Number)
        {
            graphic = new Spritemap(ANIMATION, 50, 46, onAnimationEnd);
             
            graphic.x = -25;
            graphic.y = -23;
             
            x = xPos;
            y = yPos;
             
            Spritemap(graphic).add(«Explode», [0, 1, 2, 3, 4], 25/60, false);
             
            Spritemap(graphic).play(«Explode»);
        }
         
        private function onAnimationEnd():void
        {
            world.remove(this);
             
            destroy();
        }
         
        public function destroy():void
        {
            graphic = null;
        }
         
    }
 
}

Хитрость здесь в том, чтобы использовать параметр callback класса Spritemap : когда анимация заканчивается, Spritemap эта функция, а затем она удаляет себя из мира.

Теперь вернемся к Enemy.as чтобы завершить эту функцию!

1
2
3
4
private function addExplosion():void
{
    world.add(new Explosion(x, y));
}

Легко, не правда ли? Скомпилируйте игру и уничтожьте нескольких врагов!


Следующим логическим шагом после смерти врагов является добавление очков в игру! Мы сделаем это с помощью класса Text FlashPunk. Начните с создания класса GameScore , который будет содержать счет игры. Поскольку Text является Graphic , мы сделаем GameScore и добавим его текст в виде graphic . Посмотрите на код:

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
package
{
    import net.flashpunk.Entity;
    import net.flashpunk.graphics.Text;
 
    public class GameScore extends Entity
    {
        private var _score:int;
         
        public function GameScore()
        {
            graphic = new Text(«Score: 0»);
             
            _score = 0;
        }
         
        public function addScore(points:int):void
        {
            _score += points;
             
            Text(graphic).text = «Score: » + _score.toString();
        }
         
        public function destroy():void
        {
            graphic = null;
        }
         
    }
 
}

Как вы можете видеть, мы будем вызывать addScore() чтобы добавить очки к счету игры. Во-первых, нам нужно добавить его в мир. В GameWorld.as :

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
private var _score:GameScore;
 
public function GameWorld()
{
    _playerShip = new PlayerShip();
     
    add(_playerShip);
     
    _enemy = new Enemy(0, generateEnemyPath(1), this);
     
    _score = new GameScore();
     
    _score.x = 300;
    _score.y = 470;
     
    add(_score);
}
 
public function get score():GameScore
{
    return _score;
}
 
public function get score():int
{
    return _score;
}

Если мы нажмем на compile, мы получим статическую оценку в нижней части экрана:

Счет игры

Мы должны добавлять что-то к счету каждый раз, когда враг убит. В Enemy.as :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public function takeDamage():void
{
    _health -= 50;
     
    if (_health <= 0)
    {
        addExplosion();
         
        GameWorld(world).score.addScore(1);
         
        _myWorld.remove(this);
         
        _added = false;
         
        destroy();
    }
}

Нажмите «Компилировать» сейчас, и счет всегда будет увеличиваться, когда враг убит!


Время добавлять обновления! У нас не будет красивого экрана для выбора обновлений. Вместо этого мы будем создавать улучшения, основанные на счете: каждый раз, когда счет увеличивается на 5 (до 45), скорость игрока немного увеличивается. Когда счет достигнет 25, игрок сможет сделать два выстрела на каждом клике. Мы сделаем счет 50 в конце игры.

Начнем с кодирования скорости игрока. Для этого нам нужно будет добавить множитель к скорости. В PlayerShip.as :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public var speedMultiplier:Number = 1;
 
override public function update():void
{
    calculateDistances();
     
    if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
    {
        x = Input.mouseX;
        y = Input.mouseY;
    }
    else
    {
        x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
        y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
    }
     
    if (Input.mousePressed)
    {
        world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y));
    }
}

Теперь все об изменении множителя в GameWorld.as :

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
private var _speedUpgradeNumber:int = 0;
 
override public function update():void
{
    super.update();
     
    if (_enemy)
        _enemy.update();
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        if (bullet.collideWith(_enemy, bullet.x, bullet.y))
        {
            _enemy.takeDamage();
             
            remove(bullet);
             
            bullet.destroy();
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
}

Выполнено! Теперь каждые 5 смертей противника игрок будет получать 10% увеличение скорости движения!

Для обновления с использованием двойной пули мы сделаем логическое значение в классе PlayerShip указывающее, есть ли на корабле обновление. Тогда мы проверим это при съемке. Вот:

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
public var hasDoubleShoot:Boolean = false;
 
override public function update():void
{
    calculateDistances();
     
    if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
    {
        x = Input.mouseX;
        y = Input.mouseY;
    }
    else
    {
        x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
        y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
    }
     
    if (Input.mousePressed)
    {
        if (hasDoubleShoot)
        {
            world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x — 5, y));
            world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x + 5, y));
        }
        else
        {
            world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y));
        }
    }
}

Теперь давайте сделаем то же самое, что мы сделали для скорости в GameWorld.as :

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
override public function update():void
{
    super.update();
     
    if (_enemy)
        _enemy.update();
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        if (bullet.collideWith(_enemy, bullet.x, bullet.y))
        {
            _enemy.takeDamage();
             
            remove(bullet);
             
            bullet.destroy();
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
}

Вот и все! Нажмите «Компилировать», и ваша игра будет обновлена!


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

В этом и заключается идея: игра теперь сможет вывести на экран более одного врага. И для каждого убийства, которое получает игрок, мы уменьшаем таймер появления нового врага. Интересно, не правда ли? Но это не единственное. А как насчет увеличения здоровья врагов и уменьшения урона, который они получают от игрока? Теперь мы куда-то добираемся!

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

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
private var _health:int;
 
public static var healthMultiplier:Number = 1;
public static var damageMultiplier:Number = 1;
 
public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World)
{
    graphic = new Image(IMAGE);
     
    graphic.x = -15;
    graphic.y = -8;
     
    mask = new Pixelmask(IMAGE, -15, -8);
     
    _timeToAct = timeToAct;
     
    _pathToFollow = pathToFollow;
     
    _currentPoint = 0;
     
    _myWorld = worldToBeAdded;
    _added = false;
     
    type = «Enemy»;
     
    health = 100 * Enemy.healthMultiplier;
}
 
public function takeDamage():void
{
    _health -= 50 * Enemy.damageMultiplier;
     
    if (_health <= 0)
    {
        addExplosion();
         
        GameWorld(world).score.addScore(1);
         
        _myWorld.remove(this);
         
        _added = false;
         
        destroy();
    }
}

А теперь в GameWorld.as :

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
private var _enemyUpgradeNumber:int = 0;
 
override public function update():void
{
    super.update();
     
    if (_enemy)
        _enemy.update();
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        if (bullet.collideWith(_enemy, bullet.x, bullet.y))
        {
            _enemy.takeDamage();
             
            remove(bullet);
             
            bullet.destroy();
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
     
    if (_score.score > _enemyUpgradeNumber)
    {
        Enemy.damageMultiplier -= 0.015;
        Enemy.healthMultiplier += 0.02;
         
        _enemyUpgradeNumber++;
    }
}

И теперь наши враги становятся сильнее после каждого убийства! Возьми это, злой игрок!

Теперь нам нужно изменить GameWorld , чтобы порождать врагов в зависимости от времени. Это простая вещь: нам просто понадобится таймер и интервал появления. Вот весь модифицированный код:

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
private var _enemyList:Vector.<Enemy>;
 
private var _enemySpawnInterval:int = 5000;
private var _enemySpawnTimer:int;
 
public function GameWorld()
{
    _playerShip = new PlayerShip();
     
    add(_playerShip);
     
    _enemyList = new Vector.<Enemy>();
     
    _score = new GameScore();
     
    _score.x = 300;
    _score.y = 470;
     
    add(_score);
     
    _enemySpawnTimer = 0;
}
 
override public function update():void
{
    super.update();
     
    _enemySpawnTimer—;
     
    if (_enemySpawnTimer <= 0)
    {
        _enemySpawnTimer = _enemySpawnInterval;
         
        _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
         
        add(_enemyList[_enemyList.length — 1]);
    }
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        for each (var enemy:Enemy in _enemyList)
        {
            if (bullet.collideWith(enemy, bullet.x, bullet.y))
            {
                enemy.takeDamage();
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
     
    if (_score.score > _enemyUpgradeNumber)
    {
        Enemy.damageMultiplier -= 0.015;
        Enemy.healthMultiplier += 0.02;
         
        _enemyUpgradeNumber++;
    }
}
 
override public function remove(e:Entity):Entity
{
    if (e is Enemy)
    {
        _enemyList.splice(_enemyList.indexOf(e), 1);
    }
     
    return super.remove(e);
}

Обратите внимание, что я дал врагам несколько случайных кадров «времени ожидания» перед тем, как появиться. Это сделает их появление непредсказуемым. Теперь осталось только уменьшить интервал появления после каждого убийства:

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
override public function update():void
{
    super.update();
     
    _enemySpawnTimer—;
     
    if (_enemySpawnTimer <= 0)
    {
        _enemySpawnTimer = _enemySpawnInterval;
         
        _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
         
        add(_enemyList[_enemyList.length — 1]);
    }
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        for each (var enemy:Enemy in _enemyList)
        {
            if (bullet.collideWith(enemy, bullet.x, bullet.y))
            {
                enemy.takeDamage();
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
     
    if (_score.score > _enemyUpgradeNumber)
    {
        Enemy.damageMultiplier -= 0.015;
        Enemy.healthMultiplier += 0.02;
         
        _enemySpawnInterval -= 3;
         
        _enemyUpgradeNumber++;
    }
}

С этим у нас в основном все сделано в игровом мире!


Я думаю, что у нас все закончено в игровом мире. Игра на удивление сложна с более жесткими врагами! Что вы думаете о создании хорошего главного меню сейчас? Я создал хороший фон для этого:

Экран главного меню

Создайте класс net.flashpunk.World , который происходит от net.flashpunk.World , и добавьте в него фон. Я оставлю код для вас. Нам понадобится кнопка воспроизведения, которую я тоже создал:

Кнопка воспроизведения

Чтобы создать кнопку воспроизведения, мы будем использовать класс Button созданный в первой части этого урока. Вот код для кнопки в MainMenuWorld.as :

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
[Embed(source = ‘../img/PlayGameButton.png’)]
private const PLAYBUTTON:Class;
 
private var _playButton:Button;
 
public function MainMenuWorld()
{
    addGraphic(new Image(TITLE));
     
    _playButton = new Button(playTheGame, null, 48, 395);
     
    _playButton.setSpritemap(PLAYBUTTON, 312, 22);
     
    add(_playButton);
}
 
private function playTheGame():void
{
    FP.world = new GameWorld();
     
    destroy();
}
 
public function destroy():void
{
    removeAll();
     
    _playButton = null;
}

Не забудьте также изменить Main класс!

1
2
3
4
5
6
override public function init():void
{
    FP.world = new MainMenuWorld();
     
    FP.console.enable();
}

Хит скомпилировать и … Да! Наш блестящий мир главного меню работает! Теперь к игре по всему миру!


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

Игра закончена, когда игрок проигрывает
Игра закончена, когда игрок выигрывает
Кнопка выхода

Я оставлю вам кодировку. Единственное, что изменится в этом классе, — это то, что ему понадобится аргумент, переданный конструктору, указывающий, уничтожил ли игрок врагов. Вот код для конструктора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public function GameOverWorld(hasLost:Boolean)
{
    if (hasLost)
    {
        addGraphic(new Image(BACKGROUNDLOST));
    }
    else
    {
        addGraphic(new Image(BACKGROUNDWON));
    }
     
    _quitButton = new Button(quitToMain, null, 166, 395);
     
    _quitButton.setSpritemap(QUITBUTTON, 69, 19);
     
    add(_quitButton);
}

Наконец, момент, которого все ждали. Каждому стрелку нужен босс, и это наш!

Босс!  Убей его!

Что нам нужно сейчас, это код. Во-первых, движения. А потом пули. Этот шаг для движений.

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

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
package
{
    import flash.geom.Point;
    import net.flashpunk.Entity;
    import net.flashpunk.graphics.Image;
    import net.flashpunk.masks.Pixelmask;
 
    public class Boss extends Entity
    {
        [Embed(source = ‘../img/BossImage.png’)]
        private const IMAGE:Class;
         
        private var _currentDistanceX:Number;
        private var _currentDistanceY:Number;
         
        private var _randomPoint:Point;
         
        private const SPEED:int = 3;
         
        public var speedMultiplier:Number = 1;
         
        public function Boss()
        {
            graphic = new Image(IMAGE);
             
            graphic.x = -38;
            graphic.y = -35;
             
            mask = new Pixelmask(IMAGE, -38, -35);
             
            type = «BossEnemy»;
             
            getRandomPoint();
        }
         
        private function getRandomPoint():void
        {
            _randomPoint = new Point();
             
            _randomPoint.x = Math.random() * 400;
            _randomPoint.y = 38 + (Math.random() * 100);
        }
         
        override public function update():void
        {
            calculateDistances();
             
            if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
            {
                x = _randomPoint.x;
                y = _randomPoint.y;
                 
                getRandomPoint();
            }
            else
            {
                x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
                y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
            }
        }
         
        private function calculateDistances():void
        {
            _currentDistanceX = _randomPoint.x — x;
            _currentDistanceY = _randomPoint.y — y;
        }
         
    }
 
}

Мы в основном используем тот же код движения от PlayerShip . Обратите внимание, что мы сохранили множители скорости, потому что в итоге будет действительно весело!

Теперь нам нужно найти способ проверить это движение. Запуск игры и получение 50 убийств — это слишком много времени, чтобы ждать, пока мы не увидим босса (и в итоге поймем, что есть ошибка, и нам нужно будет делать это снова и снова!) Давайте просто добавим босса на экран, когда игра начнется (да, другие враги идут вниз по экрану!) И проверим движение! В GameWorld.as :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private var _boss:Boss;
 
public function GameWorld()
{
    _playerShip = new PlayerShip();
     
    add(_playerShip);
     
    _enemyList = new Vector.<Enemy>();
     
    _score = new GameScore();
     
    _score.x = 300;
    _score.y = 470;
     
    add(_score);
     
    _enemySpawnTimer = 0;
     
    _boss = new Boss();
     
    add(_boss);
}

Хит скомпилировать и протестировать игру! Наш босс хорошо двигается, не так ли? Это будет трудно убить это!


Время для действительно злых боссов! Вот их изображение:

Босс пуля

Но есть кое-что, что действительно беспокоит меня: они будут вести себя точно так же, как пуля игрока, но вместо этого они просто будут следовать по тому же пути врага вниз и будут иметь другой type FlashPunk, но я не хочу копировать и вставлять тот же код в своем классе. Что вы думаете об использовании некоторого объектно-ориентированного дизайна и наследовании? Возьмите весь код (да, весь код) PlayerBullet и скопируйте его в новый класс с именем Bullet . Удалите код, связанный с изображением маркера, и вот что вы получите:

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
package
{
    import flash.geom.Point;
    import net.flashpunk.Entity;
 
    public class Bullet extends Entity
    {
        private var _pathToFollow:Vector.<Point>;
         
        private var _xPos:Number;
        private var _yPos:Number;
         
        public function Bullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number)
        {
            _pathToFollow = pathToFollow;
             
            _xPos = xPos;
            _yPos = yPos;
        }
         
        override public function update():void
        {
            x = _xPos + _pathToFollow[0].x;
            y = _yPos + _pathToFollow[0].y;
             
            _pathToFollow.shift();
             
            if (_pathToFollow.length == 0)
            {
                world.remove(this);
                 
                destroy();
            }
        }
         
        public function destroy():void
        {
            _pathToFollow = null;
             
            graphic = null;
        }
         
    }
 
}

Это основное поведение пули. Теперь, что мы делаем с классом PlayerBullet ? Поместите туда только вещи, связанные с изображением пули, и удалите все остальное. А также сделать его наследником от Bullet :

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
package
{
    import flash.geom.Point;
    import net.flashpunk.graphics.Image;
    import net.flashpunk.masks.Pixelmask;
 
    public class PlayerBullet extends Bullet
    {
        [Embed(source = ‘../img/BulletImage.png’)]
        private const IMAGE:Class;
         
        public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number)
        {
            super(pathToFollow, xPos, yPos);
             
            graphic = new Image(IMAGE);
             
            graphic.x = graphic.y = -3.5;
             
            mask = new Pixelmask(IMAGE, -3.5, -3.5);
             
            type = «PlayerBullet»;
        }
         
    }
 
}

Вы не против сделать то же самое для класса BossBullet ? Разница лишь в том, что он будет иметь тип «BossBullet». Я оставлю код для вас. Проверьте источник учебника, если вам нужна помощь!

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

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
private var _shootingInterval:int = 75;
private var _shootingTimer:int = 0;
 
override public function update():void
{
    calculateDistances();
     
    if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
    {
        x = _randomPoint.x;
        y = _randomPoint.y;
         
        getRandomPoint();
    }
    else
    {
        x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
        y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
    }
     
    _shootingTimer—;
     
    if (_shootingTimer <= 0)
    {
        _shootingTimer = _shootingInterval;
         
        world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x — 10, y));
        world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x + 10, y));
        world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x, y + 5));
    }
}

Скомпилируйте и запустите код, и теперь у вас есть босс, стреляющий пулями и летающий вокруг! Время сделать последний бой на следующем этапе 🙂


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

Первая задача: заставить босса появляться только тогда, когда счет достигнет 50. Мы сделаем это, проверив счет в GameWorld.as , точно так же, как мы делали обновления противника и игрока.

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
private var _bossAppeared:Boolean = false;
 
public function GameWorld()
{
    _playerShip = new PlayerShip();
     
    add(_playerShip);
     
    _enemyList = new Vector.<Enemy>();
     
    _score = new GameScore();
     
    _score.x = 300;
    _score.y = 470;
     
    add(_score);
     
    _enemySpawnTimer = 0;
     
    _boss = new Boss();
     
    // Not adding the boss in the game this time
}
 
override public function update():void
{
    super.update();
     
    _enemySpawnTimer—;
     
    if (_enemySpawnTimer <= 0 && !_bossAppeared)
    {
        _enemySpawnTimer = _enemySpawnInterval;
         
        _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
         
        add(_enemyList[_enemyList.length — 1]);
    }
     
    _bulletList = new Vector.<PlayerBullet>();
     
    getType(«PlayerBullet», _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        for each (var enemy:Enemy in _enemyList)
        {
            if (bullet.collideWith(enemy, bullet.x, bullet.y))
            {
                enemy.takeDamage();
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
     
    if (_score.score > _enemyUpgradeNumber)
    {
        Enemy.damageMultiplier -= 0.015;
        Enemy.healthMultiplier += 0.02;
         
        _enemySpawnInterval -= 3;
         
        _enemyUpgradeNumber++;
    }
     
    if (_score.score == 50 && !_bossAppeared)
    {
        add(_boss);
         
        _bossAppeared = true;
    }
}

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

Второе задание: сделать так, чтобы пули босса попали в игрока, а пули игрока попали в босса. Это также сделано в GameWorld.as. Посмотрите на код:

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
95
96
97
98
private var _bulletList:Vector.<Bullet>;
 
override public function update():void
{
    super.update();
     
    _enemySpawnTimer--;
     
    if (_enemySpawnTimer <= 0 && !_bossAppeared)
    {
        _enemySpawnTimer = _enemySpawnInterval;
         
        _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
         
        add(_enemyList[_enemyList.length - 1]);
    }
     
    _bulletList = new Vector.<Bullet>();
     
    getType("PlayerBullet", _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        for each (var enemy:Enemy in _enemyList)
        {
            if (bullet.collideWith(enemy, bullet.x, bullet.y))
            {
                enemy.takeDamage();
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
         
        if (_bossAppeared)
        {
            if (bullet.collideWith(_boss, bullet.x, bullet.y))
            {
                _boss.takeDamage();
                 
                if (!_boss)
                {
                    endTheGame();
                     
                    return;
                }
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
    }
     
    _bulletList = new Vector.<Bullet>();
     
    getType("BossBullet", _bulletList);
     
    for each (var bossBullet:BossBullet in _bulletList)
    {
        if (bossBullet.collideWith(_playerShip, bossBullet.x, bossBullet.y))
        {
            endTheGame();
             
            return;
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
     
    if (_score.score > _enemyUpgradeNumber)
    {
        Enemy.damageMultiplier -= 0.015;
        Enemy.healthMultiplier += 0.02;
         
        _enemySpawnInterval -= 3;
         
        _enemyUpgradeNumber++;
    }
     
    if (_score.score == 50 && !_bossAppeared)
    {
        add(_boss);
         
        _bossAppeared = true;
    }
}

В первой части кода мы проверяем столкновение пуль игрока и босса. Если происходит столкновение, мы вызываем takeDamage()функцию у босса, которая ниже. После проверки пуль игрока мы проверяем патроны босса, и если мы обнаруживаем столкновение между любым из них и игроком, мы заканчиваем игру. Эта функция также ниже.

Теперь о takeDamage()функции босса. Мы хотим, чтобы у босса было много здоровья, и мы заставим его летать быстрее после каждого удара. В Boss.as:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private var _health:int = 1000;
 
public function takeDamage():void
{
    _health -= 50;
     
    speedMultiplier += 0.05;
     
    if (_health <= 0)
    {
        world.remove(this);
         
        destroy();
    }
}
 
public function destroy():void
{
    graphic = null;
     
    mask = null;
     
    _randomPoint = null;
}

Наша битва с боссами завершена! Он умрет после 20 попаданий игрока, становясь быстрее после каждого удара.

Теперь остается только завершить игру и показать экран, когда босс убит, а также заставить игрока проиграть перед битвой с боссом, если враг уходит вниз по экрану. В конце игры нам нужно проверить, когда босс убит, и создать endTheGame()функцию GameWorld.as. Эта функция в основном удалит каждую пулю с экрана, удалит игрока и босса, а затем добавит игру поверх экрана. В GameWorld.as:

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
override public function remove(e:Entity):Entity
{
    if (e is Enemy)
    {
        _enemyList.splice(_enemyList.indexOf(e), 1);
    }
     
    if (e is Boss)
    {
        _boss = null;
    }
     
    return super.remove(e);
}
 
public function endTheGame():void
{
    removeAll();
     
    while (_bulletList.length > 0)
    {
        _bulletList[0].destroy();
         
        _bulletList.shift();
    }
     
    while (_enemyList.length > 0)
    {
        _enemyList[0].destroy();
         
        _enemyList.shift();
    }
     
    _bulletList = null;
    _enemyList = null;
    _playerShip = null;
    _score = null;
     
    if (_boss)
    {
        FP.world = new GameOverWorld(true);
    }
    else
    {
        FP.world = new GameOverWorld(false);
    }
     
    _boss = null;
}

Вот и все!endTheGame()Функция в основном такой же , как destroy()функция. Это просто очищает все ссылки в игровом мире.

Последняя часть: заставить игрока проиграть игру, если враг достиг конца экрана. Для этого мы будем помнить, что враг достигает нижней части экрана, если у него все еще есть здоровье. Итак, в remove()функции GameWorld.as:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
private var _gameEnded:Boolean = false;
 
override public function remove(e:Entity):Entity
{
    if (e is Enemy)
    {
        if (Enemy(e).health > 0)
        {
            endTheGame();
             
            return e;
        }
         
        _enemyList.splice(_enemyList.indexOf(e), 1);
    }
     
    if (e is Boss)
    {
        _boss = null;
    }
     
    return super.remove(e);
}
 
override public function update():void
{
    super.update();
     
    if (_gameEnded)
    {
        return;
    }
     
    _enemySpawnTimer--;
     
    if (_enemySpawnTimer <= 0 && !_bossAppeared)
    {
        _enemySpawnTimer = _enemySpawnInterval;
         
        _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
         
        add(_enemyList[_enemyList.length - 1]);
    }
     
    _bulletList = new Vector.<Bullet>();
     
    getType("PlayerBullet", _bulletList);
     
    for each (var bullet:PlayerBullet in _bulletList)
    {
        for each (var enemy:Enemy in _enemyList)
        {
            if (bullet.collideWith(enemy, bullet.x, bullet.y))
            {
                enemy.takeDamage();
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
         
        if (_bossAppeared)
        {
            if (bullet.collideWith(_boss, bullet.x, bullet.y))
            {
                _boss.takeDamage();
                 
                if (!_boss)
                {
                    endTheGame();
                     
                    return;
                }
                 
                remove(bullet);
                 
                bullet.destroy();
            }
        }
    }
     
    _bulletList = new Vector.<Bullet>();
     
    getType("BossBullet", _bulletList);
     
    for each (var bossBullet:BossBullet in _bulletList)
    {
        if (bossBullet.collideWith(_playerShip, bossBullet.x, bossBullet.y))
        {
            endTheGame();
             
            return;
        }
    }
     
    if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
    {
        _playerShip.speedMultiplier += 0.1;
         
        _speedUpgradeNumber++;
    }
     
    if (_score.score >= 25)
    {
        _playerShip.hasDoubleShoot = true;
    }
     
    if (_score.score > _enemyUpgradeNumber)
    {
        Enemy.damageMultiplier -= 0.015;
        Enemy.healthMultiplier += 0.02;
         
        _enemySpawnInterval -= 3;
         
        _enemyUpgradeNumber++;
    }
     
    if (_score.score == 50 && !_bossAppeared)
    {
        add(_boss);
         
        _bossAppeared = true;
    }
}
 
public function endTheGame():void
{
    removeAll();
     
    while (_bulletList.length > 0)
    {
        _bulletList[0].destroy();
         
        _bulletList.shift();
    }
     
    while (_enemyList.length > 0)
    {
        _enemyList[0].destroy();
         
        _enemyList.shift();
    }
     
    _bulletList = null;
    _enemyList = null;
    _playerShip = null;
    _score = null;
     
    if (_boss)
    {
        FP.world = new GameOverWorld(true);
    }
    else
    {
        FP.world = new GameOverWorld(false);
    }
     
    _boss = null;
     
    _gameEnded = true;
}

Этот код заканчивает игру только тогда, когда враг достигает конца (уничтожается) со здоровьем выше 0. Если враг умирает, эта функция также вызывается, но тогда здоровье врага будет ниже (или равно) 0, пропуская код для завершения игры. Мы также создали _gameEndedлогическое значение, потому что, когда враг достигает конца экрана и удаляется, мир все еще обновляет свои сущности. Только когда они закончили обновлять их (после super.update()звонка в классе), мы можем закончить игру.

После того, как все эти строки и строки кода были изменены, удалите консоль FlashPunk, нажмите «Скомпилировать» и протестируйте игру. Наконец-то, ваша первая игра полностью сделана во FlashPunk! Поздравляем!


Поздравляем, вы создали свою первую игру FlashPunk! Вы использовали практически все функции FlashPunk для очень простой игры, а это значит, что вы готовы создавать больше игр FlashPunk и распространять информацию! Что вы думаете об улучшении этой игры? У него могут быть разные враги, враги, которые также стреляют в пули, уровни, больше боссов, больше апгрейдов и многое другое! Готовы ли вы принять вызов?