Вступление
В этом уроке вы узнаете, как создать, Highway Dodge , простую, но захватывающую игру. Шоссе Dodge легко подобрать и играть, но оно обеспечивает захватывающее качество других популярных игр в App Store.
Шоссе Dodge начинается с размещения игрока в гоночной машине на шоссе. Во время игры игрок должен увернуться от встречного движения на шоссе, нажав на одну из трех доступных полос движения. За каждую уклоненную машину игрок получает одно очко, и игра заканчивается, когда встречная машина врезается в гоночную машину. Со временем встречные машины выходят все быстрее и быстрее, чтобы создать настоящий вызов для игрока.
Требования
Эта игра построена с использованием Lua и Corona SDK. Как минимум, вам необходимо иметь учетную запись в Corona Labs и установленном Corona SDK. Вы можете бесплатно загрузить Corona SDK на веб-сайте Corona Labs . Для этого урока я использовал сборку 2015.2731 Corona SDK.
1. Настройка шоссе Dodge
Давайте сразу же запустим Highway Dodge, создав пустой шаблон. Откройте Corona Simulator и выберите « Новый проект» в меню « Файл» . Открыв окно « Создать новый проект» , введите « Уклон шоссе» в качестве имени проекта, выберите пустой шаблон и установите ширину 400 и высоту 600 . Оставьте стандартную ориентацию установленной вертикально .
После настройки проекта загрузите изображения для Highway Dodge. Создайте новую папку в вашем новом проекте, назовите ее images и добавьте изображения в эту папку. Ваш проект должен теперь выглядеть так:
2. Настройки проекта
После настройки проекта давайте кратко рассмотрим два важных файла: build.settings и config.lua .
build.settings
Этот файл обрабатывает свойства времени сборки игры. Он хранит информацию об ориентации вашего приложения, информацию о значках, настройках iOS и настройках Android. Настройка по умолчанию подходит для нашей игры.
config.lua
Этот файл конфигурации управляет свойствами игры во время выполнения. Это включает в себя width
, height
, scale
, fps
(кадров в секунду) и imageSuffix
. Свойство, на которое нам нужно взглянуть, это imageSuffix
. Свойство imageSuffix
используется для динамического выбора изображения. В двух словах, оно говорит приложению использовать изображение с более высоким разрешением на устройстве с более высоким разрешением.
Я предоставил изображения с высоким разрешением в папке images, поэтому нам нужно соответствующим образом обновить config.lua . Файл config.lua вашего проекта должен выглядеть так, как показано ниже. Я пропустил раздел push-уведомлений, который закомментирован.
01
02
03
04
05
06
07
08
09
10
11
12
|
application = {
content = {
width = 400,
height = 600,
scale = «letterBox»,
fps = 30,
imageSuffix = {
[«@2x»] = 2,
},
},
}
|
3. Точка запуска
После настройки проекта и динамического выбора изображения перейдем к main.lua . Этот файл является точкой запуска каждого приложения, созданного с помощью Corona SDK. В нашей игре будет три строки кода.
Шаг 1
Первая строка скрывает строку состояния на устройствах iOS. Откройте main.lua и добавьте следующую строку после комментария -- Your code here
.
1
|
display.setStatusBar( display.HiddenStatusBar )
|
Шаг 2
Далее мы начинаем использовать композитор, требуя библиотеки в нашей игре. Мы делаем это, добавив следующую строку:
1
|
local composer = require( «composer» )
|
Шаг 3
Далее мы используем composer для перехода к сцене меню. Мы перемещаем сцены, вызывая функцию composer.gotoScene()
, передавая значение "scene_menu"
в качестве параметра. Значение "scene_menu"
— это имя сцены и имя файла, который мы создадим в следующем разделе.
1
|
composer.gotoScene(«scene_menu»)
|
Слово о композиторе
Composer — официальная библиотека управления сценой Corona. Composer позволяет разработчикам легко создавать сцены и переходы между сценами. В две строки я смог перейти от основной сцены к сцене меню. Если вы хотите узнать больше о Composer, посетите Руководство библиотеки Comonaser, доступное на веб-сайте Corona Labs Docs .
4. Меню
Сцена меню нашей игры будет состоять только из пары элементов. Сцена будет содержать фоновый рисунок, заголовок и кнопку запуска. Мы будем использовать встроенную библиотеку виджетов Corona для создания кнопки запуска. Библиотека виджетов позволяет нам быстро и легко создавать общие элементы пользовательского интерфейса. В нашей игре мы будем использовать ее только для создания кнопок.
Шаг 1
В папке проекта Highway Dodge создайте новый файл scene_menu.lua и откройте его в любом текстовом редакторе. Вместо того, чтобы начинать с нуля, мы собираемся использовать шаблон сцены, доступный на веб-сайте Corona Labs Docs. С их шаблоном мы сможем двигаться намного быстрее. Перейдите в Документы Corona Labs и скопируйте / вставьте шаблон сцены в scene_menu.lua .
Шаг 2
Добавьте библиотеку виджетов в нашу игру, добавив следующую строку, чуть ниже библиотеки композитора.
1
|
local widget = require( «widget» )
|
Шаг 3
Мы добавляем наш фон, используя загруженную ранее графику. Фон должен быть расположен в центре экрана. В функции scene:create()
и после screenGroup
переменной screenGroup
добавьте следующий код:
1
2
3
|
local background = display.newImageRect(sceneGroup, «images/background.png», 475, 713)
background.x = display.contentCenterX
background.y = display.contentCenterY
|
Шаг 4
Далее нам нужно добавить три полосы, которые представляют шоссе. Мы делаем это, используя таблицу для хранения дорожек и создавая цикл for
который выполняется три раза. Поместите этот фрагмент после фонового рисунка:
1
2
3
4
5
6
|
local lanes = {}
for i=1,3 do
lanes[i] = display.newImageRect(sceneGroup, «images/lane.png», 79, 713)
lanes[i].x = (display.contentCenterX — 79*2) + (i*80)
lanes[i].y = display.contentCenterY
end
|
Чтобы полосы были всегда центрированы, я разместил полосы на оси X, используя немного математики. Это гарантирует, что дорожки остаются в центре, независимо от устройства, на котором работает игра.
Шаг 5
Мы также добавляем наш логотип для Highway Dodge, размещая изображение объекта в верхней части экрана.
1
2
3
|
local logo = display.newImageRect(sceneGroup, «images/logo.png», 300, 150)
logo.x = display.contentCenterX
logo.y = 75
|
Шаг 6
Прежде чем мы сможем добавить наш виджет кнопки, нам нужно создать функцию, которая реагирует на нажатие кнопки. Мы handleButtonEvent()
функцию handleButtonEvent()
и переместим игрока на игровую сцену с помощью composer.gotoScene()
. Мы ответим только тогда, когда игрок снял палец с кнопки или на ended
фазе события.
1
2
3
4
5
|
local function handleButtonEvent( event )
if ( «ended» == event.phase ) then
composer.gotoScene(«scene_game», «slideLeft»)
end
end
|
Шаг 7
С добавленной функцией мы можем добавить кнопку. Мы создаем кнопку, используя widget.newButton
и передавая ей некоторые значения. Мы указываем ширину и высоту кнопки, файлы изображений для кнопки, отображаемый текст, тип шрифта, размер шрифта и цвет шрифта.
Мы также сообщаем Corona, какую функцию вызывать при нажатии кнопки, и помещаем ее в середину экрана. Исходные файлы этого руководства содержат комментарии, объясняющие каждое из значений, используемых для настройки кнопки.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
local btn_startPlaying = widget.newButton {
width = 220,
height = 100,
defaultFile = «images/btn-blank.png»,
overFile = «images/btn-blank-over.png»,
label = «Start Playing»,
font = system.defaultFontBold,
fontSize = 32,
labelColor = { default={ 0, 0, 0 }, over={ 0, 0, 0, 0.5 } },
onEvent = handleButtonEvent
}
btn_startPlaying.x = display.contentCenterX
btn_startPlaying.y = display.contentCenterY
sceneGroup:insert(btn_startPlaying)
|
Шаг 8
Чтобы завершить сцену меню, нам нужно удалить игровую сцену, когда она выйдет. Когда Corona перемещается между сценами, она не всегда удаляет предыдущую сцену. Без этих линий игра всегда будет в игре за сценой после того, как играется однажды.
Чтобы удалить предыдущую сцену, мы получаем имя сцены и вызываем composer.removeScene()
чтобы удалить ее, если она существует. Добавьте следующий код на scene:show()
функция scene:show()
.
1
2
3
4
|
local prevScene = composer.getSceneName( «previous» )
if(prevScene) then
composer.removeScene(prevScene)
end
|
5. Создание игровой сцены
Теперь мы можем начать работать над игровой сценой. Мы будем использовать тот же рабочий процесс, который мы использовали для создания сцены меню. Создайте новый файл scene_game.lua и скопируйте / вставьте шаблон сцены, доступный в Документах Corona Labs . Как только у вас есть код, откройте scene_game.lua в вашем любимом текстовом редакторе.
Шаг 1
Чтобы упростить нам кодирование игровой сцены, мы собираемся использовать библиотеку виджетов и библиотеку физики. Последний используется для обнаружения столкновений. Добавьте следующий код в scene_game.lua :
1
2
3
4
|
local widget = require( «widget» )
local physics = require(«physics»)
physics.start()
physics.setGravity(0,0)
|
В первой и второй строках нам требуются библиотека виджетов и библиотека физики соответственно. Затем мы начинаем физику и отключаем гравитацию. Нам не нужна гравитация для нашей игры на шоссе, вместо этого мы будем использовать transition.to()
для перемещения машин.
Шаг 2
В функции scene:create()
мы объявляем ряд переменных, которые будем использовать в игре. Эти переменные будут отвечать за машину игрока, полосы движения, машины противника и очки игрока. Чтобы было легче читать, я добавил несколько комментариев.
01
02
03
04
05
06
07
08
09
10
11
12
|
— «scene:create()»
function scene:create( event )
local lanes = {} — create a table called lanes
local playerCar — a variable for the player car
local laneID = 1 — a variable for the land id
local enemyCars = {} — a table to hold the enemy cars
local enemyCounter = 1 — start the enemy counter at 1 to keep track of the enemy cars
local sendEnemyFrequency = 2500 — defines how often to send enemy cars in milliseconds
local tmrToSendCars — a variable to hold a reference to the timer of sending cars
local playerScore = 0 — start the player score at 0
local playerScoreText — an object to hold the score text object
end
|
Шаг 3
Ниже объявлений переменных мы устанавливаем функции для игры. Мы реализуем каждую функцию на следующем шаге. Добавьте следующий код после объявления переменных в scene:create()
функция scene:create()
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
local function incrementScore()
end
local function moveCar(event)
end
local function sendEnemyCar()
end
local function onPlayAgainTouch()
end
local function onGlobalCollision(event)
end
|
Шаг 4
После функций добавляем фон и полосы. Для дорожек мы прикрепляем прослушиватель событий к каждой полосе, чтобы они реагировали на сенсорные события. При касании слушатель вызывает moveCar()
.
01
02
03
04
05
06
07
08
09
10
11
|
local background = display.newImageRect(sceneGroup, «images/background.png», 475, 713)
background.x = display.contentCenterX
background.y = display.contentCenterY
for i=1,3 do
lanes[i] = display.newImageRect(sceneGroup, «images/lane.png», 79, 713)
lanes[i].x = (display.contentCenterX — 79*2) + (i*80)
lanes[i].y = display.contentCenterY
lanes[i].id = i
lanes[i]:addEventListener(«touch», moveCar)
end
|
Шаг 5
С заданными фоном и полосами пора создать текстовый объект, чтобы держать счет игрока и создать автомобиль игрока. Счет будет в верхней части экрана, а автомобиль игрока будет расположен на самой левой полосе. Кроме того, мы сделаем автомобиль игрока динамическим физическим объектом.
01
02
03
04
05
06
07
08
09
10
|
playerScoreText = display.newText(sceneGroup, «Score: «..playerScore, 0, 0, native.systemFont, 36)
playerScoreText.x = display.contentCenterX
playerScoreText.y = 25
playerCar = display.newImageRect(sceneGroup, «images/playerCar.png», 50, 100)
playerCar.anchorY = 1
playerCar.x = lanes[1].x
playerCar.y = display.contentHeight
physics.addBody(playerCar)
playerCar.bodyType = «dynamic»
|
Шаг 6
Затем мы устанавливаем таймер для отправки автомобиля на основе значения переменной sendEnemyFrequency
и создаем прослушиватель событий времени выполнения для глобальных коллизий.
1
2
|
tmrToSendCars = timer.performWithDelay(sendEnemyFrequency, sendEnemyCar, 0)
Runtime:addEventListener( «collision», onGlobalCollision)
|
6. Добавление функциональности игры
Мы наконец можем добавить функциональность в нашу игру. В этом разделе будет добавлен дополнительный код для каждой функции, которую мы объявили в предыдущем разделе. Мы onGlobalCollision()
с incrementScore()
и onGlobalCollision()
.
incrementScore()
Эта функция вызывается, когда вражеская машина проходит мимо игрока и переход завершен. При вызове счет игрока увеличивается на 1 . Добавьте следующую реализацию в функцию incrementScore()
.
1
2
3
4
5
|
— This function will increment the player score by 1. This function is called when the transition for the enemy car is complete and is off screen.
local function incrementScore()
playerScore = playerScore + 1
playerScoreText.text = «Score: «..playerScore
end
|
moveCar()
Функция moveCar()
вызывается при касании полосы. На ended
фазе события мы берем идентификатор полосы движения и перемещаем машину в нужную полосу. В конце мы возвращаем true
чтобы указать на успешное сенсорное событие. Добавьте следующую реализацию в moveCar()
.
1
2
3
4
5
6
7
8
|
— moveCar will respond to the touch event on the lanes
local function moveCar(event)
if(event.phase == «ended») then
laneID = event.target.id — grab the lane id which will be 1, 2, or 3
transition.to(playerCar, {x=lanes[laneID].x,time=50}) — move the player car to the appropriate lane
return true — to indicate a successful touch event, return true
end
end
|
sendEnemyCar()
Функция sendEnemyCar()
создает вражескую машину, назначает машину на полосу движения, прикрепляет к ней физическое тело и использует transition.to()
чтобы отправить машину к нижней части экрана. Для начала давайте создадим объект вражеской машины.
1
|
enemyCars[enemyCounter] = display.newImageRect(sceneGroup, «images/enemyCar»..math.random(1,3)..».png», 50, 100)
|
Далее мы устанавливаем координаты x и y вражеской машины. Мы также создаем оператор if-then
чтобы назначить вражеский автомобиль той же полосе, в которой автомобиль игрока находится в 50% случаев. Это заставит игрока двигаться чаще и сделает игру веселее.
1
2
3
|
enemyCars[enemyCounter].x = lanes[math.random(1,#lanes)].x — place the car on a random lane
if(math.random(1,2) == 1) then enemyCars[enemyCounter].x = lanes[laneID].x;
enemyCars[enemyCounter].y = -125 — place the enemy off screen at the top
|
Нам также нужно повернуть вражескую машину так, чтобы она была обращена вниз, и добавить к телу кинематическую физику.
1
2
3
|
enemyCars[enemyCounter]:scale(1,-1) — rotate the cars so they are facing down
physics.addBody(enemyCars[enemyCounter]) — add a physics body to enemy cars
enemyCars[enemyCounter].bodyType = «kinematic» — make the bodies kinematic
|
Когда вражеская машина настроена, мы используем функцию transition.to()
чтобы отправить вражескую машину по одной из полос движения. Когда переход завершен, игра вызывает функцию для удаления объекта. Я также увеличиваю переменную enemyCounter
на 1, чтобы отслеживать количество машин противника в игре.
1
2
3
|
transition.to(enemyCars[enemyCounter], {y=display.contentHeight+enemyCars[enemyCounter].height+20, time=math.random(2250,3000), onComplete=function(self) display.remove(self); incrementScore(); end}) — a transition that moves the enemy car towards the bottom of the screen.
enemyCounter = enemyCounter + 1 — increase enemy counter by one for tracking
|
Наконец, мы хотим, чтобы игра стала быстрее. Для каждой отправляемой машины игра воссоздает таймер и устанавливает его на 200 мс быстрее.
1
2
3
4
5
6
|
if(enemyCounter%2 == 0) then
sendEnemyFrequency = sendEnemyFrequency — 200
if(sendEnemyFrequency < 800) then sendEnemyFrequency = 800;
timer.cancel(tmrToSendCars)
tmrToSendCars = timer.performWithDelay(sendEnemyFrequency, sendEnemyCar, 0)
end
|
onPlayAgainTouch()
Самая короткая функция onPlayAgainTouch()
возвращает проигрыватель в главное меню. Вот как выглядит реализация функции onPlayAgainTouch()
.
1
2
3
4
|
— Allow the player to return to the menu
local function onPlayAgainTouch()
composer.gotoScene(«scene_menu», «fade») — move player to menu
end
|
onGlobalCollision()
Функция onGlobalCollision()
используется для обнаружения столкновений между любыми двумя физическими объектами на экране. В нашей игре у нас есть только два типа физических объектов:
- машина игрока
- вражеская машина
Когда эти два объекта сталкиваются, игра останавливает все таймеры, переходы и прослушиватели событий. Кроме того, игра отображает игровую сцену, которая позволяет игроку снова играть.
Шаг 1
Сначала мы создаем оператор if-then
который прослушивает начальную фазу.
1
2
3
|
if(event.phase == «began») then
end
|
Шаг 2
В заявлении if-then
мы останавливаем игру. Мы приостанавливаем все переходы, отменяем таймер, приостанавливаем физику (даже если она не нужна, это хороший вызов очистки) и удаляем прослушиватель событий из всех трех полос. Добавьте следующий код внутри оператора if-then
:
1
2
3
4
5
6
7
|
transition.pause()
timer.cancel(tmrToSendCars)
physics.pause()
for i=1,3 do
lanes[i]:removeEventListener(«touch», moveCar)
end
|
Шаг 3
После того, как игровая механика остановилась, мы добавляем непрозрачный прямоугольник и текстовый объект с надписью «Game Over!» , Это дает хорошее визуальное представление о том, что игра окончена.
01
02
03
04
05
06
07
08
09
10
|
local gameOverBackground = display.newRect(sceneGroup, 0, 0, display.actualContentWidth, display.actualContentHeight)
gameOverBackground.x = display.contentCenterX
gameOverBackground.y = display.contentCenterY
gameOverBackground:setFillColor(0)
gameOverBackground.alpha = 0.5
local gameOverText = display.newText( sceneGroup, «Game Over!», 100, 200, native.systemFontBold, 36 )
gameOverText.x = display.contentCenterX
gameOverText.y = 150
gameOverText:setFillColor( 1, 1, 1 )
|
Шаг 4
Чтобы завершить глобальную функцию столкновения, мы добавляем кнопку воспроизведения еще раз, которая возвращает игрока в меню.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
local playAgain = widget.newButton {
width = 220,
height = 100,
defaultFile = «images/btn-blank.png»,
overFile = «images/btn-blank.png»,
label = «Menu»,
font = system.defaultFontBold,
fontSize = 32,
labelColor = { default={ 0, 0, 0 }, over={ 0, 0, 0, 0.5 } },
onEvent = onPlayAgainTouch
}
playAgain.x = display.contentCenterX
playAgain.y = gameOverText.y + 100
sceneGroup:insert(playAgain)
|
Тест-драйв
Запустите приложение, чтобы увидеть, все ли работает как положено. Вы должны быть в состоянии играть в игру, вражеские машины должны появиться в верхней части экрана, и ваш счет должен увеличиваться для каждой вражеской машины, которую вы успешно уклоняетесь.
Вывод
Спасибо за чтение моего руководства по созданию Highway Dodge с помощью Corona SDK. Вы можете скачать исходные файлы для этой игры с GitHub . Найдите время, чтобы подумать, как вы можете улучшить игру. Если вы ищете некоторые предложения, я бы порекомендовал добавить еще одну линию, добавить больше типов врагов и даже добавить несколько интересных бонусов.