Статьи

Создайте игру Collect-the-Pieces с Corona

В этом уроке я покажу вам, как создать игру «собери кусочки», используя Corona SDK и язык программирования Lua. Мы рассмотрим, как использовать сенсорное управление и события, создавать фигуры с помощью графического API, а также будем использовать физические коллизии и таймеры. Давайте начнем.


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

Обзор приложения
Это приложение использует библиотеку Graphics 2.0 . Если вы используете старую версию Corona SDK, у вас могут возникнуть проблемы. Обратитесь к руководству по миграции для получения дополнительной информации.

В этом проекте вы освоите следующие навыки:

  • физические столкновения
  • создавать текстовые поля
  • пользовательские таймеры и изображения
  • как использовать сенсорное управление и события
  • создавать фигуры, используя новый графический API

Целевое устройство

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

Платформа iOS предъявляет следующие требования:

  • iPad 1/2 / Mini: 1024px x 768px, 132 PPI
  • Сетчатка iPad: 2048px x 1536px, 264 PPI
  • iPhone / iPod Touch: 320px x 480px, 163 PPI
  • iPhone / iPod Retina: 960px x 640px, 326 PPI
  • iPhone 5 / iPod Touch: 1136px x 640px, 326 PPI

Поскольку Android является более открытой платформой, существует множество устройств и возможных разрешений. Некоторые из наиболее распространенных перечислены ниже:

  • Asus Nexus 7 Tablet: 800px x 1280px, 216 PPI
  • Motorola Droid X: 854 x 480 пикселей, 228 PPI
  • Samsung Galaxy SIII: 720px x 1280px, 306 PPI

В этом уроке мы сосредоточимся на платформе iOS и, в частности, на iPhone и iPod Touch. Тем не менее, код, используемый в этом руководстве, также может быть использован для платформы Android.


Интерфейс

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


Экспорт графики

В зависимости от выбранного устройства вам может потребоваться преобразовать графику в рекомендованное разрешение (PPI), что вы можете сделать в своем любимом графическом редакторе. Я использовал параметр « Изменить размер…» в меню « Инструменты» приложения « Просмотр» в OS X. Не забудьте дать изображениям описательное имя и сохранить их в папке проекта.


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

1
2
3
4
5
6
7
8
9
application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = ‘letterbox’
    },
}

Давайте напишем фактическое приложение. Откройте предпочитаемый вами редактор Lua. Подойдет любой текстовый редактор, но рекомендуется использовать текстовый редактор с подсветкой синтаксиса. Создайте новый файл и сохраните его как main.lua в папке вашего проекта.


Мы будем структурировать наш код, как если бы он был классом. Если вы знакомы с ActionScript или Java, вы должны найти структуру проекта знакомой.

01
02
03
04
05
06
07
08
09
10
11
Necessary Classes
 
Variables and Constants
 
Declare Functions
 
    constructor (Main function)
 
    class methods (other functions)
 
call Main function

1
display.setStatusBar(display.HiddenStatusBar)

Этот фрагмент кода скрывает строку состояния. Строка состояния — это строка в верхней части экрана устройства, на которой отображаются время, сигнал и другие индикаторы.


Установка привязок дисплея по умолчанию полезна, если вы переносите приложение из предыдущей графической библиотеки, то есть проектов, созданных с помощью предыдущей версии Corona SDK. Со времени выпуска библиотеки Graphics 2.0 точка отсчета каждого изображения изменилась от его верхнего левого угла до его центра. Чтобы изменить это в каждом изображении, которое вы используете в своем проекте, мы можем вызвать метод setDefault и передать значение от 0.0 до 1.0 , причем 0.0 — это левое значение, если вы меняете якорь x и верхнее, если вы меняете якорь у.

1
2
display.setDefault(‘anchorX’, 0)
display.setDefault(‘anchorY’, 0)

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

1
2
3
4
— Physics
 
local physics = require(‘physics’)
physics.start()

Фон

Простой фон для пользовательского интерфейса приложения. Фрагмент кода ниже рисует фон на экране.

1
2
3
— Graphics
— [Background]
local gameBg = display.newImage(‘gameBg.png’)

Заголовок

Это заголовок. Это первый интерактивный экран, который появится в нашей игре. Эти переменные хранят его компоненты.

1
2
3
4
5
6
— [Title View]
 
local title
local playBtn
local creditsBtn
local titleView

Вид кредитов

Представление «Кредиты» отображает кредиты и авторские права на приложение. Переменная creditsView используется для его хранения.

1
2
3
— [CreditsView]
 
local creditsView

Гол

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

1
2
3
— Score TextField
 
local scoreTF

шайбы

Шайбы в игре создаются и распределяются случайным образом на сцене. Группа pucks будет использоваться для их группировки, чтобы мы могли легко ими манипулировать.

1
2
3
— Pucks
 
local pucks

бдительный

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

1
2
3
— Alert
 
local alertView

Звуки

Мы будем использовать звуковые эффекты, чтобы придать игре дополнительного характера. Звуки, которые я использовал в этом проекте, были получены из as3sfxr . Вы можете найти фоновую музыку в Play On Loop .

1
2
3
4
5
— Sounds
 
local bgMusic = audio.loadStream(‘POL-sky-sanctuary-short.mp3’)
local blip = audio.loadSound(‘blip.wav’)
local wrong = audio.loadSound(‘lose.wav’)

В следующем фрагменте кода перечислены переменные, которые мы будем использовать в игре. Переменная totalPucks хранит количество шайб, размещенных на сцене, timerSrc сохраняет ссылку на таймер игры, а time ссылается на прямоугольник, который показывает оставшееся время.

1
2
3
4
5
— Variables
 
local totalPucks = 20
local timerSrc
local time

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

01
02
03
04
05
06
07
08
09
10
11
12
— Functions
 
local Main = {}
local startButtonListeners = {}
local showCredits = {}
local hideCredits = {}
local showGameView = {}
local gameListeners = {}
local createPuck = {}
local movePuck = {}
local reduceTime = {}
local alert = {}

Давайте начнем с создания заглушки для функции, которая будет инициализировать игровую логику, функцию Main .

1
2
function Main()
end

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

1
2
3
4
5
6
7
8
function Main()
  titleBg = display.newImage(‘titleBg.png’)
  playBtn = display.newImage(‘playBtn.png’, 220, 168)
  creditsBtn = display.newImage(‘creditsBtn.png’, 204, 230)
  titleView = display.newGroup(titleBg, playBtn, creditsBtn)
 
  startButtonListeners(‘add’)
end

В startButtonListeners мы добавляем прослушиватели событий к кнопкам представления заголовка. Когда кнопка воспроизведения нажата, мы показываем и запускаем игру. При нажатии на кнопку кредитов мы показываем кредиты игры.

1
2
3
4
5
6
7
8
9
function startButtonListeners(action)
  if(action == ‘add’) then
    playBtn:addEventListener(‘tap’, showGameView)
    creditsBtn:addEventListener(‘tap’, showCredits)
  else
    playBtn:removeEventListener(‘tap’, showGameView)
    creditsBtn:removeEventListener(‘tap’, showCredits)
  end
end

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

1
2
3
4
5
6
function showCredits:tap(e)
  playBtn.isVisible = false
  creditsBtn.isVisible = false
  creditsView = display.newImage(‘credits.png’, -110, display.contentHeight-80)
  transition.to(creditsView, {time = 300, x = 0, onComplete = function() creditsView:addEventListener(‘tap’, hideCredits) end})
end

Когда игрок нажимает на кредиты, вид меняется со сцены и удаляется.

1
2
3
4
5
function hideCredits:tap(e)
  playBtn.isVisible = true
  creditsBtn.isVisible = true
  transition.to(creditsView, {time = 300, y = display.contentHeight+creditsView.height, onComplete = function() creditsView:removeEventListener(‘tap’, hideCredits) display.remove(creditsView) creditsView = nil end})
end

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

1
2
3
function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners(‘rmv’) display.remove(titleView) titleView = nil end})
end

Мы начнем с создания текстового поля счета, как показано ниже.

1
2
3
4
5
6
7
8
function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners(‘rmv’) display.remove(titleView) titleView = nil end})
 
  — TextFields
 
  scoreTF = display.newText(‘0’, 25, 18, ‘Courier’, 15)
  scoreTF:setFillColor(15, 223, 245)
end

Метод newText принимает несколько аргументов.

  • Исходный текст текстового поля.
  • Координата x текстового поля.
  • y координата текстового поля.
  • Шрифт текстового поля и размер шрифта.

На iOS у вас есть доступ к широкому спектру шрифтов . На Android доступно только три шрифта; Droid Sans, Droid Serif и Droid Sans Mono.


На следующем шаге мы создадим форму прямоугольника таймера, используя встроенную библиотеку векторной графики Corona. Функция newRect создает прямоугольник шириной 20pt, высотой 6pt и помещает его в верхний правый угол сцены. Цвет новых фигур по умолчанию — белый, поэтому нам нужно изменить цвет прямоугольника, вызвав setFillColor и передав значение RGB. Библиотека Graphics 2.0 не использует значения в диапазоне от 0 до 255 для значений RGB. Вместо этого ожидается, что значение RGB будет варьироваться от 0.0 до 1.0 .

01
02
03
04
05
06
07
08
09
10
11
12
13
function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners(‘rmv’) display.remove(titleView) titleView = nil end})
 
  — TextFields
 
  scoreTF = display.newText(‘0’, 25, 18, ‘Courier’, 15)
  scoreTF:setFillColor(15, 223, 245)
 
  — Timer
 
  time = display.newRect(450, 20, 20, 6)
  time:setFillColor(0.05, 0.87, 0.96)
end

Быстрый и простой подход для преобразования старого значения RGB в новые значения заключается в делении старого значения на 255 . Например, 123 становится 123/255 , что соответствует 0.48 .


Группа pucks хранит все шайбы, чтобы мы могли манипулировать ими одновременно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners(‘rmv’) display.remove(titleView) titleView = nil end})
 
  — TextFields
 
  scoreTF = display.newText(‘0’, 25, 18, ‘Courier’, 15)
  scoreTF:setFillColor(15, 223, 245)
 
  — Timer
 
  time = display.newRect(450, 20, 20, 6)
  time:setFillColor(0.05, 0.87, 0.96)
 
  — Pucks
 
  pucks = display.newGroup()
end

Чтобы закончить showGameViewMethod , мы устанавливаем слушателей игры и запускаем фоновую музыку. Если для параметра loops задано значение -1 , фоновая музыка будет зацикливаться до тех пор, пока мы не скажем ей остановиться.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners(‘rmv’) display.remove(titleView) titleView = nil end})
 
  — TextFields
 
  scoreTF = display.newText(‘0’, 25, 18, ‘Courier’, 15)
  scoreTF:setFillColor(15, 223, 245)
 
  — Timer
 
  time = display.newRect(450, 20, 20, 6)
  time:setFillColor(0.05, 0.87, 0.96)
 
  — Pucks
 
  pucks = display.newGroup()
 
  gameListeners(‘add’)
  audio.play(bgMusic, {loops = -1, channel = 1})
end

В showGameView мы вызываем функцию gameListeners в которой создаются шайбы, вызывая функцию createPucks . Мы также создаем прямоугольник для отображения времени игры.

01
02
03
04
05
06
07
08
09
10
11
12
function gameListeners(action)
  if(action == ‘add’) then
    createPucks()
    timerSrc = timer.performWithDelay(1000, reduceTime, 20)
  else
    for i = 1, pucks.numChildren do
      pucks[i]:removeEventListener(‘touch’, movePuck)
    end
    timer.cancel(timerSrc)
    timerSrc = nil
  end
end

В createPucks мы используем цикл for для создания экземпляров шайб. Количество шайб хранится в totalPucks , который мы установили на 20 немного раньше в этом уроке.

Случайная позиция рассчитывается для каждой шайбы с использованием math.random . Мы также используем math.random чтобы помочь нам загрузить другой цвет из изображений проекта. Как это работает? Мы генерируем случайное целое число от 1 до 4 и добавляем результат к имени изображения, которое мы хотим загрузить. Например, если случайное число равно 3 , игра загружает изображение с именем puck3.png .

01
02
03
04
05
06
07
08
09
10
11
12
13
function createPucks()
  for i = 1, totalPucks do
    local p
    local rnd = math.floor(math.random() * 4) + 1
    p = display.newImage(‘puck’ .. tostring(rnd) .. ‘.png’, math.floor(math.random() * display.contentWidth), math.floor(math.random() * display.contentHeight))
    p.name = ‘p’ .. tostring(rnd)
    p:addEventListener(‘touch’, movePuck)
    — Physics
    physics.addBody(p, ‘dynamic’, {radius = 12})
    p.isSensor = true
    pucks:insert(p)
  end
end

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function movePuck(e)
  if(e.phase == ‘began’) then
    — Collision
    e.target.x = ex — e.target.width/2
    e.target.y = ey — e.target.height/2
    e.target:addEventListener(‘collision’, onCollision)
  end
  if(e.phase == ‘moved’) then
    e.target.x = ex — e.target.width/2
    e.target.y = ey- e.target.height/2
  end
  if(e.phase == ‘ended’) then
    e.target:addEventListener(‘collision’, onCollision)
  end
end

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

1
2
3
4
5
6
7
8
function reduceTime(e)
  time.xScale = time.xScale — 0.05
  time.x = 450
  if(time.xScale <= 0.2) then
    display.remove(time)
    alert()
  end
end

Функция onCollision отвечает за обработку столкновений между шайбами.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
function onCollision(e)
  if(e.other ~= nil) then
    if(e.other.name == e.target.name) then
      audio.play(blip)
      — Local Score
      local score = display.newText(’50’, e.other.x, e.other.y, ‘Courier New Bold’, 14)
      transition.to(score, {time = 500, xScale = 1.5, yScale = 1.5, y = score.y — 20, onComplete = function() display.remove(score) score = nil end })
      — Remove
      display.remove(e.other)
      e.other = nil
      — Total Score
      scoreTF.text = tostring(tonumber(scoreTF.text) + 50)
      scoreTF.x = 15
    else
      audio.play(wrong)
      alert(‘lose’)
    end
  end
end

Вот что происходит, когда происходит столкновение:

  • Сначала мы проверяем, совпадают ли названия шайб, чтобы убедиться, что их цвет совпадает.
  • Если они имеют одинаковый цвет, мы играем звуковой эффект.
  • Счет игрока увеличивается на 50 очков, а текстовое поле счета обновляется.
  • Если цвет шайб не совпадает, воспроизводится другой звуковой эффект, и игроку отображается вид «игра поверх предупреждения».

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function alert(action)
  timer.performWithDelay(700, function() audio.stop(1) audio.dispose(bgMusic) bgMusic = nil end, 1)
  gameListeners(‘rmv’)
  if(action == ‘lose’) then
    alertView = display.newImage(‘gameOver.png’, 155, 125)
  else
    alertView = display.newImage(‘timeUp.png’, 155, 125)
    local score = display.newText(scoreTF.text, 225, 160, ‘Courier New Bold’, 20)
    score:setFillColor(255, 255, 255)
  end
  transition.from(alertView, {time = 300, xScale = 0.5, yScale = 0.5})
 
  — Wait 100 ms to stop physics
  timer.performWithDelay(10, function() physics.stop() end, 1)
end

Чтобы начать игру, мы вызываем функцию Main как показано ниже.

1
Main()

Экран загрузки

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


Икона

Используя графику, которую вы создали ранее, теперь вы можете создать красивый значок. Размеры значков для iPhone без сетчатки составляют 57px x 57px, в то время как версия Retina должна быть 114px x 114px. Иллюстрации для iTunes должны быть размером 1024px x 1024px. Я предлагаю сначала создать обложку iTunes, а затем создать изображения меньшего размера, уменьшив обложку iTunes до нужных размеров. Нет необходимости делать значок приложения глянцевым или добавлять закругленные углы, так как об этом позаботится операционная система.


тестирование

Пришло время протестировать наше приложение в симуляторе. Откройте Corona Simulator, перейдите в папку вашего проекта и нажмите « Открыть» . Если все работает, как ожидалось, вы готовы к последнему шагу.


Сложение

В Corona Simulator перейдите в File> Build и выберите целевое устройство. Заполните обязательные поля и нажмите « Построить» . Подождите несколько секунд, и ваше приложение готово к тестированию на устройстве и / или будет отправлено для распространения.


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