Статьи

Corona SDK: создайте защитника обезьяны

В этом уроке мы будем создавать игру под названием Monkey Defender с использованием Corona SDK! Эта игра послужит отличной основой для множества разных жанров, включая игры в стиле обороны. Итак, начнем!


В этой версии Monkey Defender игроку придется защищать обезьяну, стреляя обезьяньими гранатами в приближающиеся космические корабли. Каждый раз, когда игрок успешно поражает вражеский космический корабль, его счет увеличивается на единицу. Если им не ударить вражеский космический корабль до того, как он достигнет обезьяны, они потеряют один банан или одну жизнь. Когда у игрока заканчиваются бананы, игра окончена!

Эта игра была построена с использованием Corona SDK, и вот несколько вещей, которые вы узнаете:

  • Как использовать раскадровку
  • Как использовать виджеты
  • Как вращать объекты на основе касания
  • Как использовать Corona’s Collision
  • Как собрать полноценную игру с Corona SDK

Чтобы использовать это руководство, на вашем компьютере должен быть установлен Corona SDK. Если у вас нет SDK, зайдите на сайт http://www.coronalabs.com, чтобы создать бесплатную учетную запись и загрузить бесплатное программное обеспечение.

Чтобы загрузить графику, которую я использовал для игры, пожалуйста, загрузите исходные файлы, прикрепленные к этому сообщению. Графика для этой игры взята с www.opengameart.org и www.vickiwenderlich.com . Фоновая графика взята из Sauer2 из Open Game Art, а остальная часть — из Вики Вендерлих. Если вы решили опубликовать эту игру с этой графикой, пожалуйста, не забудьте указать обоих художников.


Первый шаг для создания нашей игры, Monkey Defender, — это создать новый файл с именем build.settings и поместить его в папку вашего проекта. Файл build.settings обрабатывает все свойства времени сборки внутри нашего приложения. Для нашей игры нам нужно только беспокоиться об ориентации игры, и мы собираемся разрешить только ландшафтный режим.

1
2
3
4
5
6
settings = {
  orientation = {
    default = «landscapeRight»,
    supported = { «landscapeRight», «landscapeLeft»}
  },
}

Далее мы создадим еще один файл с именем config.lua и поместим его в папку вашего проекта. Файл config.lua обрабатывает все наши конфигурации времени выполнения, такие как высота, ширина, тип масштабирования и частота кадров. Для нашей игры мы настроим наше приложение на 320×480, в почтовом ящике и на 30 кадров в секунду.

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

Теперь, когда у нас настроен наш проект, мы можем перейти к созданию main.lua. Файл main.lua является отправной точкой каждого приложения, созданного с помощью Corona SDK, поэтому создайте новый файл с именем main.lua и переместите его в папку своего проекта. Внутри нашего файла main.lua мы будем скрывать строку состояния, добавлять некоторые глобальные переменные и использовать функцию раскадровки Corona для управления нашими сценами.

01
02
03
04
05
06
07
08
09
10
11
— hide the status bar
display.setStatusBar( display.HiddenStatusBar )
 
— Set up some global variables for our game
screenW, screenH, halfW, halfH = display.contentWidth, display.contentHeight, display.contentWidth*0.5, display.contentHeight*0.5
 
— include the Corona «storyboard» module
local storyboard = require «storyboard»
 
— load menu screen
storyboard.gotoScene( «menu» )

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

Для начала создайте новый файл с именем menu.lua и поместите файл в папку вашего проекта. Первое добавление к этому файлу — добавить раскадровку и библиотеку виджетов. В то время как раскадровка позволит нам легко управлять нашей сценой, библиотека виджетов позволит нам легко добавлять общие элементы в наше приложение. В этом случае мы будем использовать библиотеку виджетов, чтобы добавить кнопку в наше меню. Мы вернемся к этому позже.

1
2
3
4
local storyboard = require( «storyboard» )
local scene = storyboard.newScene()
 
local widget = require «widget»

После того, как требуется, мы создадим нашу первую функцию с именем scene:createScene() . Эта функция будет вызываться, когда сцена не существует и является идеальным местом для названия нашей игры и кнопки.

1
2
3
4
— Called when the scene’s view does not exist:
function scene:createScene( event )
  local group = self.view
end

Внутри нашей scene:createScene() , мы создадим новый экранный объект, который будет использоваться в качестве фона. Если вы этого еще не сделали, убедитесь, что вы загрузили исходные файлы для этого урока и поместили всю графику в папку с изображениями в вашем проекте.

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

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

1
2
3
4
5
— Insert a background into the game
local background = display.newImageRect(«images/background.png», 480, 320)
  background.x = halfW
  background.y = halfH
  group:insert(background)

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

1
2
3
4
5
— Insert the game title
local gameTitle = display.newText(«Space Monkey»,0,0,native.systemFontBold,32)
  gameTitle.x = halfW
  gameTitle.y = halfH — 80
  group:insert(gameTitle)

Наше последнее дополнение к функции scene:createScene() будет виджетом кнопки. Этот виджет позволит игрокам начать игру. Прежде чем мы сможем добавить виджет, нам нужно создать функцию, которая будет обрабатывать событие касания.

При нажатии кнопки вызывается следующая функция. Эта функция отправит игроков на игровую сцену, которую мы будем создавать позже.

1
2
3
4
5
— This function will only be fired when the widget playBtn is touched.
local function onPlayBtnRelease()
  storyboard.gotoScene( «game», «fade», 500 )
  return true
end

После функции onPlayBtnRelease мы добавим кнопку в сцену меню. Мы добавляем кнопку с помощью widget.newButton с парой параметров. Свойство label устанавливает текст нашей кнопки, а свойство onRelease сообщает нашему приложению, какую функцию onRelease при касании. Затем мы поместим кнопку в центр экрана и вставим ее в переменную группы. playBtn будет последним фрагментом, добавленным к scene:createScene() .

01
02
03
04
05
06
07
08
09
10
— Create a widget button that will let the player start the game
local playBtn = widget.newButton
{
  label=»Play Now»,
  onRelease = onPlayBtnRelease
}
 
playBtn.x = halfW
playBtn.y = halfH
group:insert( playBtn )

Сразу после функции scene:createScene() мы добавим функцию scene:enterScene() . Эта функция будет вызвана после того, как сцена будет создана и появится на экране.

1
2
3
4
5
6
7
8
function scene:enterScene(event)
  local group = self.view
 
  if(storyboard.getPrevious() ~= nil) then
    storyboard.purgeScene(storyboard.getPrevious())
    storyboard.removeScene(storyboard.getPrevious())
  end
end

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

1
2
3
4
scene:addEventListener(«createScene», scene)
scene:addEventListener(«enterScene», scene)
 
return scene

К настоящему времени мы завершили настройку, файл main.lua и файл menu.lua. Далее мы собираемся создать логику для нашей игры.

Логика игры будет храниться в файле с именем game.lua. Для начала создайте новый файл с именем game.lua и поместите его в папку вашего проекта. Нашим первым дополнением к новому файлу является требование Storyboard Corona.

1
2
local storyboard = require( «storyboard» )
local scene = storyboard.newScene()

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

1
2
local physics = require «physics»
physics.start();

После физики мы заранее определим переменные для нашей игры.

1
2
3
4
5
6
local background, monkey, bullet, txt_score
local tmr_createBadGuy
local lives = {}
local badGuy = {}
local badGuyCounter = 1
local score = 0

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

1
2
3
4
local numberOfLives = 3
local bulletSpeed = 0.35
local badGuyMovementSpeed = 1500
local badGuyCreationSpeed = 1000

Теперь мы создадим нашу scene:createScene функцию scene:createScene .

1
2
3
— Called when the scene’s view does not exist:
function scene:createScene( event )
local group = self.view

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

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

1
2
function touched(event)
  if(event.phase == «began») then

В операторе if-then мы используем математическую библиотеку, чтобы определить угол между обезьяной и событием касания. Это достигается с помощью комбинации math.deg и math.atan2 чтобы найти, где обезьяну нужно повернуть. После определения степени вращения мы поворачиваем обезьяну в соответствующую позицию.

1
2
angle = math.deg(math.atan2((event.y-monkey.y),(event.x-monkey.x)))
monkey.rotation = angle + 90

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

1
2
3
4
5
6
bullet = display.newImageRect(«images/grenade_red.png»,12,16)
bullet.x = halfW
bullet.y = halfH
bullet.name = «bullet»
physics.addBody( bullet, «dynamic», { isSensor=true, radius=screenH*.025} )
group:insert(bullet)

Теперь, когда у нас есть пуля, нам нужно выяснить, куда ее отправить. Чтобы сделать это, мы сначала определяем, нужно ли отправлять пулю влево или вправо, а затем используем формулу y = mx + b для определения местоположения y. Наша последняя математическая задача — найти расстояние между двумя точками, чтобы мы могли определить, как быстро проецировать пулю.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
— Find out if we need to fire the bullet to the left or right
local farX = screenW*2
 
if(event.xStart >= screenW/2)then
  farX = screenW*2
else
  farX = screenW-(screenW*2)
end
 
— Use y = mx + b to find the Y position
local slope = ((event.yStart-screenH/2)/(event.xStart-screenW/2))
local yInt = event.yStart — (slope*event.xStart)
local farY = (slope*farX)+yInt
 
— Get the distance from the bullet to bullet destination
local xfactor = farX-bullet.x
local yfactor = farY-bullet.y
local distance = math.sqrt((xfactor*xfactor) + (yfactor*yfactor))

Теперь, когда мы знаем расстояние и координаты x / y пункта назначения маркера, мы можем отправить наш элемент назначения пункту назначения с помощью transition.to . Нам также нужно будет включить пару операторов завершения, чтобы завершить затронутую функцию.

1
2
3
  bullet.trans = transition.to(bullet, { time=distance/bulletSpeed, y=farY, x=farX, onComplete=nil})
  end
end

После всей этой математики, следующие несколько шагов просты. Мы добавим фон в нашу игру (позже мы прикрепим слушателя событий к фону), обезьяну и текстовый объект для отображения счета.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
— Create a background for our game
background = display.newImageRect(«images/background.png», 480, 320)
  background.x = halfW
  background.y = halfH
  background:setFillColor( 128 )
  group:insert(background)
 
— Place our monkey in the center of screen
monkey = display.newImageRect(«images/spacemonkey-01.png»,30,40)
  monkey.x = halfW
  monkey.y = halfH
  group:insert(monkey)
 
— Create a text object for our score
txt_score = display.newText(«Score: «..score,0,0,native.systemFont,22)
  txt_score.x = 430
  group:insert(txt_score)

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

1
2
3
4
5
6
7
— Insert our lives, but show them as bananas
for i=1,numberOfLives do
  lives[i] = display.newImageRect(«images/banana.png»,45,34)
  lives[i].x = i*40-20
  lives[i].y = 18
  group:insert(lives[i])
end

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
— This function will create our bad guy
function createBadGuy()
 
  — Determine the enemies starting position
  local startingPosition = math.random(1,4)
 
  if(startingPosition == 1) then
    — Send bad guy from left side of the screen
    startingX = -10
    startingY = math.random(0,screenH)
  elseif(startingPosition == 2) then
    — Send bad guy from right side of the screen
    startingX = screenW + 10
    startingY = math.random(0,screenH)
  elseif(startingPosition == 3) then
    — Send bad guy from the top of the screen
    startingX = math.random(0,screenW)
    startingY = -10
  else
    — Send bad guy from the bototm of the screen
    startingX = math.random(0,screenW)
    startingY = screenH + 10
end

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

1
2
3
4
5
6
7
— Start the bad guy according to starting position
badGuy[badGuyCounter] = display.newImageRect(«images/alien_1.png»,34,34)
  badGuy[badGuyCounter].x = startingX
  badGuy[badGuyCounter].y = startingY
  physics.addBody( badGuy[badGuyCounter], «dynamic», { isSensor=true, radius=17} )
  badGuy[badGuyCounter].name = «badGuy»
  group:insert(badGuy[badGuyCounter])

Затем мы будем использовать Corona’s transition.to чтобы переместить плохого парня к центру экрана. Как только переход сделан и враг не нанес удар, мы удалим плохого парня и вычтем одну жизнь из игрока.

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

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
badGuy[badGuyCounter].trans = transition.to(badGuy[badGuyCounter], {
  time=badGuyMovementSpeed, x=halfW, y=halfH,
  onComplete = function (self)
  self.parent:remove(self);
  self = nil;
 
  — Since the bad guy has reached the monkey, we will want to remove a banana
  display.remove(lives[numberOfLives])
  numberOfLives = numberOfLives — 1
 
  — If the numbers of lives reaches 0 or less, it’s game over!
  if(numberOfLives <= 0) then
    timer.cancel(tmr_createBadGuy)
    background:removeEventListener(«touch», touched)
 
    local txt_gameover = display.newText(«Game Over!»,0,0,native.systemFont,32)
      txt_gameover.x = halfW
      txt_gameover.y = screenH * 0.3
      group:insert(txt_gameover)
 
      local function onGameOverTouch(event)
      if(event.phase == «began») then
        storyboard.gotoScene(«menu»)
      end
  end
 
  local txt_gameover = display.newText(«Return To Menu»,0,0,native.systemFont,32)
    txt_gameover.x = halfW
    txt_gameover.y = screenH * 0.7
    txt_gameover:addEventListener(«touch»,onGameOverTouch)
    group:insert(txt_gameover)
  end
end;
 
badGuyCounter = badGuyCounter + 1
end

Последняя функция внутри scene:createScene предназначена для обнаружения столкновений. Когда пуля и плохой парень сталкиваются, эта функция сработает, и произойдет следующее:

  • Добавьте 1 к счету игрока и обновите текстовый объект счета.
  • Установите альфа обоих объектов на 0.
  • Отмените переход на каждом объекте, чтобы он не мешал процессу удаления.
  • Наконец, мы создадим таймер, который будет ждать 1 миллисекунду, прежде чем удалять оба объекта. Это всегда плохая идея, чтобы удалить экранные объекты в середине обнаружения столкновений.
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
function onCollision( event )
  if(event.object1.name == «badGuy» and event.object2.name == «bullet» or event.object1.name == «bullet» and event.object2.name == «badGuy») then
 
    — Update the score
    score = score + 1
    txt_score.text = «Score: «..score
 
    — Make the objects invisible
    event.object1.alpha = 0
    event.object2.alpha = 0
 
    — Cancel the transitions on the object
    transition.cancel(event.object1.trans)
    transition.cancel(event.object2.trans)
 
    — Then remove the object after 1 cycle.
    local function removeObjects()
      display.remove(event.object1)
      display.remove(event.object2)
    end
 
  timer.performWithDelay(1, removeObjects, 1)
 
  end
  end
end

После scene:createScene мы напишем нашу функцию ввода сцены. Эта функция запускается после того, как сцена создана и перемещена на экран. Внутри функции ввода сцены мы запустим все функции, которые мы написали в функции создания сцены.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function scene:enterScene( event )
  local group = self.view
 
  — Actually start the game!
  physics.start()
 
  — Start sending the bad guys
  tmr_createBadGuy = timer.performWithDelay(badGuyCreationSpeed, createBadGuy, 0)
 
  — Start listening for the background touch event to fire the bullets
  background:addEventListener(«touch», touched)
 
  — Start the listener to remove bad guys and bullets when they collide
  Runtime:addEventListener( «collision», onCollision )
End

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

1
2
3
4
scene:addEventListener( «createScene», scene )
scene:addEventListener( «enterScene», scene )
 
return scene

Надеюсь, вам понравился этот урок по созданию игры Monkey Defender с помощью Corona SDK! Мы затронули множество тем — от раскадровок до геометрических формул! Если у вас есть вопросы или комментарии, пожалуйста, оставьте их ниже и спасибо за чтение.