Статьи

Создайте файтинг в Corona: больше геймплея

Конечный продукт
Что вы будете создавать

В предыдущем уроке этой серии мы начали реализовывать игровой процесс и уже успели заставить самолет двигаться по экрану. В этом уроке мы продолжим реализацию игрового процесса. Давайте startTimers функцию startTimers .

Как видно из startTimers функция startTimers запускает таймеры. Добавьте следующий код в gamelevel.lua .

1
2
3
function startTimers()
 
end

Вызовите эту функцию в методе enterScene как показано ниже.

1
2
3
4
5
6
function scene:enterScene( event )
    local planeSound = audio.loadStream(«planesound.mp3»)
    planeSoundChannel = audio.play( planeSound, {loops=-1} )
    Runtime:addEventListener(«enterFrame», gameLoop)
    startTimers()
end

Функция firePlayerBullet создает пулю для игрока.

1
2
3
4
5
function firePlayerBullet()
    local tempBullet = display.newImage(«bullet.png»,(player.x+playerWidth/2) — bulletWidth,player.y-bulletHeight)
    table.insert(playerBullets,tempBullet);
    planeGroup:insert(tempBullet)
end

Здесь мы используем метод newImage объекта newImage для создания newImage . Мы размещаем его таким образом, чтобы он находился в центре плоскости по оси x и в самом верху плоскости по оси y. Затем пуля вставляется в таблицу playerBullets для последующего использования, а также в planeGroup .

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

1
2
3
function startTimers()
    firePlayerBulletTimer = timer.performWithDelay(2000, firePlayerBullet ,-1)
end

Как видно из его имени, метод performWithDelay вызывает указанную функцию по истечении определенного периода времени. Время firePlayerBullet в миллисекундах, поэтому здесь мы вызываем функцию firePlayerBullet каждые две секунды. Передав -1 в качестве третьего аргумента, таймер будет повторяться вечно.

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

В movePlayerBullets мы playerBullets таблицу playerBullets и меняем координату y каждого playerBullets . Сначала мы проверяем, есть ли в таблице playerBullets . # playerBullets называется оператором длины, и он возвращает длину вызываемого объекта. Полезно знать, что оператор # также работает со строками.

1
2
3
4
5
6
7
function movePlayerBullets()
    if(#playerBullets > 0) then
        for i=1,#playerBullets do
            playerBullets[i].
    end
   end
end

Нам нужно вызвать movePlayerBullets в функции gameLoop как показано ниже.

1
2
3
4
5
6
function gameLoop()
    —SNIP—
    numberOfTicks = numberOfTicks + 1
    movePlayer()
    movePlayerBullets()
end

Когда пуля выходит за пределы экрана, она больше не относится к игре. Тем не менее, они все еще являются частью таблицы playerBullets и продолжают двигаться как любая другая пуля в таблице. Это пустая трата ресурсов, и если игра будет продолжаться очень долго, это приведет к сотням или тысячам неиспользованных объектов.

Чтобы преодолеть это, мы отслеживаем маркеры и, как только они перемещаются за пределы экрана, мы удаляем их как из таблицы playerBullets так и с экрана. Посмотрите на реализацию checkPlayerBulletsOutOfBounds .

01
02
03
04
05
06
07
08
09
10
11
function checkPlayerBulletsOutOfBounds()
 if(#playerBullets > 0) then
     for i=#playerBullets,1,-1 do
            if(playerBullets[i].y < -18) then
        playerBullets[i]:removeSelf()
        playerBullets[i] = nil
        table.remove(playerBullets,i)
            end
       end
     end
end

Важно отметить, что мы playerBullets таблицу playerBullets в обратном направлении. Если мы переместимся по таблице вперед, то при удалении одного из маркеров будет отключен индекс зацикливания, что приведет к ошибке. Зацикливая таблицу в обратном порядке, последний маркер уже обработан. Метод removeSelf удаляет экранный объект и освобождает его память. В качестве наилучшей практики вы должны установить любые объекты removeSelf nil после вызова removeSelf .

Мы вызываем эту функцию в функции gameLoop .

1
2
3
4
5
6
function gameLoop()
    —SNIP—
    movePlayer()
    movePlayerBullets()
    checkPlayerBulletsOutOfBounds()
end

Если вы хотите проверить, работает ли эта функция должным образом, вы можете временно вставить оператор print("Removing Bullet") сразу после установки для экранного объекта значения nil .

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

1
2
3
4
5
function generateIsland()
    local tempIsland = display.newImage(«island1.png», math.random(0,display.contentWidth — islandWidth),-islandHeight)
    table.insert(islands,tempIsland)
    islandGroup:insert( tempIsland )
end

Мы снова newImage метод newImage и позиционируем остров, устанавливая отрицательное значение для islandHeight . Для позиции x мы используем метод math.random чтобы сгенерировать число между 0 и contentWidth минус islandWidth . Причина, по которой мы вычитаем ширину острова, состоит в том, чтобы убедиться, что остров полностью на экране. Если бы мы не вычитали ширину острова, был бы шанс, что часть острова не была бы на экране.

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

1
2
3
4
function startTimers()
    firePlayerBulletTimer = timer.performWithDelay(2000, firePlayerBullet ,-1)
    generateIslandTimer = timer.performWithDelay( 5000, generateIsland ,-1)
end

Реализация moveIslands практически идентична функции movePlayerBullets . Мы проверяем, содержит ли таблица islands какие-либо острова, и, если она есть, мы перебираем ее и немного перемещаем каждый остров.

1
2
3
4
5
6
7
function moveIslands()
if(#islands > 0) then
    for i=1, #islands do
        islands[i].y = islands[i].y + 3
   end
  end
end

Точно так же, как мы проверяем, переместились ли пули игрока за пределы экрана, мы проверяем, не переместились ли какие-либо острова за пределы экрана. Поэтому реализация checkIslandsOutOfBounds должна быть вам знакома. Мы проверяем, больше ли положение островов y чем display.contentHeight и если это так, мы знаем, что остров переместился за пределы экрана и поэтому может быть удален.

01
02
03
04
05
06
07
08
09
10
11
function checkIslandsOutOfBounds()
    if(#islands > 0) then
        for i=#islands,1,-1 do
            if(islands[i].y > display.contentHeight) then
            islands[i]:removeSelf()
        islands[i] = nil
        table.remove(islands,i)
    end
      end
  end
end

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

1
2
3
4
5
6
7
8
function generateFreeLife ()
    if(numberOfLives >= 6) then
    return
   end
   local freeLife = display.newImage(«newlife.png», math.random(0,display.contentWidth — 40), 0);
   table.insert(freeLifes,freeLife)
   planeGroup:insert(freeLife)
end

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

Нам нужно вызывать эту функцию очень часто. Добавьте следующий фрагмент к функции startTimers .

1
2
3
4
5
function startTimers()
    firePlayerBulletTimer = timer.performWithDelay(2000, firePlayerBullet ,-1)
    generateIslandTimer = timer.performWithDelay( 5000, generateIsland ,-1)
    generateFreeLifeTimer = timer.performWithDelay(7000,generateFreeLife, — 1)
end

Реализация moveFreeLifes должна выглядеть знакомо. Мы freeLifes таблицу freeLifes и перемещаем каждое изображение в ней.

1
2
3
4
5
6
7
function moveFreeLifes()
    if(#freeLifes > 0) then
    for i=1,#freeLifes do
        freeLifes[i].y = freeLifes[i].y +5
    end
   end
end

Все, что нам нужно сделать, это вызвать moveFreeLifes в функции gameLoop .

1
2
3
4
5
function gameLoop()
    —SNIP—
    checkIslandsOutOfBounds()
    moveFreeLifes()
end

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

01
02
03
04
05
06
07
08
09
10
11
function checkFreeLifesOutOfBounds()
   if(#freeLifes > 0) then
       for i=#freeLifes,1,-1 do
           if(freeLifes[i].y > display.contentHeight) then
           freeLifes[i]:removeSelf()
       freeLifes[i] = nil
       table.remove(freeLifes,i)
      end
   end
    end
end

Мы называем эту функцию в функции gameLoop .

1
2
3
4
5
6
function gameLoop()
    —SNIP—
    checkIslandsOutOfBounds()
    moveFreeLifes()
    checkFreeLifesOutOfBounds()
end

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function hasCollided( obj1, obj2 )
   if ( obj1 == nil ) then
      return false
   end
   if ( obj2 == nil ) then
      return false
   end
 
   local left = obj1.contentBounds.xMin <= obj2.contentBounds.xMin and obj1.contentBounds.xMax >= obj2.contentBounds.xMin
   local right = obj1.contentBounds.xMin >= obj2.contentBounds.xMin and obj1.contentBounds.xMin <= obj2.contentBounds.xMax
   local up = obj1.contentBounds.yMin <= obj2.contentBounds.yMin and obj1.contentBounds.yMax >= obj2.contentBounds.yMin
   local down = obj1.contentBounds.yMin >= obj2.contentBounds.yMin and obj1.contentBounds.yMin <= obj2.contentBounds.yMax
 
   return (left or right) and (up or down)
end

Я нашел этот фрагмент кода на сайте CoronaLabs . Это работает очень хорошо, потому что игровые объекты в нашей игре прямоугольные. Если вы работаете с объектом, который не является прямоугольным, то вам лучше воспользоваться физическим движком Corona, поскольку его обнаружение столкновений очень хорошо оптимизировано для этого.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function checkPlayerCollidesWithFreeLife()
    if(#freeLifes > 0) then
        for i=#freeLifes,1,-1 do
         if(hasCollided(freeLifes[i], player)) then
            freeLifes[i]:removeSelf()
        freeLifes[i] = nil
        table.remove(freeLifes, i)
        numberOfLives = numberOfLives + 1
        hideLives()
        showLives()
        end
    end
    end
end

В функции checkPlayerCollidesWithFreeLife мы freeLives таблицу freeLives обратном направлении по той же причине, что я описал ранее. Мы вызываем функцию hasCollided и передаем текущее изображение и плоскость игрока. Если два объекта сталкиваются, мы удаляем образ свободной жизни, увеличиваем переменную hideLives и showLives функции hideLives и showLives .

Мы вызываем эту функцию в функции gameLoop .

1
2
3
4
5
6
function gameLoop()
    —SNIP—
    moveFreeLifes()
    checkFreeLifesOutOfBounds()
    checkPlayerCollidesWithFreeLife()
end

Функция hideLives livesImages таблицу livesImages и устанавливает для свойства isVisible каждого изображения жизни значение false .

1
2
3
4
5
function hideLives()
    for i=1, 6 do
        livesImages[i].isVisible = false
   end
end

Функция showLives livesImages таблицу livesImages и устанавливает для свойства isVisible каждого изображения isVisible true .

1
2
3
4
5
function showLives()
    for i=1, numberOfLives do
        livesImages[i].isVisible = true;
    end
end

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