Шукати в цьому блозі

Практичне керівництво розробки ігор в Gideros 4

назад

Зміст

******************************************
  • Поліпшення гри
  • Додавання звуків
  • Додавання фонової музики
  • Додавання рекордів
  • Створення анімованих елементів гри
  • Покадрова анімація з використанням MovieClip
  • Твінінг елементи з GTween
  • Поліпшення геймплея
  • Зміна пакета рівнів за допомогою жестів
  • Впровадження магніту для машбол
******************************************

Поліпшення гри



Тепер, коли ми маємо   прототип нашої гри, давайте трохи покращимо, додавши деякі з найпоширеніших  елементів ігор.
У цьому розділі описано, як прокачати нашу гру, щоб зробити її більш привабливою та живою. Теми, що розглядаються в цьому розділі, є:

  • Додавання фонової музики та звукових ефектів у гру
  • Використання підрахунок балів  для gamification*
  • Анімація елементів гри
  • Tweening**, щоб зробити гру більш динамічною
  • Покращення геймплея за допомогою додавання жестів
  • Надання спонукальних засобів для взаємодії з елементами гри
*Гейміфіка́ція — використання ігрових практик та механізмів у неігровому контексті для залучення кінцевих користувачів до вирішення проблем. 
**Tweening (Твінінг)
Послідовність ключових кадрів, між якими прораховуються проміжні кадри створюючи необхідну анімацію.

Додавання звуків

Хоча зазвичай розробники її недооцінюють, фонова музика та звукові ефекти є важливою частиною будь-якої гри. Вона встановлює настрій і атмосферу геймплея. Але це естетика. Те, що ми повинні вивчати, це те, як налаштувати фонову музику та звукові ефекти в Gideros.
API Gideros для відтворення звуків досить простий. Ми завантажуємо звук, створюючи екземпляр класу Sound.


local sound = Sound.new("mysound.mp3")
А потім ми відтворюємо цей звук, викликаючи с sound:play() метод.


У Gideros хороша практика полягає у використанні формату MP3 для фонової музики або звуків, які не вимагають негайного відтворення. Це відбувається тому, що MP3 - це стиснутий формат, і для його розпакування потрібні додаткові ресурси.
Для звукових ефектів та інших звуків, які потребують негайного відтворення, слід використовувати WAV-файли, тому що цей формат файлів не стиснутий і може бути прочитаний і відтворений негайно.
Але що нам потрібно зробити, це забезпечити можливість вимикання та відтворення звуків. Тож, що ми будемо робити тут, створіть два окремих класи, один для фонової музики та інший для звукових ефектів, і надайте гравцю можливість включити/вимкнути їх на екрані Options.

Додавання фонової музики


Спочатку створімо музичний клас, який буде займатися відтворенням фонової музики. Давайте створимо файл Music.lua всередині папки classes у проекті Gideros Studio.
Оскільки музика не є чимось, що  буде показано на екрані, нам не потрібно успадковувати цей клас від sprite.
Але ми хочемо відправити події, коли музика вмикається чи вимикається, тому нам потрібно успадкувати від класу EventDispatcher.

Music = Core.class(EventDispatcher)
Потім, в межах методу init, ми отримаємо шлях до музичного файла та створимо з нього об'єкт Sound. Ми також підготуємо події, які надсилатимуться, коли звук буде увімкнено або вимкнено.

function Music:init(music)
 --завантажити основну тему
 self.theme = Sound.new(music)
 self.eventOn = Event.new("musicOn")
 self.eventOff = Event.new("musicOff")
end
Після цього давайте створимо метод Music: on (), який почне відтворювати звук з початку, і передамо true, щоб вказати, що ми потребуємо нескінченного циклу звуку.
Крім того, ми відправляємо  подію "musicOn".

--увімкнути  музику
function Music:on()
 if not self.channel then
 self.channel = self.theme:play(0, true)
 self:dispatchEvent(self.eventOn)
 end
end
Тепер, подібно, щоб вимкнути музику, ми створимо метод Music: off (), який припинить відтворення музичного каналу та вимкне його. Таким чином, він звільняє пам'ять і відправляє подію musicOff.

--вимкнути музику
function Music:off()
 if self.channel then
 self.channel:stop()
 self.channel = nil
 self:dispatchEvent(self.eventOff)
 end
end
Тепер давайте спробуємо наш клас. Спочатку створіть папку sounds у нашій папці проекту та в студії Gideros. Тоді ми зможемо скопіювати всі звуки, які ми будемо використовувати для цієї папки, і додати їх до студії Gideros.

Тепер нам також потрібен параметр, який буде локально зберігатися, щоб вказати, чи в даний час музика включена або вимкнена. Для реалізації цього
ми перейдемо до файлу Settings.lua і додамо music = true до початкової таблиці налаштувань.

--наші початкові налаштування
local settings = {
 username = "Player",
 curPack = 1,
 curLevel = 1,
 music = true
}
Тепер давайте перейдемо до нашого файла main.lua і створіть екземпляр нашого класу музики, вказавши шлях до фонового музичного файлу.

--фонова музика
music = Music.new("sounds/main.mp3")
Тоді нам потрібно додати події, щоб слухати, коли музика увімкнута або вимкнена, і відповідно оновлювати наші налаштування. Якщо подія musicOn відправлена, ми встановили параметр music на значення true; якщо подія musicOff відправлена, ми встановили параметр music на "false". Третє значення, true, вказує на те, що ми хочемо  зберегти це налаштування.

--коли музику ввімкнено
music:addEventListener("musicOn", function()
 sets:set("music", true, true)
end)
--коли музика відключена
music:addEventListener("musicOff", function()
 sets:set("music", false, true)
end)
Ми також повинні перевірити,  music  якщо це true, ми повинні почати грати фонову музику.
--грати музику якщо можна
if sets:get("music") then
 music:on()
end
Тепер, якщо ви запускаєте проект, слід почути відтворення фонової музики.
Останнє, що нам потрібно зробити - це забезпечити можливість on/off музики. Тож давайте перейдемо до OptionsScene.lua і додамо параметр для перемикання музики. По-перше, ми створюємо текст, який вказує, що це музичний варіант. Ми встановлюємо текст, його колір та позицію та додаємо його до сцени.

local musicText = TextField.new(conf.fontSmall, "Music: ")
musicText:setPosition(100, 250)
musicText:setTextColor(0xffff00)
self:addChild(musicText)
Потім, на основі налаштувань музики, нам потрібно визначити текст кнопки. Якщо музика увімкнена, ми встановимо Викл  текст; але якщо музика вимкнена, ми встановимо параметр Вкл текст

local switchText = "Вкл"
if sets:get("music") then
 switchText = "Викл"
end
Потім ми створюємо об'єкт TextField з цим текстом і встановлюємо його колір.

local musicSwitch = TextField.new(conf.fontSmall, switchText)
musicSwitch:setTextColor(0x00ff00)
Після цього ми можемо створити об'єкт Button з цього тексту, розмістити його та додати його до сцени.

local musicButton = Button.new(musicSwitch)
musicButton:setPosition(conf.width - musicButton:getWidth() - 100, 250)
self:addChild(musicButton)
Тепер, коли ви натискаєте кнопку, ми повинні  перемикати музику (увімкнути або вимкнути, відповідно до налаштування) і відповідно змінити текст кнопки.
musicButton:addEventListener("click", function()
 if sets:get("music") then
 music:off()
 musicSwitch:setText("Вкл")
 else
 music:on()
 musicSwitch:setText("Викл")
 end
end)
Спробуйте, запустіть проект і перейдіть на екран Options . Тут ви можете включити та вимкнути музику.

Додавання звукових ефектів

Тепер, як і фонову музику, ми також створимо клас для звукових ефектів під назвою Sounds. Різниця полягає в тому, що нам може знадобитися грати різні звуки для різних ефектів. Наприклад, для тих, коли ударяє м'яч, а інший, коли рівень закінчений. Крім того, ми не будемо відтворювати один і той же звук удару м'ячем , ми встановимо кілька звуків ударів і гратимемо випадковий звук у кожному випадку.
Інша відмінність полягає в тому, що нам потрібно знати заздалегідь, якщо ми повинні грати звуковий ефект чи ні. Отже, у нас буде флаг внутрішнього класу, щоб перевірити, чи включені звуки.
Тепер створіть Sounds.lua всередині папки class і створіть у ньому клас Sounds,
які успадкують від EventDispatcher
Sounds = Core.class(EventDispatcher)
У методі Sounds: init () ми визначаємо флаг self.isOn, щоб перевірити, чи включено звук, і визначити таблицю self.sounds для зберігання всіх звукових ефектів. Подібно до класу Music, ми також визначимо події soundsOn і soundsOff.

function Sounds:init()
 self.isOn = false
 self.sounds = {}
 self.eventOn = Event.new("soundsOn")
 self.eventOff = Event.new("soundsOff")
end
Потім, дуже схоже на клас Music, ми визначаємо методи ввімкнення та вимкнення, які спочатку перевірять внутрішній флаг, а потім, якщо потрібно, перемикатимуть його і відправлятимуть відповідну подію.

--включіть звуки
function Sounds:on()
 if not self.isOn then
 self.isOn = true
 self:dispatchEvent(self.eventOn)
 end
end
--вимкнути звуки
function Sounds:off()
 if self.isOn then
 self.isOn = false
 self:dispatchEvent(self.eventOff)
 end
end
Наступний метод , який нам потрібен, - це додавання певного звуку до певного звукового ефекту. Таким чином, метод додавання прийме ім'я звукового ефекту та шлях до звукового файла, а потім перевіре, чи існує таблиця для вказаного звукового ефекту, і якщо ні, створить її.
Врешті-решт, ми просто створюємо об'єкт Sound і додаємо його до таблиці конкретного звукового ефекту.

function Sounds:add(name, sound)
 if self.sounds[name] == nil then
 self.sounds[name] = {}
 end
 self.sounds[name][#self.sounds[name]+1] = Sound.new(sound)
end
Останній спосіб, який нам знадобиться, - це відтворення  звукового ефекту. У цьому методі ми перевіряємо, чи ввімкнено внутрішній флаг і чи дозволяється нам грати звук.
Потім ми перевіряємо, чи є звуки, визначені для цього звукового ефекту; якщо обидва є істинними, ми просто відтворюємо випадковий файл для вказаного звукового ефекту.
Якщо для певного звукового ефекту визначено лише один звуковий файл, він буде відтворюватися.


function Sounds:play(name)
 if self.isOn and self.sounds[name] then
 self.sounds[name][math.random(1, #self.sounds[name])]:play()
 end
end
Тепер знову додаємо настройки sounds = true всередині нашої початкової таблиці ініціалізації  в класі Settings.

sounds = true
Створіть екземпляр Sounds всередині main.lua і зв'язуйте події, щоб оновити налаштування, як і в класі Music.
--звуки
sounds = Sounds.new()
--настроїти звукові події, коли звуки вмикаються
sounds:addEventListener("soundsOn", function()
 sets:set("sounds", true, true)
end)
--коли звуки вимикаються
sounds:addEventListener("soundsOff", function()
 sets:set("sounds", false, true)
end)
Після цього ми хочемо включити звуки, якщо значення параметр sounds існує.

--Увімкнути звук, якщо параметр увімкнено
if sets:get("sounds") then
 sounds:on()
end
Потім ми повинні додати звукові файли до певних звукових ефектів.
Отже, у цьому прикладі ми будемо мати два звукові ефекти:

  • complete: звуковий ефект з одним звуковим файлом, коли рівень завершується
  • hit: звуковий ефект з кількома звуками, коли головний м'яч стикається з іншим м'яч

Ми можемо додати їх просто, викликавши наш визначений метод add та передаючи ім'я звукового ефекту та шлях до файлу.

sounds:add("complete", "sounds/complete.wav")
sounds:add("hit", "sounds/hit0.wav")
sounds:add("hit", "sounds/hit1.wav")
sounds:add("hit", "sounds/hit2.wav")
sounds:add("hit", "sounds/hit3.wav")
Тепер давайте перейдемо до LevelScene і відтворимо звукові ефекти. Спочатку давайте відтворимо повний звуковий ефект у методі LevelScene: completed ().
sounds:play("complete")

Далі ми будемо грати випадковий звук у вигляді методу onBeginContact, коли м'ячі стикаються.

sounds:play("hit")
Нарешті, ми хочемо додати в меню опцій, щоб включити звукові ефекти точно так само, як ми робили з фоновою музикою.
Ми робимо це, створивши текст, який визначає, що це sounds , встановлюючи його колір і положення, а також додаючи його до сцени.

local soundsText = TextField.new(conf.fontSmall, "Звуки: ")
soundsText:setPosition(100, 300)
soundsText:setTextColor(0xffff00)
self:addChild(soundsText)
Потім ми створюємо об'єкт Button з об'єкта TextField з належним текстом - залежно від налаштувань звуків - і розташовуємо відповідно. Тоді ми додамо його до сцени.

local switchText = "Вкл"
if sets:get("sounds") then
 switchText = "Викл"
end
local soundsSwitch = TextField.new(conf.fontSmall, switchText)
soundsSwitch:setTextColor(0x00ff00)
local soundsButton = Button.new(soundsSwitch)
soundsButton:setPosition(conf.width - soundsButton:getWidth() -
 100, 300)
self:addChild(soundsButton)
Потім додайте подію click до звуків Button, щоб включити та вимкнути звуки та відповідно змінити кнопку.
soundsButton:addEventListener("click", function()
 if sets:get("sounds") then
 sounds:off()
 soundsSwitch:setText("Вкл")
 else
 sounds:on()
 soundsSwitch:setText("Викл")
 end
end)
Це воно. Тепер ми можемо додати звуки до нашої гри для фонової музики або звукових ефектів в грі, а також забезпечити гравцеві можливість включити або вимкнути звуки.


Додавання  рекордів


Реалізація рекордів є обов'язковим для практично кожної гри, щоб гравці конкурували один з одним або щоб гравці повторювали рівні, щоб поліпшити свій результат. Тож давайте змінюємо наш клас GameManager, щоб також керувати балами для кожного рівня.
Усередині нашого класу GameManager class createLevel, ми вже зберігали значення за замовчуванням та значення часу, тому нам потрібні лише методи для їх встановлення та вилучення.
По-перше, ми створимо метод для встановлення рахунку , який просто прийматиме номер пачки та рівня, а також користувальницький бал у якості параметрів, і повертати true , щоб показати, що користувач побив високий бал або false , якщо рівень користувача нижчий ніж найвищий бал.
Так,як це було зроблено раніше майже для кожного методу GameManager, спочатку ми намагаємося завантажити пакет і створити інформацію про рівень, якщо нам це потрібно. Потім ми перевіряємо, чи  рахунок користувача вище, ніж збережений; і якщо це буде, ми збережемо новий рахунок і час, коли це було досягнуто. Тоді ми повертаємо true , інакше ми просто повернемо false .

function GameManager:setScore(pack, level, score)
 self:loadPack(pack)
 if(self.packs[pack][level] == nil) then
 self:createLevel(pack, level)
 end
 if(self.packs[pack][level].score < score)then
 self.packs[pack][level].score = score
 self.packs[pack][level].time = os.time()
 self:save(pack)
 return true
 end
 return false
end
Тепер ми можемо встановити рекорд, але нам також потрібен метод, щоб отримати поточний бал за певний рівень. Ми візьмемо номер пакунка та рівень як параметри, завантажуватимемо інформацію про пакет, а також створюємо інформацію про рівень за необхідності. Тоді ми можемо просто зчитати рахунок і час, коли він був досягнутий.

function GameManager:getScore(pack, level)
 self:loadPack(pack)
 if(self.packs[pack][level] == nil) then
self:createLevel(pack, level)
 end
 return self.packs[pack][level].score,
 self.packs[pack][level].time
end

Аналогічним чином ви можете застосувати будь-які додаткові параметри рівня, такі як час, необхідний для завершення рівня або скільки "зірок" завершено.
Для кожного з цих параметрів, включених в початковий рівень інформації в межах методу GameManager: createLevel, вам доведеться створити свій власний get  та set методи.

Тепер давайте перейдемо до LevelScene і впровадимо систему оцінки в нашій грі. По-перше, в межах методу LevelScene: init () ми покажемо рекорд, отриманий з класу GameManager.

Зчитування рекордів

Отже, спочатку нам потрібно отриматирекорд  і зберегти його в змінній HighScore.
Потім ми створюємо об'єкт TextField з невеликим шрифтом, який буде відображати наш рекорд.  Ми встановлюємо колір тексту та позицію, а потім додаємо його до сцени.

local highScore = gm:getScore(self.curPack, self.curLevel)
local highScoreText = TextField.new(conf.fontSmall, "Рекорд:"..highScore)
highScoreText:setTextColor(0xffff00)
highScoreText:setPosition(10, 25)
self:addChild(highScoreText)

Тепер давайте зробимо те саме для поточного рахунку, який буде 0 на початку гри.
На відміну від рекорду , яку ми покажемо, а потім замінимо, ми зробимо поточну оцінку об'єктом TextField як властивістю екземпляра, щоб ми могли оновити рахунок пізніше.
Таким чином, ми створюємо self.score  змінну, де ми можемо зберегти поточну оцінку і змінну self.scoreText, де ми можемо зберігати посилання на об'єкт TextField, який відображає оцінку. Подібно до високої оцінки, ми встановлюємо його текстовий колір та позицію та додаємо його до сцени.
self.score = 0
self.scoreText = TextField.new(conf.fontSmall, "Рекорд: 0")
self.scoreText:setTextColor(0xffff00)
self.scoreText:setPosition(10, 55)
self:addChild(self.scoreText)

Оновлення рекорду

Тепер нам потрібно створити метод оновлення рахунку на екрані. Давайте назвати це updateScore і передаємо рахунок на цей метод і всередині нього; ми додамо  бали до поточного рахунку і оновимо об'єкт self.scoreText, щоб відобразити його на екрані
function LevelScene:updateScore(score)
 self.score = self.score + score
 self.scoreText:setText("Рекорд: "..self.score)
end
У грі наша оцінка залежатиме від того, з якою силою м'ячі зіткнулися. У нас вже є подія BEGIN_CONTACT; але, на жаль, коли починається контакт, ми ще не знаємо, яка була сила зіткнення. Для цього нам доведеться додати нову подію в наш світ Box2D під назвою POST_SOLVE.
Тож давайте спочатку створіть метод onPostSolve для обробки цієї події.
Зовсім подібно до того, що ми зробили з методом onBeginContact, ми отримаємо fixtures , які стикаються і отримують їх тіла. Потім ми намагаємося визначити, чи мають тіла визначені властивості типу, і чи є вони те, що нас цікавлять.

function LevelScene:onPostSolve(e)
 --getting contact bodies
 local fixtureA = e.fixtureA
 local fixtureB = e.fixtureB
 local bodyA = fixtureA:getBody()
 local bodyB = fixtureB:getBody()
 --check if this collision interests us
 if bodyA.type and bodyB.type then
 --check which bodies collide
 if bodyA.type == "touch" and bodyB.type == "main" then
 --handle score here
 end
 end
end
Подія onPostSolve буде викликатися після події onBeginContact; але, як ви пам'ятаєте, ми знищуємо тип об'єкта TouchBall при зіткненні всередині onBeginContact. Отже, в цьому стані ми ніколи не будемо входити в те, якщо bodyA.type == "touch" і bodyB.type == "main" then statement. Таким чином, нам доведеться видалити частину коду, який встановлює значення nil і обробляє завершення рівня з onBeginContact і перемістимо його на метод onPostSolve.


Отже, тепер ми знищуємо тип об'єкта TouchBall у методі onPostSolve і оновлюємо оцінку, отримавши округлене значення властивості maxImpulse з об'єкта події. Потім ми визначаємо, чи закінчилася гра, вирахувавши self.ballsLeft та перевіряючи, чи == 0, як ми це робили в методі onBeginContact раніше.

bodyA.type = nil
self:updateScore(math.floor(e.maxImpulse))
self.ballsLeft = self.ballsLeft - 1
if self.ballsLeft == 0 then
 self:completed()
end
Тепер останнє, що потрібно зробити, - спробувати зберегти новий рахунок в методі LevelScene: complete (). Ми просто додамо одну нову лінію, щоб викликати метод GameManager: setScore і передавати поточний пакет, рівень і рахунок, який був збережений у властивості self.score.

local isNewHighScore = gm:setScore(self.curPack, self.curLevel, self.score)
Крім того, ми отримаємо логічне значення, яке покаже нам, чи є рахунок користувача новим рекордом, щоб ми знали, чи оновлювати повідомлення.
Тепер ви можете спробувати запустити гру та грати пару рівнів, тоді, коли ви повернетеся до цих рівнів, ви побачите, що ваш високий рахунок збережено та відображається.
Чи можете ви побити свій рекорд знову?

Створення анімованих елементів гри

Анімації - важлива частина вдосконалення гри. Вони роблять вашу гру стильнішою та живою, що зацікавлює багатьох гравців, і саме тому анімація є одним із способів вдосконалення нашої гри.
Є два основні способи анімувати елементи в Gideros:


  • Використання покадрової анімації   MovieClip
  • рух і трансформація  об'єкта за допомогою класу GTween

Покадрова анімація з використанням MovieClip

Спочатку створіть покадрову анімацію для нашого TouchBall. Щоб створити екземпляр MovieClip, ми повинні передати послідовність frame -кадрів , з яких складається анімація.


Кожен кадр анімації містить інформацію, таку як {1, 10, sprite},   номер  початкового фрейму, і номер  кінцевого фрейму та зоображення sprite, яке буде відображатися під час цього проміжку фреймів(від 1 до 10  ,тобто   1 кадр відображатиметься 10 фреймів).
Якщо ви хочете показати наступний sprite без перекриття попереднього,
ми додаємо інше визначення до таблиці, використовуючи той самий номер фрейму, що й номер кінцевого попереднього спрайту плюс один, {11, 20, sprite2}.
Оскільки ми запускаємо нашу гру на 60FPS на секунду, якщо ми хочемо, щоб на екрані з'явився 1 секунда, ми повинні забезпечити для нього діапазон 60 фреймів.
Таким чином, додавання іншого рядка в таблицю з об'єктом sprite, який з'явиться на 1 секунду на екрані, буде виглядати так: {21, 80, sprite3}. Крім того, ми могли б також змінювати ці кадри, надаючи початкові та кінцеві властивості; але зараз нам це не потрібно.
Тепер давайте створимо нашу першу кадрову анімацію. Відкрийте TouchBall.lua файл, а потім завантажте зображення, які ми будемо використовувати в нашій анімації. Ми маємо ці зоображення, визначені в нашому пакеті текстур, які називаються touch0.png, touch1.png ...та touch18. png..Це було зроблено навмисно, щоб ми могли завантажувати їх у цикл.
Спочатку визначимо таблицю frames , в якій ми зберігаємо зображення для кадрів. Потім,
ми створюємо об'єкт Bitmap для кожної текстури і встановлюємо його точку прив'язки до 0.5 всередині циклу від 0 до 18 (як наші імена зображень). Після цього ми просто зберігаємо посилання на об'єкт Bitmap у нашу таблицю frames під тим самим ідентифікатором, який використовувався в зображенні.

local frames = {}
for i = 0, 18 do
 local bitmap = Bitmap.new(self.level.g:getTextureRegion("touch"..i..".png"))
 bitmap:setAnchorPoint(0.5, 0.5)
 frames[i] = bitmap
end
Замість того, щоб створювати єдине зображення Bitmap для позначення сенсорного м'яча, ми можемо створити об'єкт MovieClip, визначивши анімацію з завантаженими зображеннями. Як вже було сказано раніше, ми надаємо діапазон фреймів, в якому кожне зображення буде відображатися на екрані та надавати цю таблицю методу MovieClip.new ().
Деякі зображення будуть повторюватися. Наприклад, щоб кілька разів обертати голову вбік, ми просто повторюємо ті ж зображення з новими інтервалами кадрів.

self.bitmap = MovieClip.new{
 {1, 10, frames[0]},
 {11, 15, frames[1]},
 {16, 20, frames[2]},
 {21, 30, frames[3]},
 {31, 40, frames[4]},
 {41, 45, frames[5]},
 {46, 50, frames[4]},
 {51, 55, frames[6]},
 {56, 60, frames[4]},
 {61, 65, frames[5]},
 {66, 70, frames[4]},
 {71, 75, frames[6]},
 {76, 80, frames[4]},
 {81, 85, frames[5]},
 {86, 90, frames[4]},
 {91, 95, frames[6]},
 {96, 100, frames[3]},
 {101, 110, frames[2]},
 {111, 115, frames[1]},
 {116, 200, frames[0]},
 {201, 205, frames[7]},
 {206, 210, frames[8]},
 {211, 215, frames[9]},
 {216, 220, frames[10]},
 {221, 225, frames[11]},
 {226, 230, frames[12]},
 {231, 235, frames[13]},
 {236, 240, frames[14]},
 {241, 245, frames[15]},
 {246, 250, frames[16]},
 {251, 255, frames[17]},
 {256, 260, frames[18]}
}
Якщо ви подивитеся на графіку , ви побачите, що насправді існує два види анімації. Один з них, де об'єкт насміхається над нами, а інший, коли об'єкт був вражений. Ми додали обидва з них до екземпляру MovieClip, і тепер нам потрібно їх розділити.
Отже, спочатку  кулька буде насміхатися тільки і відображатиме тільки hit анімацію
після того, як він був вражений.
Для цього ми можемо визначити перейти до дій. Тому, коли анімація досягає кінцевого кадру останнього зображення першої анімації, ми повинні автоматично перейти до першого кадру першого зображення однієї анімації. У нашому випадку кінець фрейму першої анімації становить 200; тому з 200 нам потрібно перейти до першого кадру,
що можна зробити за допомогою методу setGotoAction ().

Потім виконайте те ж саме для другої анімації, яка закінчується на 260-му кадрі і починається з 201-го кадру

self.bitmap:setGotoAction(200, 1)
self.bitmap:setGotoAction(260, 201)
Тепер нам потрібно створити метод класу TouchBall для зміни анімації з першої  на другу. Для цього ми будемо використовувати метод gotoAndPlay, який просто перейде на кадр № 201 і почне грати від нього, таким чином циклу у другій анімації.

function TouchBall:hit()
 self.bitmap:gotoAndPlay(201)
end
Нарешті, нам потрібно викликати щойно створений метод TouchBall, коли він потрапляє. Таким чином, давайте перейдемо до LevelScene.lua і назвемо bodyA.object: hit () у методі onBeginContact, відразу після того, як ми зробимо основний смайл.
Тепер, якщо ви запустите проект і спробуєте це, ви побачите, що спочатку м'яч насміхається; і після удару він отримує очі, що крутиться.

Твінінг елементи з GTween

Ми вивчили перший тип анімації, тобто анімацію покадрову; давай створимо  Твінінг  Tweening або інтерполяція - це процес створення анімаційних кадрів між двома станами трансформації. Наприклад, якщо ми хочемо анімувати об'єкт, що рухається з однієї координати в іншу, ми могли б просто встановити  координати і рухати  об'єкт за допомогою спеціальної бібліотеки tweening. Це автоматично змінює координати поетапно, щоб забезпечити анімацію, доки вона не досягне цільової координати. У Gideros можна змінити всі властивості об'єкта успадкованого спрайту , такі як координати, масштабування, і прозорість значення серед інших.
Ми можемо також сміщати tweening об'єкти з використанням MovieClip, і це дуже корисно, коли у вас є кадрова анімація, яка також потребує твінінга. Але якщо ви хочете об'єднати один простий спрацьований успадкований об'єкт, немає необхідності використовувати MovieClip; ви можете просто використовувати бібліотеку GTween для цієї мети.
Ви можете отримати останню версію GTween для Gideros за адресою https://github.com/gideros/GTween.
Просто скопіюйте gtween.lua до вашого проекту та додайте його до папки classes всередині Gideros Studio.

Потім, щоб анмувати будь-який предмет, успадкований від sprite, ви просто пишете GTween.new і вказуєте властивості, які повинні бути анімовані.
Наприклад, якщо спрайт знаходиться в координатах (10, 10) та альфа=0,5, ми хочемо анімувати його в координати (100,100) з альфа=1, і ми хочемо, щоб анімація тривала 10 секунд:
GTween.new(sprite, 10, {x=100, y=100, alpha=1}).
Ми підкреслимо колізію смайла в нашій грі, потрясаючи екран трохи, використовуючи клас GTween. Для цієї мети,давайте створимо метод shakeScreen у класі LevelScene, де ми спочатку поставимо сцену в координати (-10, -10), а потім повернемо її назад до координати (0,0) за півсекунди. Ми також будемо використовувати easing.outBounce , щоб додати ще більший ефект від коллізії.
function LevelScene:shakeScreen()
 self:setPosition(-10, -10)
 GTween.new(self, 0.5, {= 0,= 0}, {delay = 0, ease = easing.outBounce })
end
Давайте викликати метод shakeScreen відразу після зміни анімації м'яча в межах методу onBeginContact.

if bodyA.type == "touch" and bodyB.type == "main" then
 --смайл
 bodyB.object:smile()
 bodyA.object:hit()
 self:shakeScreen()
end
Тепер, коли головний м'яч   стикається з іншим,е викликає додатковий ефект струшування на всього екрану, щоб підкреслити удар.


Підсумовуючи, ви повинні використовувати MovieClip, якщо ви хочете використовувати анімацію покадрову. Використовуйте GTween, якщо ви хочете анімувати деякі властивості елемента. Хоча MovieClip також забезпечує tweening, GTween забезпечує набагато більш чистий підхід з опціями для синхронізації tweens, скидання та інвертування значень та інших, які MovieClip не пропонує.

Поліпшення геймплея


Існує багато способів покращити закавленість гравця, починаючи від геймплея самої логіки гри, полпшити інтерфейс решти ігрових сцен. Було важко вирішити, яке конкретно з поліпшень вам показати.
Отже, я вирішив показати одне з поліпшень вибору пакету рівнів. Це покращення
дозволить переключитися між пакетами за допомогою жестів. Існують також інші вдосконалення, які показують вам, як реалізувати ключовий елемент гри Машбол, керуючи   м'ячем за допомогою сенсорного вводу в магнітному стилі .

Зміна пакета рівнів за допомогою жестів

В даний час ми можемо лише перемикати пакети, натиснувши відповідну кнопку. Але для більшості мобільних користувачів це може здаватися досить природним, щоб перемикати такі види екранів, перетягнувши їх пальцем вліво або в правильному напрямку. Давайте реалізуємо це для зручності користувача

Почнемо з MouseDown

Почнемо з події  MOUSE_DOWN. Всередині ми встановимо self.isFocus властивість, щоб ми знали, що ми взаємодіємо з сценою в даний час. Ми будемо зберігати поточну координату кліка в self.startX, а позицію сцени по x у self.initX та попередньої координати x у self.prevX (який у цьому випадку буде таким же, як self.startX). Ми також визиваємо  event:stopPropagation(), щоб зупинити поширення події.

function LevelSelectScene:onMouseDown(event)
 self.isFocus = true
 self.startX = event.x
 self.initX = self:getX()
 self.prevX = event.x
 event:stopPropagation()
end

Продовжимо  з MouseMove

Тепер нам потрібно обробляти подію MOUSE_MOVE, де ми будемо рухати сцену на осі x. Отже, спочатку ми перевіряємо, чи взаємодіємо ми з цією сценою, перевіряючи прапорець self.isFocus. Потім ми обчислимо відстань, на яку ми перемістили на курсор/палець і зберігаємо її в змінній dx. Після цього ми збільшуємо позицію сцени dx. Таким чином,
вона рухається за допомогою курсора/пальця. Потім ми встановлюємо поточні координати як self.prevX, щоб ми могли використовувати його в наступний раз, і тоді ми припиняємо поширення події.

function LevelSelectScene:onMouseMove(event)
 if self.isFocus then
 local dx = event.- self.prevX
 self:setX(self:getX() + dx)
 self.prevX = event.x
 event:stopPropagation()
 end
end

Закінчимо з MouseUp

Найбільш складна частина настає, коли ми віддпускаємо сцену всередині події MOUSE_UP,  У цьому випадку нам потрібно визначити, чи нам слід переключити сцену на наступну чи попередню, або якщо зміна була настільки мала, що нам потрібно повернути сцену до свого місця.

function LevelSelectScene:onMouseUp(event)
end
Потім, ми знову перевіримо прапорець self.isFocus, щоб побачити, чи взаємодіємо ми з сценою. Потім ми створюємо back змінну, щоб вказати, чи слід повернути сцену та встановити її значення за замовчуванням на false.
if self.isFocus then
 local back = false
end
У твердженні if ми перевіряємо, чи ми перемістили сюжет принаймні до 10 пікселів вліво, перевіривши початкові координати всередині self.startX та останні координати в self.prevX і вирахувавши 10.
if self.startX < self.prevX - 10 then
Якщо це true , ми перевіряємо, чи поточний пакет не перший пакет, і є щось, що потрібно показати. Якщо є інший пакет раніше, ми називаємо self:prevPack()  (метод, який ми створили раніше). Це дозволить переключити сцену на попередню упаковку. Іншими словами, ми встановимо змінну back , щоб повернути сцену.

if self.curPack > 1 then
 self:prevPack()
else
 back = true
end
Якщо ми не перемістили сцену на ліву сторону, ми повинні перевірити, чи ми знов пересунули його до правої сторони, порівнюючи self.startX і self.prevX, і цього разу ми додамо 10 пікселів

elseif self.startX > self.prevX + 10 then
Якщо сцена була переміщена праворуч, ми спочатку перевіряємо, чи поточний пакет не останній,   self: nextPack (), якщо це не так,   ми встановимо змінну back на значення true.
if self.curPack <= #packs then
 self:nextPack()
else
 back = true
end
Потім ми виконуємо дію за умовчанням. Якщо ми не перемістили сцену на 10 пікселів праворуч або ліворуч, ми встановлюємо  back = true.

else
 back = true
end
Врешті-решт ми перевіряємо, чи back = true  ; якщо так, ми просто переміщаємо сцену до початкової позиції, яку ми зберігаємо всередині властивості self.initX. Потім ми просто встановили  self. isFocus  прапорець на false  і зупинили поширення події.

if back then
 GTween.new(self, 0.1, {= self.initX})
end
self.isFocus = false
event:stopPropagation()

Додавання слухачів подій

Оскільки ми будемо обробляти вхідні події та перетягувати сцену, ми повинні чекати, поки перехід між сценами закінчиться, так що воно не призведе до зриву позицій сцени. Отже, спочатку давайте визначимо новий метод LevelSelectScene: onEnterEnd () у файлі LevelSelectScene.lua, який буде обробляти подію кінця переходу.
Усередині цього методу ми додамо всіх наших слухачів подій миші.

function LevelSelectScene:onEnterEnd()
 self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self)
 self:addEventListener(Event.MOUSE_MOVE, self.onMouseMove, self)
 self:addEventListener(Event.MOUSE_UP, self.onMouseUp, self)
end
Потім ми можемо додати метод onEnterEnd () як слухача події до переходу сцени всередині методу LevelSelectScene: init ().
self:addEventListener("enterEnd", self.onEnterEnd, self)

Змінення коду для вибору рівнів

В даний час пакети повинні працювати на звичайній сцені без кнопок, але, як ви пам'ятаєте, ми також маємо кнопки рівня на цій сцені. Оскільки вони додаються до сцени (вони розташовані над сценою на осі z), їх події кліків будуть изиватись першими, що може перешкоджати нашим пакетам. Отже, нам потрібно повернутися до LevelSelectScene, частини коду, де ми обробляємо, натискаємо клацання кнопкою рівня і змінюємо її. Давайте перевіримо там
якщо ми перенесли сцену та введемо рівень лише в тому випадку, якщо сцена не була перенесена.
Для кращого управління подією ми будемо змінювати кнопку кожного рівня, щоб вона не використовувала наш клас Button, а скоріше звичайну подію MOUSE_UP. Таким чином, ми безпосередньо створюємо об'єкт Bitmap і додаємо до неї подію.
Крім того, ми зберігаємо посилання на сцену для кожного зображення рівня
level = Bitmap.new(Texture.new("images/level_unlocked.png", true))
level.id = i
level.scene = self

Потім ми перевіряємо, чи не вказана  властивість сцени startX у обробнику подій кліку; або якщо його було переміщено менше 10 пікселів, ми можемо змінити сцену на рівень, на який ми натискали.

level:addEventListener(Event.MOUSE_UP, function(self, e)
 if self:hitTestPoint(e.x, e.y) then
    if not self.scene.startX or math.abs(self.scene.startX - e.x) <=10 then
         sets:set("curLevel", self.id)
                       sceneManager:changeScene("level", conf.transitionTime, conf.transition, conf.easing)
 end
 end
end, level)
Тепер ви можете запустити проект і спробувати переключити пакети, проведіть пальцем.

Впровадження магніту для машбол

Одна з ключових ігрових елементів у Машбаллах - це здатність, а насправді не змінюватися, а скоріше впливати на траєкторію основного м'яча, торкаючись екрану. Отже, основний м'яч буде підтягнутий до точки дотику, на який постраждає якась магнітна сила.
Тож давайте перейдемо до LevelScene.lua і реалізуємо його. Спочатку нам потрібен метод обробки MOUSE_DOWN подій. У цьому випадку ми перевіримо, чи об'єкт MainBall, який зберігався в властивості self.mainBall, вже має певне тіло (це означатиме, що ми вже запустили м'яч). Потім ми повинні перевірити, чи не призупинено гру; і якщо це не призупинено, ми просто встановили прапор, що ми запускаємо магніт, налаштувавши self.magnetStart прапорець  і зберігаємо  координати x та y поточного магніту, чи користувач торкнувся його.

function LevelScene:onMouseDown( event )
 if self.mainBall.body then
 if not self.paused then
 self.magnetStarted = true
 self.magnetX = event.x
 self.magnetY = event.y
 end
 end
end
На подіях MOUSE_MOVE ми перевіряємо, чи запускається магніт, перевіряючи self.magnetStarted flag і змінити координати магніту на нові.

function LevelScene:onMouseMove( event )
 if self.magnetStarted then
 self.magnetX = event.x
 self.magnetY = event.y
 end
end
Якщо магніт працює у події MOUSE_UP, ми просто знімаємо прапор, встановивши self.magnetStarted на false.
function LevelScene:onMouseUp( event )
 if self.magnetStarted then
 self.magnetStarted = false
 end
end
Ось останнє, що нам потрібно зробити тут, - додати ці слухачі подій до сцени методу LevelScene: init ().

self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self)
self:addEventListener(Event.MOUSE_MOVE, self.onMouseMove, self)
self:addEventListener(Event.MOUSE_UP, self.onMouseUp, self)

Тепер, коли включений магніт, треба впливати на основний м'яч. Ми хочемо це зробити, застосувавши невеликий імпульс до кожного події ENTER_FRAME, щоб він мав більш природний магнітний ефект. Для цього ми перейдемо файл MainBall.lua і додамо новий спосіб: метод MainBall: onEnterFrame ().
Всередині ми перевіримо, чи запускається магніт, перевіряючи властивість магніту, вказану посиланням на сцену, що зберігається в властивості self.level класу MainBall

function MainBall:onEnterFrame()
 if self.level.magnetStarted then
 end
end

Потім ми визначаємо постійну силу, за допомогою якої ми впливаємо на головний м'яч усередині оператора if.
На осі x вона буде просто 1; але на осі y 1 величина сили є занадто великою, оскільки це дозволить вивести м'яч, навіть якщо він сидить на підлозі і більше не рухається. Так,
гравець міг підкинути м'яч у будь-якому напрямку і легко закінчити рівень.
Щоб зробити гру цікавішою, ми встановили іншу силу на осі y. Я виявив, що 0,6, здається, є підходящим, яка дозволяє привести м'яч до своєї інерції, але не дозволяє переміщувати м'яч на осі y, якщо вона сидить нерухомо і не має ніякої додаткової сили для нього.
Ви можете більше експериментувати з цими значеннями, щоб досягти точної поведінки, яку хочете.

local xForce = 1
local yForce = 0.6

Потім ми повинні перевірити положення магніту, порівнявши його з положенням основної кульки. Якщо положення магніту нижче, ніж положення кульки, це означає, що магніт вище, ніж м'яч, і нам потрібно інвертувати силу, щоб вона повернула м'яч догори, а не донизу.

if self.level.magnetY < self:getY() then
 yForce = -yForce
end

Зовсім подібна ситуація також виникає з осі x. Якщо розташування магніту x менше, ніж положення кульки, це означає, що магніт знаходиться на лівій стороні кулі, і знову нам потрібно інвертувати силу, яку ми будемо застосовувати до осі x.

if self.level.magnetX < self:getX() then
 xForce = -xForce
end
Тепер, коли ми взяли на себе позиції, ми можемо просто застосувати сили як лінійний імпульс до поточних координат кулі

self.body:applyLinearImpulse(xForce, yForce, self:getX(),
 self:getY())
Останнє, що потрібно зробити, це додати метод onEnterFrame як слухача події до події ENTER_FRAME в методі MainBall: init ().

self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
Тепер ви можете запустити проект, відкрити будь-який рівень, і після запуску м'яча спробуйте вплинути на його траєкторію, використовуючи магнітну силу з ваших власних кінчиків пальців.

Резюме

Ми розширили нашу гру, використовуючи більш стильні елементи керування, наприклад, використовуючи рух в руках і керуючи основним елементом гри з магнітною силою, створеної власними пальцями, що торкаються екрана. Ми також виготовили нашу гру з рекордами , а також додавали tweens та анімацію покадрову; і не забудьте про фонову музику та звуки, які ми додали, щоб доповнити нашу гру.
Тепер сама гра далеко не закінчена. Але дотепер ви дізналися про всі основи, які вам потрібно створити за допомогою Gideros. Ви також дізналися про додавання, які ви можете зробити, щоб поліпшити його візуально, акустично (ефектно) та естетично.


ЦЕ ВСЕ, ЧЕКАЮ ВІД ВАС ВАШИХ ІГОР!!













Немає коментарів:

Дописати коментар