В этом уроке я покажу вам, как создать игру «собери кусочки», используя Corona SDK и язык программирования Lua. Мы рассмотрим, как использовать сенсорное управление и события, создавать фигуры с помощью графического API, а также будем использовать физические коллизии и таймеры. Давайте начнем.
1. Обзор приложения
В этой игре игрок сможет управлять шайбой на экране, чтобы собирать другие фигуры того же цвета. У игрока есть только ограниченное время, чтобы собрать как можно больше фигур. Если игрок трогает фигуру другого цвета, игра окончена.
В этом проекте вы освоите следующие навыки:
- физические столкновения
- создавать текстовые поля
- пользовательские таймеры и изображения
- как использовать сенсорное управление и события
- создавать фигуры, используя новый графический API
2. Целевое устройство
Первое, что нам нужно сделать, это выбрать платформу, на которой мы хотим запустить наше приложение. Это позволяет нам выбрать правильные размеры для художественного произведения, которое мы будем использовать.
Платформа 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.
3. Интерфейс
Мы будем использовать простой и удобный интерфейс с множеством форм, кнопок, растровых изображений и многим другим. Иллюстрации для интерфейса можно найти в исходных файлах этого урока.
4. Экспорт графики
В зависимости от выбранного устройства вам может потребоваться преобразовать графику в рекомендованное разрешение (PPI), что вы можете сделать в своем любимом графическом редакторе. Я использовал параметр « Изменить размер…» в меню « Инструменты» приложения « Просмотр» в OS X. Не забудьте дать изображениям описательное имя и сохранить их в папке проекта.
5. Конфигурация приложения
Мы будем использовать файл конфигурации config.lua
, чтобы приложение работало на всех устройствах в полноэкранном режиме. Файл конфигурации показывает исходный размер экрана и метод, используемый для масштабирования содержимого в случае, если приложение запускается с другим разрешением.
1
2
3
4
5
6
7
8
9
|
application =
{
content =
{
width = 320,
height = 480,
scale = ‘letterbox’
},
}
|
6. main.lua
Давайте напишем фактическое приложение. Откройте предпочитаемый вами редактор Lua. Подойдет любой текстовый редактор, но рекомендуется использовать текстовый редактор с подсветкой синтаксиса. Создайте новый файл и сохраните его как main.lua
в папке вашего проекта.
7. Структура кода
Мы будем структурировать наш код, как если бы он был классом. Если вы знакомы с 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
|
8. Скрыть строку состояния
1
|
display.setStatusBar(display.HiddenStatusBar)
|
Этот фрагмент кода скрывает строку состояния. Строка состояния — это строка в верхней части экрана устройства, на которой отображаются время, сигнал и другие индикаторы.
9. Стандартные якоря
Установка привязок дисплея по умолчанию полезна, если вы переносите приложение из предыдущей графической библиотеки, то есть проектов, созданных с помощью предыдущей версии Corona SDK. Со времени выпуска библиотеки Graphics 2.0 точка отсчета каждого изображения изменилась от его верхнего левого угла до его центра. Чтобы изменить это в каждом изображении, которое вы используете в своем проекте, мы можем вызвать метод setDefault
и передать значение от 0.0
до 1.0
, причем 0.0
— это левое значение, если вы меняете якорь x
и верхнее, если вы меняете якорь у.
1
2
|
display.setDefault(‘anchorX’, 0)
display.setDefault(‘anchorY’, 0)
|
10. Импортная физика
Мы будем использовать физическую библиотеку для обработки столкновений. Импортируйте и запустите библиотеку, используя фрагмент кода, показанный ниже.
1
2
3
4
|
— Physics
local physics = require(‘physics’)
physics.start()
|
11. Фон
Простой фон для пользовательского интерфейса приложения. Фрагмент кода ниже рисует фон на экране.
1
2
3
|
— Graphics
— [Background]
local gameBg = display.newImage(‘gameBg.png’)
|
12. Заголовок
Это заголовок. Это первый интерактивный экран, который появится в нашей игре. Эти переменные хранят его компоненты.
1
2
3
4
5
6
|
— [Title View]
local title
local playBtn
local creditsBtn
local titleView
|
13. Кредиты Просмотр
Представление «Кредиты» отображает кредиты и авторские права на приложение. Переменная creditsView
используется для его хранения.
1
2
3
|
— [CreditsView]
local creditsView
|
14. Оценка Текстовое поле
Мы создадим текстовое поле для отображения рейтинга игрока чуть позже в этом уроке. Мы сохраняем ссылку на это текстовое поле в переменной scoreTF
.
1
2
3
|
— Score TextField
local scoreTF
|
15. Шайбы
Шайбы в игре создаются и распределяются случайным образом на сцене. Группа pucks
будет использоваться для их группировки, чтобы мы могли легко ими манипулировать.
1
2
3
|
— Pucks
local pucks
|
16. Оповещение
Переменная alertView
содержит ссылку на представление предупреждений, которое отображается, когда игрок касается шайбы неправильного цвета. Окно оповещения покажет игроку, что игра окончена.
1
2
3
|
— Alert
local alertView
|
17. Звуки
Мы будем использовать звуковые эффекты, чтобы придать игре дополнительного характера. Звуки, которые я использовал в этом проекте, были получены из 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’)
|
18. Переменные
В следующем фрагменте кода перечислены переменные, которые мы будем использовать в игре. Переменная totalPucks
хранит количество шайб, размещенных на сцене, timerSrc
сохраняет ссылку на таймер игры, а time
ссылается на прямоугольник, который показывает оставшееся время.
1
2
3
4
5
|
— Variables
local totalPucks = 20
local timerSrc
local time
|
19. Объявить функции
Мы объявляем функции как 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 = {}
|
20. Конструктор
Давайте начнем с создания заглушки для функции, которая будет инициализировать игровую логику, функцию Main
.
1
2
|
function Main()
end
|
21. Добавить заголовок просмотра
Затем мы рисуем представление заголовка на экране и добавляем прослушиватель касаний к каждой кнопке. Метод 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
|
22. Пуск кнопки прослушивания
В 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
|
23. Показать кредиты
В 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
|
24. Скрыть кредиты
Когда игрок нажимает на кредиты, вид меняется со сцены и удаляется.
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
|
25. Показать игровой вид
Когда игрок нажимает кнопку воспроизведения, чтобы начать новую игру, титульный вид отключается от сцены и скрывается. Это показывает вид игры. Вид игры — это сердце игры. Давайте разберем остальную часть 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
|
26. Оценка текстового поля
Мы начнем с создания текстового поля счета, как показано ниже.
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.
27. Таймер
На следующем шаге мы создадим форму прямоугольника таймера, используя встроенную библиотеку векторной графики 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
.
28. Шайбы
Группа 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
|
29. Начать игру
Чтобы закончить 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
|
30. Игровые слушатели
В 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
|
31. Создание шайб
В 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
|
32. Функция перемещения
Мы используем сенсорные события, чтобы позволить игроку перемещать шайбы. Когда игрок касается шайбы, он выравнивается по позиции касания, пальцу игрока, а затем перемещается путем обновления своей позиции. Мы также добавляем слушателя 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
|
33. Таймер
Функция 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
|
34. Обработка коллизий
Функция 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
очков, а текстовое поле счета обновляется. - Если цвет шайб не совпадает, воспроизводится другой звуковой эффект, и игроку отображается вид «игра поверх предупреждения».
35. Оповещение
Функция 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
|
36. Позвони на главную
Чтобы начать игру, мы вызываем функцию Main
как показано ниже.
1
|
Main()
|
37. Экран загрузки
На платформе iOS файл с именем Default.png отображается во время запуска приложения. Добавьте это изображение в исходную папку вашего проекта, и оно будет автоматически добавлено компилятором Corona.
38. Иконка
Используя графику, которую вы создали ранее, теперь вы можете создать красивый значок. Размеры значков для iPhone без сетчатки составляют 57px x 57px, в то время как версия Retina должна быть 114px x 114px. Иллюстрации для iTunes должны быть размером 1024px x 1024px. Я предлагаю сначала создать обложку iTunes, а затем создать изображения меньшего размера, уменьшив обложку iTunes до нужных размеров. Нет необходимости делать значок приложения глянцевым или добавлять закругленные углы, так как об этом позаботится операционная система.
39. Тестирование в симуляторе
Пришло время протестировать наше приложение в симуляторе. Откройте Corona Simulator, перейдите в папку вашего проекта и нажмите « Открыть» . Если все работает, как ожидалось, вы готовы к последнему шагу.
40. Построить
В Corona Simulator перейдите в File> Build и выберите целевое устройство. Заполните обязательные поля и нажмите « Построить» . Подождите несколько секунд, и ваше приложение готово к тестированию на устройстве и / или будет отправлено для распространения.
Вывод
В этом уроке мы узнали о сенсорных слушателях, обнаружении столкновений и физике, а также о некоторых других навыках, которые могут быть полезны во многих играх. Поэкспериментируйте с конечным результатом и попробуйте изменить игру, чтобы создать собственную версию. Я надеюсь, что вам понравился этот урок, и вы нашли его полезным. Спасибо за чтение.