В этом уроке я научу вас, как создать игру Word Drop с использованием Corona SDK. Мы будем строить игру от начала до конца, поэтому я призываю вас следовать за ней. В этом уроке мы будем работать с таймерами, физикой и реализовывать собственные сенсорные элементы управления. Давайте начнем.
Вступление
Цель игры — создать слова из буквенных шаров. Если шары пересекают планку сверху, игра окончена. У вас есть три бомбы, которые вы можете использовать, чтобы убрать пять шариков, поэтому важно использовать их экономно. Каждые десять секунд все больше шариков падают с верхней части экрана. Взгляните на скриншот, чтобы получить представление об игре.
1. Новый проект
Откройте Corona Simulator и выберите Новый проект .
На следующем экране примените следующие настройки.
Нажмите « Далее» и выберите « Открыть в редакторе» . Это откроет Main.lua
в выбранном вами текстовом редакторе.
2. Конфигурация проекта
Откройте Config.lua и замените содержимое файла конфигурацией, показанной ниже. Это установит ширину, высоту, масштаб и частоту кадров в проекте (кадров в секунду). letterBox
масштаба letterBox
означает, что приложение будет масштабироваться в обоих направлениях настолько равномерно, насколько это возможно. При необходимости, однако, игра будет иметь почтовый ящик.
1
2
3
4
5
6
7
8
|
application = {
content = {
width = 320,
height = 480,
scale = «letterBox»,
fps = 30,
}
}
|
3. Скрытие строки состояния
Чтобы строка состояния не отображалась в верхней части экрана, добавьте следующий фрагмент в Main.lua
.
1
|
display.setStatusBar(display.HiddenStatusBar)
|
4. Локальные переменные
В следующем фрагменте кода вы можете увидеть список переменных, которые мы будем использовать в этой игре. Прочитайте комментарии, чтобы получить представление об ответственности каждой переменной.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
local gameBackground = display.newImage(«background.png»,true)
local theWord = «» — the word the user spells
local theWordText — shows the word
local isNewGame = true
local allBallsGroup — group to hold all the balls
local wordTable = {} — will hold the words from the text file
local chosenBalls = {} — holds the balls the user has chosen
local instructionsText — shows instructions
local countDownText — shows the time
local numBallsToGenerate = 10 — how many balls to generate
local allBalls = {} — holds all the balls that have been generated
local theBombs = {} — holds references to the bomb image
local generateBallTimer — timer for generating balls
local clockTimer — timer for the clock
local gameTime = 10 — how many seconds before new balls drop
|
5. Настройка физики
С помощью следующего фрагмента мы требуем и запускаем физический движок. Мы устанавливаем гравитацию, чтобы шарики быстро падали.
1
2
3
|
local physics = require(«physics»)
physics.start(true)
physics.setGravity(0,50)
|
6. setup
При setup
мы запускаем генератор случайных чисел, чтобы гарантировать, что math.random
генерирует случайное число, когда оно нам нужно.
1
2
3
|
function setup()
math.randomseed(os.time())
end
|
Вызовите setup
сразу после ее объявления.
1
|
setup()
|
7. setupGameAssets
На следующих нескольких шагах мы остановим ряд функций, которые мы реализуем чуть позже в этом уроке.
В setupGameAssets
ресурсы игры настраиваются.
1
2
3
|
function setupGameAssets()
end
|
8. newGame
Функция newGame
отвечает за запуск новой игры.
1
2
3
|
function newGame()
end
|
9. setupGameBoundaries
В setupGameBoundaries
устанавливаются границы для шаров слева, справа и снизу экранов.
1
2
3
|
function setupGameBoundaries()
end
|
10. setupButtons
Функция setupButtons
устанавливает кнопки.
1
2
3
|
function setupButtons()
end
|
11. setupTextFields
В setupTextFields
текстовые поля настраиваются.
1
2
3
|
function setupTextFields()
end
|
12. setupBombs
Эта функция устанавливает изображения для бомб.
1
2
3
|
function setupBombs()
end
|
13. setBombsVisible
setBombsVisible
, бомбы становятся видимыми.
1
2
3
|
function setBombsVisible()
end
|
14. setBombsInvisible
Эта функция не нуждается в объяснении. Правильно?
1
2
3
|
function setBombsInvisible()
end
|
15. generateBalls
Функция generateBalls
отвечает за генерацию новых шаров через регулярные промежутки времени.
1
2
3
|
function generateBalls()
end
|
16. startTimer
startTimer
запускает таймер.
1
2
3
|
function startTimer()
end
|
17. doCountDown
В doCountDown
время игры уменьшается.
1
2
3
|
function doCountdown()
end
|
18. createBall
Функция createBall
отвечает за создание одного шара. Требуется один аргумент, буква, которую содержит мяч.
1
2
3
|
function createBall(createVowel)
end
|
19. checkGameOver
В checkGameOver
мы проверяем, закончилась ли игра, то есть перешла ли стена из шаров в верхнюю часть экрана.
1
2
3
|
function checkGameOver()
end
|
20. checkWord
В checkWord
мы проверяем правильность написанного пользователем слова.
1
2
3
|
function checkWord()
end
|
21. resetWord
Функция resetWord
вызывается, когда игрок отменяет resetWord
.
1
2
3
|
function resetWord()
end
|
22. createVowelBalls
Функция createVowelBalls
гарантирует, что некоторые шары содержат гласный. Он принимает один параметр, количество шаров, которые должны содержать гласную.
1
2
3
|
function createVowelBalls(number)
end
|
23. formString
Эта функция создает строку из букв на шарах, которые выбрал пользователь.
1
2
3
|
function formString(e)
end
|
24. explode
Функция explode
вызывается, когда игрок использует бомбу. Удаляет пять шаров из игры.
1
2
3
|
function explode(e)
end
|
25. removeBall
Функция removeBall
выбирает случайный шар для удаления из игры.
1
2
3
|
function removeBall()
end
|
26. readTextFile
Функция readTextFile
используется для чтения текстового файла и сохранения содержащихся в нем слов в таблице для использования в игре.
1
2
3
|
function readTextFile()
end
|
27. Проверка прогресса
Дважды проверьте, что вы создали реализации заглушки для вышеуказанных функций, прежде чем двигаться дальше. На следующих нескольких шагах мы реализуем каждую функцию.
28. Реализация readTextFile
В readTextFile
мы читаем текстовый файл из каталога ресурсов и сохраняем каждое слово в wordTable
. Мы используем string.sub
для обрезки пробелов в конце каждого слова.
01
02
03
04
05
06
07
08
09
10
|
local path = system.pathForFile( «wordlist.txt», system.ResourceDirectory)
local file = io.open( path, «r» )
for line in file:lines() do
line = string.sub(line,
1, #line — 1)
table.insert(wordTable,line)
end
io.close( file )
file = nil
|
readTextFile
вызывается в setupGameAssets
как показано ниже. Мы будем обновлять setupGameAssets
еще несколько раз в этом уроке.
1
2
3
|
function setupGameAssets()
readTextFile()
end
|
29. Реализация setupGameBoundaries
В setupGameBoundaries
определяются границы игры. lineLeft
и lineRight
— это правая и левая границы, тогда как groundLine
— это нижняя часть экрана, если можно так выразиться, земля. Они используются физическим движком и будут препятствовать перемещению шаров за пределы игровой зоны. Мы устанавливаем их невидимыми, потому что нам не нужно их видеть. Причина, по которой мы использовали -29
, заключается в том, что радиус шаров равен 29
а физическая система использует центр объектов при тестировании на столкновение.
1
2
3
4
5
6
7
8
9
|
local groundLine = display.newRect(0, 380,display.contentWidth, 2)
local lineLeft = display.newRect(-29,0,2,display.contentHeight)
local lineRight = display.newRect(display.contentWidth-29,0,2,display.contentHeight)
physics.addBody(groundLine, ‘static’,{bounce=0,friction=0})
physics.addBody(lineLeft, ‘static’,{bounce=0,friction=0})
physics.addBody(lineRight,’static’,{bounce=0,friction=0})
groundLine.isVisible = false
lineLeft.isVisible = false
lineRight.isVisible = false
|
Как и readTextFile
, setupGameBoundaries
вызывается в setupGameAssets
.
1
2
3
4
|
function setupGameAssets()
readTextFile()
setupGameBoundaries()
end
|
30. Реализация setupButtons
setupButtons
как показано ниже, и вызовите функцию в setupGameAssets
.
1
2
3
4
5
|
local goButton = display.newImage(«goButton.png»,260,420)
goButton:addEventListener(‘tap’, checkWord)
local stopButton = display.newImage(«stopButton.png»,5,430)
stopButton:addEventListener(‘tap’,resetWord)
local bar = display.newImage(«bar.png»,0,100)
|
1
2
3
4
5
|
function setupGameAssets()
readTextFile()
setupGameBoundaries()
setupButtons()
end
|
31. Реализация setupTextFields
setupTextFields
как показано ниже, и вызовите функцию в setupGameAssets
.
1
2
3
4
5
6
7
8
|
countDownText = display.newText(gameTime,290,10,native.systemFontBold,20)
countDownText:setTextColor(«#000000»)
theWordText = display.newText(«»,60,437,native.systemFontBold,25)
theWordText:setTextColor(«#000000»)
instructionsText = display.newText(«»,0,0,native.systemFontBold,25)
instructionsText.x = display.contentWidth/2
instructionsText.y = display.contentHeight/2
instructionsText:setTextColor(«#000000»)
|
1
2
3
4
5
6
|
function setupGameAssets()
readTextFile()
setupGameBoundaries()
setupButtons()
setupTextFields()
end
|
32. Реализация setupBombs
setupBombs
как показано ниже, и вызовите функцию в setupGameAssets
. В setupBombs
мы генерируем три изображения бомб. Храня изображения в таблице, мы можем ссылаться на них, не объявляя три отдельные переменные изображения.
1
2
3
4
5
6
7
8
9
|
for i=1, 3 do
local tempBomb = display.newImage(«bomb.png»)
tempBomb.width = 30
tempBomb.height = 30
tempBomb.x = 33 * i
tempBomb.y = 20
tempBomb:addEventListener(‘tap’, explode)
table.insert(theBombs,tempBomb)
end
|
1
2
3
4
5
6
7
|
function setupGameAssets()
readTextFile()
setupGameBoundaries()
setupButtons()
setupTextFields()
setupBombs()
end
|
33. setupGameAssets
Завершите реализацию setupGameAssets
, добавив фрагмент, показанный ниже. Инициализирует группу для шаров.
1
|
allBallsGroup = display.newGroup();
|
34. Завершающая setup
Когда функция setupGameAssets
готова к использованию, мы можем вызвать ее в функции setup
как показано ниже.
1
2
3
4
|
function setup()
math.randomseed(os.time())
setupGameAssets()
end
|
35. Реализация createBall
У нас есть две таблицы, одна для алфавита и одна только для гласных, поскольку мы хотим, чтобы некоторые шары содержали гласные. Затем мы генерируем случайные значения для переменных ballType
и ballSize
. Значение ballType
варьируется от 1
до 4
, тогда как значение ballSize
варьируется от 1
до 2
. Используя эти переменные, мы получаем цвет шара и устанавливаем его радиус. letterText
использует случайную букву, которую мы сгенерировали, и устанавливает ее x
и y
letterText
шару. Затем мы вставляем букву и мяч в группу, чтобы они выглядели как один элемент в игре. Затем мы генерируем случайную позицию x
для шара и помещаем его в -40
для позиции y
. Мы добавляем физику в шар, чтобы убедиться, что он падает с верхней части экрана на нижнюю. Присвойте ему name
и letter
ключи, добавьте событие касания и вставьте его в таблицу allBalls
а также в таблицу allBallsGroup
. Последнее позволяет нам работать со всеми шарами, которые в данный момент находятся в игре.
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
local var alphabetArray = {«A», «B», «C», «D», «E», «F», «G», «H», «I», «J», «K», «L», «M», «N», «O», «P», «Q», «R», «S», «T», «U», «V», «W», «X», «Y», «Z»}
local vowels = {«A»,»E»,»I»,»O»,»U»}
local ballType = math.random(4)
local ballSize = math.random(2)
local letterIndex
local letter
if(createVowel == true) then
letterIndex = math.random(#vowels)
letter = vowels[letterIndex]
else
letterIndex = math.random(#alphabetArray);
letter = alphabetArray[letterIndex]
end
local ballGroup = display.newGroup();
local ball
local ballRadius
if(ballType == 1) then
ball = display.newImage(«greenBall.png»)
elseif(ballType == 2) then
ball = display.newImage(«brownBall.png»)
elseif(ballType == 3) then
ball = display.newImage(«pinkBall.png»)
else
ball = display.newImage(«redBall.png»)
end
if(ballSize == 1)then
ball.width = 48
ball.height = 48
ballRadius = 24
else
ball.width = 58
ball.height = 58
ballRadius = 29
end
local letterText = display.newText( letter, 0,0, native.systemFontBold, 25 );
letterText:setTextColor(0,0, 0)
letterText.x = ball.x
letterText.y = ball.y
ballGroup:insert(ball)
ballGroup:insert(letterText)
ballGroup.x = math.random(ballRadius,display.contentWidth-ballRadius*2)
ballGroup.y= — 40
physics.addBody(ballGroup, ‘dynamic’,{friction = 0,bounce = 0,radius = ballRadius})
ballGroup.name = «ball»
ballGroup.letter = letter
ballGroup:addEventListener(‘tap’,formString)
table.insert(allBalls,ballGroup)
allBallsGroup:insert(ballGroup)
|
Если вы вызываете эту функцию в setup
, вы должны увидеть, как создается один шар, и он падает с верхней части экрана на нижнюю. Не забудьте удалить вызов из метода setup
когда закончите тестирование.
36. Реализация generateBalls
В generateBalls
мы вызываем checkGameOver
через 1500 миллисекунд, что дает шарикам достаточно времени, чтобы пройти мимо бара. Если это новая игра, нам нужно сгенерировать десять шаров, в противном случае мы сгенерируем четыре шара, из которых хотя бы один содержит гласную. Мы рассмотрим реализацию createVowelBalls
ближайшее время. Если вы вызываете generateBalls
в setup
, вы должны увидеть, как генерируются десять шаров.
01
02
03
04
05
06
07
08
09
10
11
|
function generateBalls()
timer.performWithDelay(1500,checkGameOver)
if(isNewGame == true) then
numBallsToGenerate = 10
else
numBallsToGenerate = 4
createVowelBalls(1)
end
generateBallTimer = timer.performWithDelay( 50, createBall, numBallsToGenerate)
end
|
37. Реализация createVowelBalls
Все, что делает эта функция, так это вызывает createBall
столько раз, сколько значение number
было передано в функцию. В качестве параметра мы передаем true
, что означает, что createBall
сгенерирует шар, содержащий гласную.
1
2
3
|
for i=1, number do
createBall(true)
end
|
38. Реализация removeBall
Эта функция выбирает случайный шар из таблицы allBalls
и удаляет его. Эта функция вызывается функцией explode
, которую мы реализуем через несколько минут.
1
2
3
4
5
|
local randomIndex = math.random(#allBalls)
local tempBall = allBalls[randomIndex]
tempBall:removeSelf()
tempBall = nil
table.remove(allBalls,randomIndex)
|
39. Реализация setBombsVisible
В setBombsVisible
мы перебираем бомбы и устанавливаем их видимыми.
1
2
3
|
for i=1, #theBombs do
theBombs[i].isVisible = true
end
|
40. Реализация setBombsInvisible
В этой функции мы делаем то же самое, что и в setBombsVisible
.
1
2
3
|
for i=1, #theBombs do
theBombs[i].isVisible = false
end
|
41. Реализация explode
В allBalls
мы проверяем, содержит ли allBalls
менее пяти шариков. Если присутствует менее пяти шаров, мы удаляем все шары, в противном случае мы удаляем только пять шаров.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
local thisSprite = e.target
thisSprite.isVisible = false
local randomIndex
local randomBall
if(#allBalls < 5) then
for i=1, #allBalls do
removeBall()
end
else
for i=1, 5 do
removeBall()
end
end
|
42. Реализация formString
В formString
мы формируем слово каждый раз, когда пользователь нажимает на шар. Помните, что к каждому шару добавлен letter
ключ. Мы проверяем, не содержит ли таблица chosenBalls
мяч, на который они постучали. Если это не так, мы вставляем шарик в таблицу chosenBalls
, прикрепляем букву к концу переменной theWord
и показываем ее в текстовом поле. Если мяч уже был выбран и добавлен в chosenBalls
, мы не добавляем его в chosenBalls
и вместо этого chosenBalls
сообщение на консоль. Вы уже можете протестировать нашу игру, нажав несколько шариков и увидев, как слово появляется в текстовом поле.
01
02
03
04
05
06
07
08
09
10
|
local thisSprite = e.target
local theLetter = thisSprite.letter
if(table.indexOf(chosenBalls,thisSprite) == nil) then
table.insert(chosenBalls,thisSprite)
theWord = theWord .. theLetter
theWordText.text = theWord
theWordText.x = display.contentWidth/2
else
print(«already chose that ball»)
end
|
43. Реализация resetWord
1
2
3
4
|
instructionsText.text = «»;
theWord = »
theWordText.text = «»
chosenBalls = {}
|
Эта функция сбрасывает текущее слово и очищает таблицу chosenBalls
. Если вы тестируете игру, вы можете нажать кнопку отмены, чтобы очистить текстовое поле.
44. Реализация checkWord
В checkWord
мы проверяем, является ли длина theWord
меньше или равна единице. Если это так, мы возвращаемся из функции. Нам нужно убедиться, что игрок выбрал слово с минимум двумя буквами. Затем нам нужно проверить, соответствует ли theWord
слову из wordTable
. Если этого не произойдет, мы устанавливаем для командучения текст НЕ СЛОВО и показываем его игроку. Если это так, мы chosenBalls
таблицу chosenBalls
и удаляем каждый мяч из игры.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
if(#theWord <= 1) then
return;
end
local lowerCaseWord = string.lower(theWord)
local tempBall
if(table.indexOf(wordTable,lowerCaseWord) == nil) then
instructionsText.text = «NOT A WORD!»
instructionsText:toFront()
else
for i=1, #chosenBalls do
table.remove(allBalls,table.indexOf(allBalls,chosenBalls[i]))
chosenBalls[i]:removeSelf()
chosenBalls[i] = nil
theWord = «»
theWordText.text = «»
end
chosenBalls = {}
end
|
45. Реализация doCountDown
В doCountDown
мы берем число из текстового поля, уменьшаем его и проверяем, равно ли оно нулю. Если это так, мы вызываем generateBalls
, сбрасываем его и вызываем startTimer
, который, в свою очередь, вызывает doCountDown.
1
2
3
4
5
6
7
8
|
local currentTime = countDownText.text
currentTime = currentTime -1
countDownText.text = currentTime
if(currentTime == 0) then
generateBalls()
countDownText.text = gameTime
startTimer()
end
|
46. Реализация startTimer
Реализация startTimer
проста. Мы вызываем doCountdown
каждую секунду и повторяем это столько раз, сколько указано в значении gameTime
.
1
|
clockTimer = timer.performWithDelay(1000,doCountdown,gameTime)
|
47. Реализация newGame
Чтобы начать новую игру, переменные, которые мы объявили ранее, сбрасываются, и startTimer
вызывается для запуска игры.
01
02
03
04
05
06
07
08
09
10
11
12
|
isNewGame = true
chosenBalls = {}
allBalls = {}
theWord = «»
theWordText.text = «»
instructionsText.text = «»
countDownText.text = gameTime;
createVowelBalls(2)
generateBalls()
setBombsVisible()
startTimer()
isNewGame = false
|
48. Реализация checkGameOver
В этой функции мы allBalls
таблицу allBalls
и проверяем, больше ли значение y
любого из шаров больше 100. Если это так, один или несколько шаров пересекают планку сверху, и игра окончена. Если игра окончена, шары удаляются из таблицы ballGroup
. newGame
отменяются, чтобы сделать бомбы невидимыми, и newGame
вызывается через три секунды.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
local gameOver = false;
for i=1,#allBalls do
if(allBalls[i].y < (100 — allBalls[i].height))then
gameOver = true
break;
end
end
if(gameOver) then
for i=allBallsGroup.numChildren,1,-1 do
local child = allBallsGroup[i]
child:removeSelf()
child = nil
end
timer.cancel(generateBallTimer)
timer.cancel(clockTimer)
instructionsText.text = «GameOver»
instructionsText:toFront()
setBombsInvisible()
timer.performWithDelay(3000,newGame)
|
49. Завершение игры
Нам остается только вызвать newGame
в функции setup
.
1
2
3
4
5
|
function setup()
math.randomseed(os.time())
setupGameAssets()
newGame()
end
|
Вывод
В этом уроке мы создали креативную игру в слова. Не стесняйтесь экспериментировать с кодом, который мы написали, чтобы увидеть, как он влияет на игру. Я надеюсь, что вы нашли этот урок полезным.