Статьи

Corona SDK: сделайте игру в Word Drop

В этом уроке я научу вас, как создать игру Word Drop с использованием Corona SDK. Мы будем строить игру от начала до конца, поэтому я призываю вас следовать за ней. В этом уроке мы будем работать с таймерами, физикой и реализовывать собственные сенсорные элементы управления. Давайте начнем.


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

gamescreen

Откройте Corona Simulator и выберите Новый проект .

CoronaSDK_BlackBox_15

На следующем экране примените следующие настройки.

настройки

Нажмите « Далее» и выберите « Открыть в редакторе» . Это откроет Main.lua в выбранном вами текстовом редакторе.


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

1
2
3
4
5
6
7
8
application = {
   content = {
     width = 320,
     height = 480,
     scale = «letterBox»,
     fps = 30,
   }
}

Чтобы строка состояния не отображалась в верхней части экрана, добавьте следующий фрагмент в Main.lua .

1
display.setStatusBar(display.HiddenStatusBar)

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

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

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

1
2
3
local physics = require(«physics»)
physics.start(true)
physics.setGravity(0,50)

При setup мы запускаем генератор случайных чисел, чтобы гарантировать, что math.random генерирует случайное число, когда оно нам нужно.

1
2
3
function setup()
    math.randomseed(os.time())
end

Вызовите setup сразу после ее объявления.

1
setup()

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

В setupGameAssets ресурсы игры настраиваются.

1
2
3
function setupGameAssets()
 
end

Функция newGame отвечает за запуск новой игры.

1
2
3
function newGame()
 
end

В setupGameBoundaries устанавливаются границы для шаров слева, справа и снизу экранов.

1
2
3
function setupGameBoundaries()
 
end

Функция setupButtons устанавливает кнопки.

1
2
3
function setupButtons()
 
end

В setupTextFields текстовые поля настраиваются.

1
2
3
function setupTextFields()
 
end

Эта функция устанавливает изображения для бомб.

1
2
3
function setupBombs()
 
end

setBombsVisible , бомбы становятся видимыми.

1
2
3
function setBombsVisible()
 
end

Эта функция не нуждается в объяснении. Правильно?

1
2
3
function setBombsInvisible()
 
end

Функция generateBalls отвечает за генерацию новых шаров через регулярные промежутки времени.

1
2
3
function generateBalls()
 
end

startTimer запускает таймер.

1
2
3
function startTimer()
 
end

В doCountDown время игры уменьшается.

1
2
3
function doCountdown()
 
end

Функция createBall отвечает за создание одного шара. Требуется один аргумент, буква, которую содержит мяч.

1
2
3
function createBall(createVowel)
 
end

В checkGameOver мы проверяем, закончилась ли игра, то есть перешла ли стена из шаров в верхнюю часть экрана.

1
2
3
function checkGameOver()
 
end

В checkWord мы проверяем правильность написанного пользователем слова.

1
2
3
function checkWord()
 
end

Функция resetWord вызывается, когда игрок отменяет resetWord .

1
2
3
function resetWord()
 
end

Функция createVowelBalls гарантирует, что некоторые шары содержат гласный. Он принимает один параметр, количество шаров, которые должны содержать гласную.

1
2
3
function createVowelBalls(number)
 
end

Эта функция создает строку из букв на шарах, которые выбрал пользователь.

1
2
3
function formString(e)
 
end

Функция explode вызывается, когда игрок использует бомбу. Удаляет пять шаров из игры.

1
2
3
function explode(e)
 
end

Функция removeBall выбирает случайный шар для удаления из игры.

1
2
3
function removeBall()
 
end

Функция readTextFile используется для чтения текстового файла и сохранения содержащихся в нем слов в таблице для использования в игре.

1
2
3
function readTextFile()
 
end

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


В 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

В 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

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

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

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

Завершите реализацию setupGameAssets , добавив фрагмент, показанный ниже. Инициализирует группу для шаров.

1
allBallsGroup = display.newGroup();

Когда функция setupGameAssets готова к использованию, мы можем вызвать ее в функции setup как показано ниже.

1
2
3
4
function setup()
    math.randomseed(os.time())
    setupGameAssets()
end

У нас есть две таблицы, одна для алфавита и одна только для гласных, поскольку мы хотим, чтобы некоторые шары содержали гласные. Затем мы генерируем случайные значения для переменных 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 когда закончите тестирование.


В 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

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

1
2
3
for i=1, number do
   createBall(true)
end

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

1
2
3
4
5
local randomIndex = math.random(#allBalls)
local tempBall = allBalls[randomIndex]
tempBall:removeSelf()
tempBall = nil
table.remove(allBalls,randomIndex)

В setBombsVisible мы перебираем бомбы и устанавливаем их видимыми.

1
2
3
for i=1, #theBombs do
    theBombs[i].isVisible = true
end

В этой функции мы делаем то же самое, что и в setBombsVisible .

1
2
3
for i=1, #theBombs do
   theBombs[i].isVisible = false
end

В 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

В 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

1
2
3
4
instructionsText.text = «»;
theWord = »
theWordText.text = «»
chosenBalls = {}

Эта функция сбрасывает текущее слово и очищает таблицу chosenBalls . Если вы тестируете игру, вы можете нажать кнопку отмены, чтобы очистить текстовое поле.


В 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

В 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

Реализация startTimer проста. Мы вызываем doCountdown каждую секунду и повторяем это столько раз, сколько указано в значении gameTime .

1
clockTimer = timer.performWithDelay(1000,doCountdown,gameTime)

Чтобы начать новую игру, переменные, которые мы объявили ранее, сбрасываются, и 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

В этой функции мы 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)

Нам остается только вызвать newGame в функции setup .

1
2
3
4
5
function setup()
    math.randomseed(os.time())
    setupGameAssets()
    newGame()
end

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