Статьи

Портирование игр ActionScript для iOS с помощью Corona SDK: Часть 3

В этом руководстве будет рассказано о портировании Flash / Flex-игры на Corona SDK. В частности, мы будем портировать с ActionScript на Lua с конечной целью играть в игры, которые раньше были только для Flash, на iPhone. В дополнение к демонстрации языковых и API-различий, эта серия учебников также будет учитывать аппаратные ограничения, такие как размер экрана и отсутствие физических кнопок на iPhone.

Теперь мы начнем работать с нашим врагом: «de / pixelate / flixelprimer / Alien.as». Как всегда, синтаксис конвертируется первым.

Когда вы закончите с этим, добавьте описание модуля и оберните все функции в Alien ().

01
02
03
04
05
06
07
08
09
10
11
12
13
14
module(…, package.seeall)
 
—[Embed(source=»../../../../assets/png/Alien.png»)] private var ImgAlien:Class
 
function Alien(x, y) —:void
    super(x, y, ImgAlien)
    velocity.x = -200
 
    function update() —:void
        velocity.y = Math.cos(x / 50) * 50
        super.update()
    end
 
end

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

01
02
03
04
05
06
07
08
09
10
11
12
13
module(…, package.seeall)
 
function Alien(x, y) —:void
    local Alien = display.newImage(«Alien.png»)
     
    Alien.x = x
    Alien.y = y
 
    function update() —:void
        …
    end
 
end

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
module(…, package.seeall)
 
function Alien(x, y) —:void
    …
 
    function update() —:void
        if Alien then
            if (Alien.x > 0 — Alien.contentWidth) then
                Alien.x = Alien.x — 2
            end
        end
     
        … commented code…
    end
     
    Runtime:addEventListener( «enterFrame», update )
 
end

Теперь давайте сделаем функцию kill () и заставим Alien возвращать Alien.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
module(…, package.seeall)
 
function Alien(x, y) —:void
    …
 
    function update() —:void
        …
    end
     
    function Alien:kill()
        Alien.parent:remove( Alien )
        Alien = nil
    end
     
    Runtime:addEventListener( «enterFrame», update )
     
    return Alien
end

Теперь мы можем убить () нашего пришельца, если его x находится за пределами экрана слева. Мы также можем добавить функцию new () для удобства.

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
module(…, package.seeall)
 
function Alien(x, y) —:void
    local Alien = display.newImage(«Alien.png»)
     
    Alien.x = x
    Alien.y = y
 
    function update() —:void
        if Alien then
            if (Alien.x > 0 — Alien.contentWidth) then
                Alien.x = Alien.x — 2
            else
                Alien:kill()
            end
        end
    end
     
    function Alien:kill()
        Alien.parent:remove( Alien )
        Alien = nil
    end
     
    Runtime:addEventListener( «enterFrame», update )
     
    return Alien
end
 
function new(x, y)
    return Alien(x, y)
end

Создать инопланетянина было довольно легко. Теперь нам нужно начать добавлять их в игру через PlayState.

Сначала импортируйте модуль в PlayState.lua.

1
2
3
4
5
6
module(…, package.seeall)
 
local Ship = require(«Ship»)
local Bullet = require(«Bullet»)
local Alien = require(«Alien»)
local Buttons = require(«Buttons»)

Теперь нам нужно настроить таймер. Исходный код имел переменную _spawnInterval, которая использовалась для установки _spawnTimer. Каждый раз, когда _spawnTimer достигает 0, его значение сбрасывается до значения _spawnInterval._spawnInterval будет затем уменьшаться на 0,1, в результате чего инопланетяне будут появляться быстрее.

Для начала раскомментируйте объявления свойств _spawnInterval и _spawnTimer в create ().

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
function create() —:void
    — variable declarations
    PlayState._inGame = true
    PlayState._background = nil
    PlayState._ship = nil
    —PlayState._aliens = nil
    PlayState._bullets = nil
    —PlayState._scoreText = nil
    —PlayState._gameOverText = nil
    PlayState._spawnTimer = nil
    PlayState._spawnInterval = nil
    —PlayState.SoundExplosionShip = nil
    —PlayState.SoundExplosionAlien = nil
    PlayState.SoundBullet = nil
         
    …
end

Теперь в присваиваниях переменных установите _spawnTimer равным 0 и _spawnInterval равным 2,5. Также добавьте вызов resetSpawnTimer (). Мы создадим эту функцию за секунду.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
function create() —:void
    …
     
    — variable assignments
    PlayState._background = display.newRect(0, 0, display.contentWidth, display.contentHeight)
    PlayState._background:setFillColor(171, 204, 125)
         
    PlayState._ship = Ship.new()
         
    PlayState._shoot = false
         
    PlayState._bullets = display.newGroup()
         
    PlayState.SoundBullet = media.newEventSound( «Bullet.caf» )
     
    PlayState._spawnTimer = 0
    PlayState._spawnInterval = 2.5
    resetSpawnTimer()
     
end

Теперь найдите закомментированную функцию resetSpawnTimer (). Это будет выглядеть примерно так.

1
2
3
4
5
6
7
function resetSpawnTimer() —:void
    _spawnTimer = _spawnInterval
    _spawnInterval = _spawnInterval*0.95
    if(_spawnInterval < 0.1) then
        _spawnInterval = 0.1
    end
end

Удивительно, но это именно то, что нам нужно. Нам просто нужно сделать переменные свойства PlayState. Таким образом, функция будет знать, о каких _spawnInterval и _spawnTimer мы говорим.

1
2
3
4
5
6
7
function resetSpawnTimer() —:void
    PlayState._spawnTimer = PlayState._spawnInterval
    PlayState._spawnInterval = PlayState._spawnInterval*0.95
    if(PlayState._spawnInterval < 0.1) then
        PlayState._spawnInterval = 0.1
    end
end

Теперь нам нужно добавить код для update (). В оригинальном коде игра создала инопланетян, даже если игра была закончена, а корабль мертв. Чтобы сделать то же самое, давайте разместим наш код обработки инопланетян за пределами того места, где мы проверяем, не закончилась ли игра.

1
function update() PlayState._spawnTimer = PlayState._spawnTimer — (30/1000) if(PlayState._spawnTimer < 0) then spawnAlien() resetSpawnTimer() end if PlayState._inGame then … end end

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

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

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
function create() —:void
    — variable declarations
    PlayState._inGame = true
    PlayState._background = nil
    PlayState._ship = nil
    PlayState._aliens = nil
    PlayState._bullets = nil
    —PlayState._scoreText = nil
    —PlayState._gameOverText = nil
    PlayState._spawnTimer = nil
    PlayState._spawnInterval = 2.5
    —PlayState.SoundExplosionShip = nil
    —PlayState.SoundExplosionAlien = nil
    PlayState.SoundBullet = nil
         
    — variable assignments
    PlayState._background = display.newRect(0, 0, display.contentWidth, display.contentHeight)
    PlayState._background:setFillColor(171, 204, 125)
         
    PlayState._ship = Ship.new()
         
    PlayState._shoot = false
     
    PlayState._aliens = display.newGroup()
    PlayState._bullets = display.newGroup()
         
    PlayState.SoundBullet = media.newEventSound( «Bullet.caf» )
         
    PlayState._spawnTimer = 0
    PlayState._spawnInterval = 2.5
    resetSpawnTimer()
     
    …
end

Теперь найдите функцию spawnAlien () и раскомментируйте ее. Это должно выглядеть примерно так:

1
2
3
4
5
function spawnAlien() —:void
    local x = FlxG.width
    local y = Math.random() * (FlxG.height — 100) + 50
    _aliens.add(new Alien(x, y))
end

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

1
2
3
4
5
function spawnAlien() —:void
    local x = display.contentWidth
    local y = math.random() * (display.contentHeight — 240) + 50
    PlayState._aliens:insert(Alien.new(x, y))
end

Если мы запустим код сейчас, он будет работать почти как оригинал. Инопланетяне появляются на случайных высотах и ​​медленно появляются чаще. Когда они выходят за пределы экрана, они сами вызывают kill (). Теперь нам просто нужно заставить их двигаться так же, как в исходном коде. В оригинальной игре инопланетяне следовали по пути косинусоидальной волны, которая генерировалась на основе их местоположения x. Мы закомментировали этот код в функции чужих update (). Этот код потребовал немного поиграть. Поскольку у нас нет скорости для работы, трудно использовать оригинальный код. Это время портирования, когда вам просто нужно играть с числами. Я нашел этот код работал ближе всего к оригиналу:

01
02
03
04
05
06
07
08
09
10
function update() —:void
    if Alien then
        if (Alien.x > 0 — Alien.contentWidth) then
            Alien.x = Alien.x — 2
            Alien.y = Alien.y + math.cos(Alien.x / 10) * 2
        else
            Alien:kill()
        end
    end
end

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

1
2
3
4
5
6
7
8
9
function overlapAlienBullet(alien, bullet) —:void
    local emitter = createEmitter()
    emitter.at(alien)
    alien.kill()
    bullet.kill()
    FlxG.play(SoundExplosionAlien)
    FlxG.score = FlxG.score + 1
    _scoreText.text = FlxG.score.toString()
end

Этот код создает излучатель частиц, убивает оба объекта, воспроизводит звуковой эффект и обновляет счет. Воссоздание системы частиц flixel выходит за рамки данного руководства, и нам еще предстоит внедрить систему оценок. А пока давайте просто закомментируем эти строки и уничтожим объекты.

1
2
3
4
5
6
7
function overlapAlienBullet(alien, bullet) —:void
    alien:kill()
    bullet:kill()
    —FlxG.play(SoundExplosionAlien)
    —FlxG.score = FlxG.score + 1
    —_scoreText.text = FlxG.score.toString()
end

Сделайте то же самое для overlapAlienShip ():

1
2
3
4
5
6
7
8
9
function overlapAlienShip(alien, ship) —:void
    ship:kill()
    alien:kill()
    —FlxG.play(SoundExplosionShip)
         
    —_gameOverText = new FlxText(0, FlxG.height / 2, FlxG.width, «GAME OVER\nPRESS ENTER TO PLAY AGAIN»)
    —_gameOverText.setFormat(null, 16, 0xFF597137, «center»)
    —add(_gameOverText)
end

Теперь давайте создадим звуковой эффект для использования в этих функциях. Раскомментируйте объявления звуковых переменных в create ().

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
function create() —:void
    — variable declarations
    PlayState._inGame = true
    PlayState._background = nil
    PlayState._ship = nil
    PlayState._aliens = nil
    PlayState._bullets = nil
    —PlayState._scoreText = nil
    —PlayState._gameOverText = nil
    PlayState._spawnTimer = nil
    PlayState._spawnInterval = 2.5
    PlayState.SoundExplosionShip = nil
    PlayState.SoundExplosionAlien = nil
    PlayState.SoundBullet = nil
         
    …
end

Теперь присвойте им звуки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
function create() —:void
    …
     
    — variable assignments
    PlayState._background = display.newRect(0, 0, display.contentWidth, display.contentHeight)
    PlayState._background:setFillColor(171, 204, 125)
         
    PlayState._ship = Ship.new()
         
    PlayState._shoot = false
         
    PlayState._aliens = display.newGroup()
    PlayState._bullets = display.newGroup()
         
    PlayState.SoundBullet = media.newEventSound( «Bullet.caf» )
    PlayState.SoundExplosionShip = media.newEventSound( «ExplosionShip.caf» )
    PlayState.SoundExplosionAlien = media.newEventSound( «ExplosionAlien.caf» )
         
    PlayState._spawnTimer = 0
    PlayState._spawnInterval = 2.5
    resetSpawnTimer()
end

Воспроизведение звуков так же просто, как одна строка на функцию ().

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
function overlapAlienBullet(alien, bullet) —:void
    alien:kill()
    bullet:kill()
    media.playEventSound( PlayState.SoundExplosionAlien )
     
    —FlxG.score = FlxG.score + 1
    —_scoreText.text = FlxG.score.toString()
end
 
function overlapAlienShip(alien, ship) —:void
    ship:kill()
    alien:kill()
    media.playEventSound( PlayState.SoundExplosionShip )
         
    —_gameOverText = new FlxText(0, FlxG.height / 2, FlxG.width, «GAME OVER\nPRESS ENTER TO PLAY AGAIN»)
    —_gameOverText.setFormat(null, 16, 0xFF597137, «center»)
    —add(_gameOverText)
end

Теперь, когда у нас есть наши функции перекрытия, нам нужно проверять наличие коллизий в каждом кадре. В Corona у нас нет простого способа проверить перекрывающиеся экранные объекты. Мы должны будем сделать проверки вручную. Это довольно простая концепция, но в реализации она становится довольно грязной. Давайте подумаем об этом на секунду. Что определяет перекрытие? Ваш первый инстинкт может заключаться в том, чтобы проверить, находится ли объект внутри другого. Это будет работать, но в этом случае перекрытие может быть просто частью объектов. Объект не должен быть полностью внутри другого, чтобы перекрываться. Как это выглядит в коде? Все, что нам нужно сделать, это проверить, больше ли максимальное значение х объекта, чем минимум х
стоимость другого объекта. Затем мы проверяем, меньше ли минимальное значение x того же объекта, чем максимальное значение x других объектов. Это вернет истину для каждого перекрытия. Затем мы выполняем те же проверки значений у объектов. Если мы перебираем все объекты в группах отображения, которые мы создали ранее, у нас должна быть работающая система столкновений.

Давайте попробуем это с пулями и пришельцами. Нам нужно выполнять эти проверки только в игре. Поэтому поместите этот код в правильную часть функции update ():

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
function update() —:void
    …
 
    if PlayState._inGame then
        if PlayState._shoot == true and PlayState._ship then
            local p = PlayState._ship:getBulletSpawnPosition()
            spawnBullet(p)
        end
 
        if PlayState._bullets.numChildren > 0 and PlayState._aliens.numChildren > 0 then
            for b=1, PlayState._bullets.numChildren do
                local bulletXMax = PlayState._bullets[b].contentBounds.xMax
                local bulletXMin = PlayState._bullets[b].contentBounds.xMin
                local bulletYMax = PlayState._bullets[b].contentBounds.yMax
                local bulletYMin = PlayState._bullets[b].contentBounds.yMin
                for a=1, PlayState._aliens.numChildren do
                    if (PlayState._aliens[a].contentBounds.xMin <= bulletXMax) then
                        if (PlayState._aliens[a].contentBounds.xMax >= bulletXMin) then
                           if (PlayState._aliens[a].contentBounds.yMin <= bulletYMax) then
                                if (PlayState._aliens[a].contentBounds.yMax >= bulletYMin) then
                                    overlapAlienBullet(PlayState._aliens[a], PlayState._bullets[b])
                                end
                            end
                        end
                    end
                end
            end
        end
    end
end

Как я уже сказал, этот код выглядит немного грязно, но он работает. Так, что это делает? Во-первых, он проверяет, существует ли пуля или инопланетянин. Этот код может стать очень требовательным к памяти, поэтому мы должны все проверить. Мы не хотим терять время, если у нас даже нет обоих видов объектов. Как только этот код проходит, мы запускаем цикл for. Этот цикл устанавливает переменную («b» с именем «bullets») в 1 и запускает остальную часть кода для каждого маркера в _bullets. Следующие четыре строки кода создают локальную копию минимальных и максимальных значений маркера. Как я уже говорил, нам нужно сохранить память здесь. Нам не нужно вычислять значения маркеров x и y снова и снова, если они не меняются. Следующая строка начинает еще один цикл. Это повторяется для всех пришельцев в _aliens. Код внутри второго цикла for просто выполняет проверки, о которых мы говорили ранее. Значение max x пули больше, чем значение min x иностранца? Я не могу подчеркнуть память здесь достаточно, чтобы
Вот почему мы проверяем каждое условие в отдельном операторе if. Если один из этих тестов не пройден, мы можем просто выйти из цикла. Нет необходимости продолжать проверять, нет ли столкновения. Наконец, в самом центре, если все эти проверки пройдены, мы вызываем нашу функцию overlapAlienBullet () со встречной пулей и пришельцем.

Уф. Это было много кода. Теперь нам нужно сделать то же самое для корабля.

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
function update() —:void
    …
 
    if PlayState._inGame then
        if PlayState._shoot == true and PlayState._ship then
            local p = PlayState._ship:getBulletSpawnPosition()
            spawnBullet(p)
        end
 
        …
         
        if PlayState._aliens.numChildren > 0 then
            local shipXMax = PlayState._ship.contentBounds.xMax
            local shipXMin = PlayState._ship.contentBounds.xMin
            local shipYMax = PlayState._ship.contentBounds.yMax
            local shipYMin = PlayState._ship.contentBounds.yMin
            for a=1, PlayState._aliens.numChildren do
                if (PlayState._aliens[a].contentBounds.xMin <= shipXMax) then
                    if (PlayState._aliens[a].contentBounds.xMax >= shipXMin) then
                        if (PlayState._aliens[a].contentBounds.yMin <= shipYMax) then
                            if (PlayState._aliens[a].contentBounds.yMax >= shipYMin) then
                                overlapAlienShip(PlayState._aliens[a], PlayState._ship)
                            end
                        end
                    end
                end
            end
        end
    end
end

Этот код идентичен маркеру и чужому коду. Разница лишь в том, что у нас только один корабль. Мы знаем, что есть корабль, в противном случае PlayState._inGame будет ложным. Нам не нужно циклически просматривать группу кораблей, потому что у нас есть только один.

Прежде чем мы сможем протестировать этот код, нам нужно завершить игру в overlapAlienShip (). Измените _inGame на false.

01
02
03
04
05
06
07
08
09
10
11
function overlapAlienShip(alien, ship) —:void
    ship:kill()
    alien:kill()
    media.playEventSound( PlayState.SoundExplosionShip )
         
    PlayState._inGame = false
             
    —_gameOverText = new FlxText(0, FlxG.height / 2, FlxG.width, «GAME OVER\nPRESS ENTER TO PLAY AGAIN»)
    —_gameOverText.setFormat(null, 16, 0xFF597137, «center»)
    —add(_gameOverText)
end

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

Начнем с системы оценок. Это так же просто, как создание текстовой метки и ее обновление при изменении оценки. Раскомментируйте строку _scoreText и добавьте новое свойство _score в объявлениях create ().

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
function create() —:void
        — variable declarations
        PlayState._inGame = true
        PlayState._background = nil
        PlayState._ship = nil
        PlayState._aliens = nil
        PlayState._bullets = nil
        PlayState._score = nil
        PlayState._scoreText = nil
        —PlayState._gameOverText = nil
        PlayState._spawnTimer = nil
        PlayState._spawnInterval = 2.5
        PlayState.SoundExplosionShip = nil
        PlayState.SoundExplosionAlien = nil
        PlayState.SoundBullet = nil
         
        …
end

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

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
function create() —:void
        …
         
        — variable assignments
        PlayState._background = display.newRect(0, 0, display.contentWidth, display.contentHeight)
        PlayState._background:setFillColor(171, 204, 125)
         
        PlayState._ship = Ship.new()
         
        PlayState._shoot = false
         
        PlayState._aliens = display.newGroup()
        PlayState._bullets = display.newGroup()
         
        PlayState._score = 0
        PlayState._scoreText = display.newText( «0», 10, 8, nil, 32 )
        PlayState._scoreText:setTextColor(89, 113, 55)
         
        PlayState.SoundBullet = media.newEventSound( «Bullet.caf» )
        PlayState.SoundExplosionShip = media.newEventSound( «ExplosionShip.caf» )
        PlayState.SoundExplosionAlien = media.newEventSound( «ExplosionAlien.caf» )
         
        PlayState._spawnTimer = 0
        PlayState._spawnInterval = 2.5
        resetSpawnTimer()
end

API newText прост: display.newText («текст для отображения», позиция x, позиция y, шрифт, размер). «PlayState._scoreText: setTextColor (89, 113, 55)» просто устанавливает цвет заливки на тот же зеленый цвет, что и маркеры. Все, что нам нужно сейчас, это обновить счет в overlapAlienBullet ():

1
2
3
4
5
6
7
8
function overlapAlienBullet(alien, bullet) —:void
    alien:kill()
    bullet:kill()
    media.playEventSound( PlayState.SoundExplosionAlien )
     
    PlayState._score = PlayState._score + 1
    PlayState._scoreText.text = PlayState._score
end

Это просто добавляет 1 к свойству _score, когда инопланетянин убит пулей. Затем он изменяет текстовое свойство _scoreText на значение оценки.

Прежде чем закончить эту игру, нам нужно как-то ее переустановить. Таким образом, пользователь может начать все сначала, когда он умрет. Нам нужно будет сделать это в два этапа. Во-первых, мы должны остановить все, что пользователь может контролировать, как только корабль погибнет. Затем мы должны сбросить все остальное, когда пользователь нажимает, чтобы начать новую игру.

Давайте отключим все кнопки в функции overlapAlienShip (). Также давайте удалим свойство корабля. Мы можем сделать это, установив все значения в ноль.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
function overlapAlienShip(alien, ship) —:void
    ship:kill()
    alien:kill()
    media.playEventSound( PlayState.SoundExplosionShip )
         
    PlayState._inGame = false
    PlayState._ship = nil
    PlayState._upButton.onPress = nil
    PlayState._upButton.onRelease = nil
    PlayState._downButton.onPress = nil
    PlayState._downButton.onRelease = nil
    PlayState._leftButton.onPress = nil
    PlayState._leftButton.onRelease = nil
    PlayState._rightButton.onPress = nil
    PlayState._rightButton.onRelease = nil
    PlayState._shootButton.onPress = nil
    PlayState._shootButton.onRelease = nil
             
    —_gameOverText = new FlxText(0, FlxG.height / 2, FlxG.width, «GAME OVER\nPRESS ENTER TO PLAY AGAIN»)
    —_gameOverText.setFormat(null, 16, 0xFF597137, «center»)
    —add(_gameOverText)
end

Этот код просто назначает все значения onPress и onRelease для nil. Кнопки все равно
дисплей, но они не будут вызывать код при нажатии.

Теперь мы видим, что оригинальная функция overlapAlienShip () отображала текстовую метку, чтобы сообщить пользователю, что игра окончена. Мы сделаем то же самое. Сначала раскомментируйте наше свойство _gameOverText в функции create ().

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
function create() —:void
        — variable declarations
        PlayState._inGame = true
        PlayState._background = nil
        PlayState._ship = nil
        PlayState._aliens = nil
        PlayState._bullets = nil
        PlayState._score = nil
        PlayState._scoreText = nil
        PlayState._gameOverText = nil
        PlayState._spawnTimer = nil
        PlayState._spawnInterval = 2.5
        PlayState.SoundExplosionShip = nil
        PlayState.SoundExplosionAlien = nil
        PlayState.SoundBullet = nil
         
        …
end

Вернемся к overlapAlienShip (), нам нужно заменить закомментированный код на эти строки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
function overlapAlienShip(alien, ship) —:void
    ship:kill()
    alien:kill()
    media.playEventSound( PlayState.SoundExplosionShip )
         
    PlayState._inGame = false
    PlayState._ship = nil
    PlayState._upButton.onPress = nil
    PlayState._upButton.onRelease = nil
    PlayState._downButton.onPress = nil
    PlayState._downButton.onRelease = nil
    PlayState._leftButton.onPress = nil
    PlayState._leftButton.onRelease = nil
    PlayState._rightButton.onPress = nil
    PlayState._rightButton.onRelease = nil
    PlayState._shootButton.onPress = nil
    PlayState._shootButton.onRelease = nil
             
    PlayState._gameOverText = display.newText( «GAME OVER TAP TO PLAY AGAIN», 35, display.contentHeight/2 — 50, nil, 16 )
    PlayState._gameOverText:setTextColor(89, 113, 55)
end

Это тот же код, который мы использовали для создания и заполнения текста партитуры. Положение изменяется, чтобы центрировать текст на экране, и текст говорит «НАЖМИТЕ, ЧТОБЫ ИГРАТЬ СНОВА» вместо «НАЖМИТЕ ВВОД, ЧТОБЫ ИГРАТЬ СНОВА»

Так как мы собираемся нажать, чтобы перезапустить игру, нам нужно добавить новый слушатель событий. Мы можем сделать это так же, как мы добавили прослушиватель enterFrame, но на этот раз прослушиваемое событие «tap». Также нам нужно добавить функцию для вызова слушателя. В нижней части overlapAlienShip () добавьте прослушиватель этого события:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function overlapAlienShip(alien, ship) —:void
    ship:kill()
    alien:kill()
    media.playEventSound( PlayState.SoundExplosionShip )
         
    PlayState._inGame = false
    PlayState._ship = nil
    PlayState._upButton.onPress = nil
    PlayState._upButton.onRelease = nil
    PlayState._downButton.onPress = nil
    PlayState._downButton.onRelease = nil
    PlayState._leftButton.onPress = nil
    PlayState._leftButton.onRelease = nil
    PlayState._rightButton.onPress = nil
    PlayState._rightButton.onRelease = nil
    PlayState._shootButton.onPress = nil
    PlayState._shootButton.onRelease = nil
             
    PlayState._gameOverText = display.newText( «GAME OVER TAP TO PLAY AGAIN», 35, display.contentHeight/2 — 50, nil, 16 )
    PlayState._gameOverText:setTextColor(89, 113, 55)
     
    Runtime:addEventListener( «tap», tap )
end

Этот код проверяет наличие касания в любом месте экрана. Когда происходит «тап», вызывается функция тап (). Давайте создадим пустую функцию tap () для вызова. Поместите это чуть выше create ().

1
2
3
4
5
6
7
8
function tap( event )
    — we just need this as a placeholder for now
end
     
function create()
    …
end

Давайте создадим функцию remove () для очистки всех игровых объектов. Поместите эту функцию выше нашей новой функции tap ().

01
02
03
04
05
06
07
08
09
10
11
12
function remove()
    —
end
 
function tap( event )
    — we just need this as a placeholder for now
end
     
function create()
    …
end

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

01
02
03
04
05
06
07
08
09
10
11
12
function remove()
    for i=PlayState._bullets.numChildren, 1, -1 do
        PlayState._bullets[i]:kill()
    end
    for i=PlayState._aliens.numChildren, 1, -1 do
        PlayState._aliens[i]:kill()
    end
    PlayState._bullets:removeSelf()
    PlayState._bullets = nil
    PlayState._aliens:removeSelf()
    PlayState._aliens = nil
end

Эти петли работают особым образом. Каждый цикл начинается с «i», установленного на последний объект в группах отображения. Затем цикл убивает этот объект и вычитает 1 из «я». Цикл повторяется до тех пор, пока «i» не станет равным 1. Причина, по которой мы вычитаем из «i», состоит в том, что каждый раз, когда мы убиваем объект, он удаляет себя из группы отображения. Это означает, что в группе отображения на один объект меньше. Если бы мы добавили 1 к «i», мы бы в конечном итоге вызвали kill () для объектов, которые больше не существуют. Чтобы исправить это, мы считаем в обратном порядке каждый раз, когда удаляем объект

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

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

1
function remove() for i=PlayState._bullets.numChildren, 1, -1 do PlayState._bullets[i]:kill() end for i=PlayState._aliens.numChildren, 1, -1 do PlayState._aliens[i]:kill() end PlayState._bullets:removeSelf() PlayState._bullets = nil PlayState._aliens:removeSelf() PlayState._aliens = nil PlayState._upButton:removeSelf() PlayState._upButton = nil PlayState._downButton:removeSelf() PlayState._downButton = nil PlayState._leftButton:removeSelf() PlayState._leftButton = nil PlayState._rightButton:removeSelf() PlayState._rightButton = nil PlayState._shootButton:removeSelf() PlayState._shootButton = nil end

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

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
function remove()
    for i=PlayState._bullets.numChildren, 1, -1 do
        PlayState._bullets[i]:kill()
    end
    for i=PlayState._aliens.numChildren, 1, -1 do
        PlayState._aliens[i]:kill()
    end
    PlayState._bullets:removeSelf()
    PlayState._bullets = nil
    PlayState._aliens:removeSelf()
    PlayState._aliens = nil
     
    PlayState._upButton:removeSelf()
    PlayState._upButton = nil
    PlayState._downButton:removeSelf()
    PlayState._downButton = nil
    PlayState._leftButton:removeSelf()
    PlayState._leftButton = nil
    PlayState._rightButton:removeSelf()
    PlayState._rightButton = nil
    PlayState._shootButton:removeSelf()
    PlayState._shootButton = nil
     
    PlayState.SoundExplosionShip = nil
    PlayState.SoundExplosionAlien = nil
    PlayState.SoundBullet = nil
end

Теперь наши текстовые объекты:

1
2
3
4
5
6
7
8
function remove()
    …
     
    PlayState._scoreText:removeSelf()
    PlayState._scoreText = nil
    PlayState._gameOverText:removeSelf()
    PlayState._gameOverText = nil
end

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

1
2
3
4
5
6
7
8
function remove()
    …
     
    Runtime:removeEventListener( «enterFrame», update )
    Runtime:removeEventListener( «tap», tap )
    PlayState = nil
    PlayState = {}
end

Перезапустить игру так же просто, как вызвать remove () и затем создать () в нашей функции tap ().

1
2
3
4
function tap( event )
    remove()
    create()
end

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

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

01
02
03
04
05
06
07
08
09
10
local PlayState = require(«PlayState»)
 
function Main()
    system.activate( «multitouch» )
    display.setStatusBar( display.HiddenStatusBar )
 
    PlayState.PlayState()
end
 
Main()

У меня также были некоторые проблемы с модулем ui.lua. Кнопки не считаются, когда вы отпускаете палец с устройства, если только последним не коснулся кнопки. Это не всегда работает должным образом для этой конкретной игры, и иногда кажется, что корабль движется сам по себе, потому что функция выпуска не вызывается. Это было просто исправить. Я снял флажок, чтобы увидеть, была ли нажата кнопка при отпускании пальца пользователя. Это просто означало закомментировать операторы if и end в строках 91 и 98.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
 
if «ended» == phase then
    — Only consider this a «click» if the user lifts their finger inside button’s stageBounds
    —if isWithinBounds then
        if onEvent then
            buttonEvent.phase = «release»
            result = onEvent( buttonEvent )
        elseif onRelease then
            result = onRelease( event )
        end
    —end
end
 

Эти изменения включены в файл ui.lua, включенный в это руководство.

Мы закончили. Теперь у нас есть полностью рабочая копия оригинальной флеш игры. У нас есть корабль, который движется и стреляет, инопланетяне и система очков. Игра управляет памятью всех объектов, имеет возможность сброса и запуска заново. Помимо системы частиц, эта игра является идентичным портом. Теперь, когда вы завершили это руководство, у вас должно быть достаточно знаний, чтобы перенести практически любую игру flash / actioncript на iPhone.

Многие разработчики любят добавлять функциональность или немного менять игровой процесс при переносе игры на мобильное устройство. Я хотел бы предложить вам улучшить / изменить эту игру, чтобы она стала больше похожа на настоящее приложение. Вы можете изменить схему управления на любой из типов, о которых мы говорили в уроке 2. Вы можете создать меню с настройками или несколькими уровнями. Вы можете добавить различные типы врагов. Вы можете добавить локальную или онлайн систему рекордов. Возможности безграничны. К счастью, Corona делает разработку действительно быстрой и действительно плавной. Надеюсь, вам понравился этот урок и, что более важно, вы узнали много нового. Оставьте комментарий ниже, чтобы рассказать нам, как все прошло.