Chapter 3 — Bringing it to Life / Глава 3 – Приведение его к жизни

Chapter 3 — Bringing it to Life / Глава 3 - Приведение его к жизни

Наш проект начинает приобретать форму, но это еще не игра. Давайте вдохнуть жизнь в этот ребенок!

Создание Астероиды

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

Как и наши предыдущие функции, мы начинаем с local functionи имя функции, createAsteroid(). Внутри мы начнем с создания нового экземпляра астероид по имени newAsteroid, предваряется localкак обычно. Сам объект представляет собой изображение так же , как и все остальное мы создали до сих пор, взяты из того же листа изображения ( objectSheet) ,который мы загрузили ранее.

local function createAsteroid()

    local newAsteroid = display.newImageRect( mainGroup, objectSheet, 1, 102, 85 )

end

Так как там будет много астероидов на экране в любой момент времени, нам нуженsbs-starexplorer-4 способ , чтобы следить за ними. Как вы помните из предыдущей главы, мы инициализируется несколько переменных, среди которых таблица с именами asteroidsTable. Эта таблица в настоящее время вступает в игру в качестве места для сохранения нового астероида.

Для того, чтобы вставить новый астероид экземпляр в таблицу, мы можем использовать встроенную в Lua table.insert()команду. Эта команда просто требует имя таблицы (asteroidsTable) и объект / значение для вставки, в этом случае newAsteroidобъект мы только что создали:

    table.insert( asteroidsTable, newAsteroid )

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

    physics.addBody( newAsteroid, "dynamic", { radius=40, bounce=0.8 } )

И, наконец, давайте назначим Астероид в myNameсобственность "asteroid". В дальнейшем, при обнаружении столкновений, это упростит нашу жизнь , чтобы знать , что этот объект является астероид:

    newAsteroid.myName = "asteroid"

размещение

Теперь, когда у нас есть новый астероид на экране, давайте установим свою точку происхождения. Реально, астероиды должны исходить от левой, правой или верхней части экрана - это не было бы справедливо, чтобы иметь астероид подкрасться сзади!

Учитывая эти три точки происхождения, мы можем сказать Lua , чтобы случайным образом выбрать целое число между 1и 3. Это легко сделать с помощью math.random()команды:

    local whereFrom = math.random( 3 )

После этой команды, локальная переменная whereFromбудет иметь значение либо 1, 2либо 3. Пользуясь этим, мы можем реализовать условное if-then структуру для обработки каждого из трех случаев. В Lua, эти структуры начинают в этой основной форме:

    if ( whereFrom == 1 ) then

    end
Заметки
  • Обратите внимание важное различие о языке Lua: когда вы присвоения значения переменной, вы используете единый знак равенства ( =). Однако, если вы делаете сравнение в условном операторе, вы должны использовать два знака равенства ( ==) , чтобы указать , что вы проверяете равенства вместо присвоения значения.
  • Сказать Lua , что вы закончили с условной структурой, используйте ключевое слово end.
  • Скобки вокруг сравнения не являются обязательными , но многие программисты используют их для ясности или для построения более сложных нескольких условий сравнения.

Давайте использовать это первое условие ( whereFrom == 1) , чтобы поместить астероид немного от левого края экрана. Вставьте три строки , чтобы ваш условный блок выглядит следующим образом :

    if ( whereFrom == 1 ) then
        -- From the left
        newAsteroid.x = -60
        newAsteroid.y = math.random( 500 )

    end

Так как этот астероид придет с левой стороны, мы устанавливаем его xсвойство -60. Это должно быть достаточное количество , чтобы гарантировать , что даже не часть астероида видна игроку , когда он впервые создал. Что касается yнедвижимости, мы используем math.random()еще раз , чтобы случайным образом выбрать значение между 1и500, фактически делая астероид появится где - то между верхней части области содержимого и примерно половину расстояния вниз - в конце концов, мы не хотим никаких астероиды , поступающих из место , которое делает невозможным их снимать!

движение

Теперь, когда мы имеем начальную точку, мы должны сказать астероид , где он должен переместиться. На этот раз мы будем использовать другую команду из физического движка:object:setLinearVelocity(). Эта команда аналогична объекту: applyLinearImpulse () команда , которую мы использовали в предыдущем проекте, но вместо того , чтобы применять внезапный "толчок" к объекту, он просто задает объект , движущийся в неуклонного направлении.

Добавьте эту строку непосредственно после предыдущей строке вы напечатали:

        newAsteroid:setLinearVelocity( math.random( 40,120 ), math.random( 20,60 ) )

Это может показаться сложным, но object:setLinearVelocity()просто требует два числа , указывающие скорость в х и у направлениях соответственно. Единственный поворот мы используем здесь math.random()для рандомизации значения таким образом, что каждый астероид движется в несколько ином направлении.

!

Обратите внимание на то, что мы называем math.random()с двумя параметрами на этот раз, в то время , прежде чем мы называли его только с одним. При вызове с одним параметром, команда случайным образомгенерирует целое число между 1и указанное вами значение. При вызове с двумя параметрами, команда случайным образом генерирует целое число между двумя заданными значениями, например , между 40и120в первую очередь выше.

Если мы решили позвонить / запустить эту функцию сейчас, мы можем увидеть астероид медленно движется по экрану с левой стороны, но , вероятно , нет. Зачем? Потому что мы не добавили условные случаи для двух других сторон экрана! Помните, что Lua случайным образом выбирать число между 1и 3, но в данный момент мы только обращение возникновения 1.

Следующие два условия могут закончить три возможных сторон , из которых астероиды могут возникать. При добавлении нескольких условий в том же if-then структуры, последовавших первое условие должно начинаться сelseif, не с if. Соблюдайте эти дополнения:

    if ( whereFrom == 1 ) then
        -- From the left
        newAsteroid.x = -60
        newAsteroid.y = math.random( 500 )
        newAsteroid:setLinearVelocity( math.random( 40,120 ), math.random( 20,60 ) )

    elseif ( whereFrom == 2 ) then
        -- From the top
        newAsteroid.x = math.random( display.contentWidth )
        newAsteroid.y = -60
        newAsteroid:setLinearVelocity( math.random( -40,40 ), math.random( 40,120 ) )

    elseif ( whereFrom == 3 ) then
        -- From the right
        newAsteroid.x = display.contentWidth + 60
        newAsteroid.y = math.random( 500 )
        newAsteroid:setLinearVelocity( math.random( -120,-40 ), math.random( 20,60 ) )
    end

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

вращение

Давайте завернуть createAsteroid()функцию с дополнительным подстройке для визуального интереса. Для того, чтобы астероиды медленно вращаться вокруг своей центральной точки , как они движутся через пространство, мы можем применить случайную величину крутящего момента. Непосредственно после if-then структура, на линии после того end, добавьте следующую команду:

    newAsteroid:applyTorque( math.random( -6,6 ) )

 

Обжиг Механика

Что такое стрельба игра без стрельбы?

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

Мы обсудим переходы больше немного. Во-первых, тем не менее, давайте напишем функцию, которая создаст новый лазерный объект:

local function fireLaser()
    local newLaser = display.newImageRect( mainGroup, objectSheet, 5, 14, 40 )
    physics.addBody( newLaser, "dynamic", { isSensor=true } )
    newLaser.isBullet = true

end

Большая часть этого кода должен быть простым: мы создаем новый объект лазер внутри mainGroupгруппы индикации, то мы добавим его в физический движок. На следующей строке мы указываем , что тело следует рассматривать как "пуля", установив его isBulletсвойство true. Это делает объект , подлежащий непрерывному обнаружения столкновений , а не периодического обнаружения столкновений на этапах мирового времени. Потому что наш лазер будет двигаться очень быстро по экрану, это поможет гарантировать , что он не "проходит через" любых астероидов без регистрации столкновения.

И, наконец, давайте назначьте лазер с myNameсобственностью , "laser"которая, подобно кораблю и астероидов, будет полезен при обнаружении столкновений:

    newLaser.myName = "laser"

размещение

Новый лазерный объект теперь загружается, но мы еще не установили его правильно. В этом случае мы не можем использовать статическое положение , потому что корабль будет в конечном итоге будет двигаться влево и вправо с помощью ДУ проигрывателя. К счастью, это очень легко позиционировать новый лазер на том же месте, что и корабль, установив xи yзначения корабельные xи yзначения:

    newLaser.x = ship.x
    newLaser.y = ship.y

Это позволsbs-starexplorer-7ит правильно позиционировать лазер по горизонтали и по вертикали,но есть еще один вопрос решить. Поскольку эта функция создает новые лазеры после того, как уже загружен корабль, и оба объекта являются частью mainGroupгруппы дисплея, лазеры будут появляться визуально выше (в передней части) судна с точки зрения отводками.Очевидно , что это выглядит глупо, так что давайте толкать его позади корабля с помощью следующей команды:

    newLaser:toBack()
sbs-starexplorer-6

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

движение

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

Добавьте следующую строку в fireLaser()функции:

    transition.to( newLaser, { y=-40, time=500 } )

Как вы можете видеть, первый параметр является объектом для перехода ( newLaser). Для второго параметра, мы включаем таблицу , которая может содержать различные свойства для перехода. Здесь мы установили ,y=-40который указывает вертикальный лазера назначения , слегка в сторону от верхнего края экрана. Мы также установить пользовательский timeпараметр 500. Для переходов, время (длительность) всегда должен быть указан в миллисекундах  - помните , что 1 секунда равна 1000 миллисекунд, так что этот переход будет происходить в течение длительности ½ секунды.

уборка

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

В Corona, существуют различные подходы к очистке и это будет зависеть от ситуации. Для лазеров, мы будем использовать очень удобный метод , известный как onCompleteобратный вызов. В качестве опции в пределахtransition.to()и несколько других команд, это говорит Corona , что вы хотите выполнить действие - вызвать функцию, установите переменную и т.д. - когда что - то "завершает" . Это идеально подходит для наших лазеров ,потому что мы просто хотим , чтобы удалить их , когда переход будет завершен.

Давайте расширять нашу transition.to()команду , чтобы включить функцию onCompleteобратного вызова:

    transition.to( newLaser, { y=-40, time=500,
        onComplete = function() display.remove( newLaser ) end
    } )

Проще говоря, это дополнение говорит Corona запустить функцию , когда переход завершается. Внутри функции, единственная команда , нам нужно , display.remove( newLaser )которая удаляет объект лазера со сцены. Lua ввстроенный сборщик мусора позаботится об остальном. Просто!

!

Те , с острым глазом заметить , что функция , заданная после того, как onComplete =не имеет названия. В Lua, это известно как анонимной функции . Они полезны для "временных" функций, для функций ,необходимых в качестве параметра другой функции, или когда вам нужно использовать функцию , прежде чем определить его. Хотя мы могли бы написать специальную функцию для удаления лазеров и вызывать его с помощью onCompleteобратного вызова, то проще использовать анонимные функции в этом случае.

Почти готово! Давайте присвоить кораблю "tap"слушатель событий , так что игрок может на самом деле огонь лазеров. Сразу после fireLaser()функции, после ее закрытия end, добавьте следующую команду:

ship:addEventListener( "tap", fireLaser )
Действие!

Давайте проверим результат нашего кода. Сохраните измененный main.luaфайл, sbs-starexplorer-8повторно запустить Simulator, и попытаться нажав / щелкнув на корабле , чтобы увидеть , как он стреляет лазерами. Теперь мы получаем где - то!

 

Перемещение корабль

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

local function dragShip( event )

    local ship = event.target
    local phase = event.phase

    if ( "began" == phase ) then
        -- Set touch focus on the ship
        display.currentStage:setFocus( ship )
        -- Store initial offset position
        ship.touchOffsetX = event.x - ship.x

    elseif ( "moved" == phase ) then
        -- Move the ship to the new touch position
        ship.x = event.x - ship.touchOffsetX

    elseif ( "ended" == phase or "cancelled" == phase ) then
        -- Release touch focus on the ship
        display.currentStage:setFocus( nil )
    end

    return true  -- Prevents touch propagation to underlying objects
end

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

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

Внутри функции, чтобы сделать вещи немного яснее, мы устанавливаем локальную переменную , shipравную event.target. В касание / TAP событий, event.targetявляется объектом , который был растроган / прослушиваются, поэтому установка этой локальной переменной в качестве ссылки на объект корабля сэкономит нам немного набрав , как мы работаем через функцию.

Сенсорные Фазы

В следующей строке, local phase = event.phaseлокально устанавливает фазу сенсорного события. Сенсорные события имеют четыре различных этапа на основе состояния прикосновения пользователя на экране: "began","moved", "ended", или "cancelled".

С фазы локально установлен, мы можем использовать if-then структуру , чтобы проверить , какая фаза фактического касания событие в случае , если он только начался, как и в первоначальном контакте на корабле, то."began"Фаза отправляется в нашу функцию. В этом условном случае, мы устанавливаем сенсорный фокус на корабле - по сути, это означает , что объект будет корабль "собственный" сенсорный событие на протяжении всей своей продолжительности. В то время как акцент делается на корабле, никакие другие объекты в игре не будут обнаруживать события этого конкретного прикосновения.

sbs-starexplorer-9

Непосредственно после этой линии, мы сохраняем начало "смещение" положение прикосновения по отношению к кораблю. Концептуально, прикосновение может происходить в различных местах в пределах границ объекта перечисляются , а не только в центре. Здесь, event.x - ship.xдает нам горизонтальное смещение между точной точки касания на экране ( event.x) и корабля х позиций ( ship.x) и мы устанавливаем это как свойство корабля ( touchOffsetX) для использования в следующей фазе.

Для "moved"фазы (вы можете догадаться) , мы перемещаем корабль! Это делается простой установкой судна х положение - но посмотрите внимательно: это где нашаtouchOffsetXсобственность входит в игру. Если мы проигнорировали это смещение и просто установить судна х позицию event.x, казалось бы пропустить / прыгать вокруг , потому что его центральное положение будет соответствовать точной точки касания на экране. Используя значение сдвига, тем не менее, обеспечивает более гладкую, последовательную перетягивания эффект.

Заметка

Если вы создаете игру , где объект может быть тащили по всему экрану, как по горизонтали , так и по вертикали, вы должны отразить это смещение концепции вертикальной оси , а также. Например, в "began"случае, хранить начало у смещения:

ship.touchOffsetY = event.y - ship.y

А в "moved"фазе, установите объекта у позицию следующим образом :

ship.y = event.y - ship.touchOffsetY

Окончательный условный случай включает в себя как "ended"и "cancelled"фазы. "ended"Фаза указывает на то, что пользователь отпустит контакт на объекте в то время как "cancelled"фаза указывает на то, что система сама отменена / прекращена сенсорного события. Как правило, обе эти фазы могут быть обработаны в том же условном блоке. Для этой игры, мы просто выпустить сенсорный фокус на корабле с этой командой:

        display.currentStage:setFocus( nil )
Важный

Обратите внимание на последнюю строку в пределах этой dragShip()функции:

    return true  -- Prevents touch propagation to underlying objects

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

Мы почти закончили с механикой движения - давайте присвоить кораблю "touch"слушатель событий , так что игрок может коснуться / перетащить его влево и вправо по экрану. Сразу после dragShip()функции, после ее закрытия end, добавьте следующую команду:

ship:addEventListener( "touch", dragShip )
Действие!

Давайте проверим результат нашего кода. Сохраните измененный main.luaфайл, повторно запустить Simulator, и экспериментировать с трогательным и перетащив корабль вокруг.

 

Игра Loop

Многие игры включают в себя некоторый тип игрового цикла для обработки обновления информации, проверки / обновления состояния игровых объектов и т.д. Несмотря на то, что вполне возможно построить игру в Corona никогда не реализует цикл игры, мы будем использовать один здесь иллюстрации концепции.

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

Добавьте следующую функцию в свой main.luaфайл следующий код ужедобавленный:

local function gameLoop()

    -- Create new asteroid
    createAsteroid()

    -- Remove asteroids which have drifted off screen
    for i = #asteroidsTable, 1, -1 do
        local thisAsteroid = asteroidsTable[i]

        if ( thisAsteroid.x < -100 or
             thisAsteroid.x > display.contentWidth + 100 or
             thisAsteroid.y < -100 or
             thisAsteroid.y > display.contentHeight + 100 )
        then
            display.remove( thisAsteroid )
            table.remove( asteroidsTable, i )
        end
    end
end

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

Астероид Cleanup

В следующем блоке кода, мы удалить астероиды , которые задремал экран, обернув через asteroidsTableстол. Помните , когда мы объявили эту таблицу в качестве места для хранения ссылки для каждого астероида? Это не могло бы показаться уместным в то время, но теперь он полностью идет в игру!

Переберите таблицы, мы используем Lua forцикл. По существу, forпетля позволяет нам использовать индекс переменную для подсчета вверх или вниз от исходного числа в конечный номер.

В этом случае, мы должны отсчитывать (декремент) , начиная с числа астероидов в asteroidsTableтаблице, но есть одна небольшая загвоздка: сумма будет постоянно меняться , как создаются новые астероиды и другие будут уничтожены самим игроком. К счастью, Lua имеет чрезвычайно удобный метод для подсчета количества элементов в таблице, совершенное просто предварив имя таблицы #:

#asteroidsTable

Мы можем использовать этот метод в нашем forцикле , который в основном имеет следующий вид - это говорит Lua использовать индекс i , начните #asteroidsTable, остановится1 , и подсчитывают-1 (декремент).

    for i = #asteroidsTable, 1, -1 do

    end

Внутри forцикла, мы включаем код , который должен быть обработан каждый раз при итерации цикла. Если есть десять астероиды в таблице, петля с итерацию в десять раз. Если есть только один астероид в таблице, он будет перебирать один раз.

Заметка

Для каждой итерации цикла, мы упростим наш код, объявив локальную ссылку на астероид , который петля , ссылающегося на этой конкретной итерации. Следующая команда выполняет это с другим удобным способом Lua, [i]после имени таблицы ( asteroidsTable):

        local thisAsteroid = asteroidsTable[i]

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

Следующий блок представляет собой несколько условное утверждение , которое проверяет положение астероида на который ссылается во время итерации цикла:

        if ( thisAsteroid.x < -100 or
             thisAsteroid.x > display.contentWidth + 100 or
             thisAsteroid.y < -100 or
             thisAsteroid.y > display.contentHeight + 100 )
        then
            display.remove( thisAsteroid )
            table.remove( asteroidsTable, i )
        end

С этим утверждением, мы проверяем , если астероид дрейфует по существу далеко от любого края экрана, проверяя ее xи yсвойства. Если какой - либо из четырех условий выполнены, мы выполняем два важных действия:

  1. Удалите астероид с экрана с помощью display.remove()команды.
  2. Удалить астероид из asteroidsTableтаблицы с встроенным в Lua table.remove()команде. Эта команда просто удаляет элемент из таблицы по указанному индексу, в этом случае петля индексi .

Таймер Loop

Очевидно, что игровой цикл не имеет смысла, если это на самом деле не работать. Давайте добавим эту функциональность в настоящее время.

Есть несколько различных способов запустить функцию игры петли несколько раз. Иногда вам нужно проверить / обновить вещи на каждом кадре во время выполнения этого приложения - если вы помните, что это60 раз в секунду , потому что мы устанавливаем fps = 60в config.luaфайле.

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

Сразу после gameLoop()функции, после его закрытия end, добавьте следующее:

gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )

Давайте проанализируем эту строку:

  1. Во- первых, мы утверждаем , что gameLoopTimerпеременная - заполнитель (объявленная ранее) будет связан с нашим таймером. Это позволяет нам использовать эту переменную в качестве ссылки / ручка для приостановки или отмены таймера позже, если это необходимо.
  2. Далее, мы называем timer.performWithDelay(). Эта удобная команда говорит Corona , чтобы выполнить какое - либо действие по истечении заданного количества миллисекунд. Таймеры могут быть использованы для широкого спектра функциональных возможностей игры, так что освоиться с ними сейчас!
  3. Внутри скобок, мы начинаем с числом миллисекунд ожидания (задержки) , пока таймер сработает. Здесь мы используем 500что ровно половина секунды, но вы можете экспериментировать с другими значениями.Меньшее число , как 250будет делать астероиды икру быстрее и увеличить сложность игры.
  4. Следующим параметром является функция , которая будет называться / запускается , когда таймер сработает. Очевидно , что мы указываем gameLoopздесь.
  5. И, наконец, мы включаем дополнительные итерации параметр со значением 0. Если этот параметр опущен, то таймеры будут просто стрелять один раз и остановится. Если включить этот параметр, то таймер будет повторяться для этого числа итераций, но не вводить в заблуждение 0в данном случае - мы не говоря таймера "запустить" ноль раз , а повторить навсегда. В основном, проходя 0или -1заставит таймер повторять добесконечности , если мы не сказать ему , чтобы сделать паузу / стоп.

Все сделано! Игровой цикл завершен и у нас есть таймер на месте, чтобы запустить его повторно и на неопределенный срок.

Действие!

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

 

Столкновение Погрузка

Время обработки столкновений! Первоначально мы только собираемся обнаружить специфические столкновения:

  1. Когда лазер сталкивается с астероидом.
  2. Когда астероид сталкивается с кораблем.
Заметка

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

  • Обработка Локальное столкновение лучше всего использовать в один-ко-многим сценарии столкновений, например , одного объекта игрока , который может столкнуться с несколькими врагами, бонусы и т.д.
  • Глобальная обработка столкновений лучше всего использовать в многие-ко-многим сценарии столкновений, например , нескольких символов героя , который может столкнуться с несколькими врагами.

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

Восстановление Ship

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

local function restoreShip()

    ship.isBodyActive = false
    ship:setLinearVelocity( 0, 0 )
    ship.x = display.contentCenterX
    ship.y = display.contentHeight - 100

    -- Fade in the ship
    transition.to( ship, { alpha=1, time=4000,
        onComplete = function()
            ship.isBodyActive = true
            died = false
        end
    } )
end

Давайте рассмотрим содержание этой функции:

  1. Первая команда ship.isBodyActive = false, эффективно удаляет корабль от моделирования физики , так что она перестает взаимодействовать с другими телами. Мы делаем это (временно) , так что, как корабль исчезает обратно в поле зрения, сталкиваясь астероиды не вызовет другой ответ столкновения.
  2. На следующей строке, мы сбрасываем линейную скорость судна к 0. Это отменяет любую скорость , что «мертвый» корабль от удара астероида.
  3. Следующие две строки просто изменить положение судна в нижнем центре экрана.
  4. Последняя команда является transition.to()вызов , который выцветает корабль обратно к полной непрозрачности ( alpha=1) за промежуток четырех секунд. Она также включает в себя onCompleteобратный вызов анонимной функции. Эта функция восстанавливает судно в качестве активного физического тела и сбрасывает diedпеременную false.

Столкновение Функция

Давайте напишем основу нашей функции столкновения:

local function onCollision( event )

    if ( event.phase == "began" ) then

        local obj1 = event.object1
        local obj2 = event.object2

    end
end

Это относительно просто, и вы должны признать некоторые основные понятия из ранее:

  1. Похожие на события прикосновения, столкновения имеют различные фазы , в данном случае "began"и "ended". Первый из них, "began"является на сегодняшний день наиболее распространенной фазе вам нужно обрабатывать, но бывают случаи , когда обнаружение "ended"фазы является чрезвычайно важным. Не беспокойтесь слишком много об этом сейчас - здесь, мы просто изолировать "began"фазу, окружив нашу функциональность в условном предложении.
  2. Подобно тому , как мы это делали в dragShip()функции, мы будем ссылаться на два объекта , участвующих в столкновении с локальными переменными obj1и obj2уменьшить количество нажатий клавиш. При обнаружении столкновений с глобальным методом, эти объекты ссылаются event.object1и event.object2.

Лазеры и астероиды

Давайте обрабатывать наши первые столкновения условие: лазеры и астероиды. Помните , как мы присваиваем myNameсвойство к каждому объекту , когда мы создаем его? Это свойство теперь становится критическим вкачестве средства для обнаружения которых два типа объекта сталкиваются. Здесь открытие условное предложение проверяет myNameсвойство как obj1и obj2. Если эти значения "laser"и "asteroid", мы знаем , какие два типа объекта столкнулись и мы можем приступить к обработке результатов.

        if ( ( obj1.myName == "laser" and obj2.myName == "asteroid" ) or
             ( obj1.myName == "asteroid" and obj2.myName == "laser" ) )
        then
            -- Remove both the laser and asteroid
            display.remove( obj1 )
            display.remove( obj2 )

            for i = #asteroidsTable, 1, -1 do
                if ( asteroidsTable[i] == obj1 or asteroidsTable[i] == obj2 ) then
                    table.remove( asteroidsTable, i )
                    break
                end
            end

            -- Increase score
            score = score + 100
            scoreText.text = "Score: " .. score
        end
!

При обнаружении столкновений с глобальным методом, нет никакого способа определить , что является "первой" и "второй" объект участвует в столкновении. Другими словами, obj1может быть лазер иobj2астероид, или они могут быть перевернуты вокруг. Таким образом, мы должны учитывать это путем создания мульти-условное положение , чтобы обнаружить обе возможности.

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

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

!

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

И, наконец, потому что мы хотим , чтобы вознаградить игрока за уничтожение астероида, мы увеличиваем scoreпеременную 100и обновить scoreTextтекстовый объект , чтобы отразить новое значение.

Астероиды и корабль

Теперь давайте обрабатывать второе условие столкновения: астероиды и корабль. Добавьте следующее условие к if-thenоператору , следующему за первой (обратите внимание , что мы используем elseif) :

        elseif ( ( obj1.myName == "ship" and obj2.myName == "asteroid" ) or
                 ( obj1.myName == "asteroid" and obj2.myName == "ship" ) )
        then
            if ( died == false ) then
                died = true

                -- Update lives
                lives = lives - 1
                livesText.text = "Lives: " .. lives

                if ( lives == 0 ) then
                    display.remove( ship )
                else
                    ship.alpha = 0
                    timer.performWithDelay( 1000, restoreShip )
                end
            end

Внутри этого условного пункта, мы начинаем с дополнительной if-thenпроверкой:

            if ( died == false ) then

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

Внутри этой статье, мы немедленно , died = trueпотому что игрок на самом деле умер на этот раз!

Вслед за этим, мы вычитаем жизнь из livesпеременной и обновить livesTextтекстовый объект , чтобы отразить новое значение.

И, наконец, мы включаем условный оператор , чтобы проверить , если игрок находится вне жизни. В первом пункте, если livesравно 0, мы просто удалить судно целиком. Это та точка , где вы могли бы показать "игра окончена" сообщение или выполнить другие действия, но сейчас мы оставим возможности открытыми.

Во втором пункте (игрок имеет по крайней мере один оставшийся срок службы), мы делаем корабль невидимым, установив его alphaсвойство 0. Это позволит связать в restoreShip()функции мы писали ранее , где, когда корабль замирает обратно в поле зрения, то transition.to()команда переходит корабля alphaназад к 1. Сразу после этого, мы на самом деле вызвать restoreShip()функцию после задержки в одну секунду - это дает небольшую задержку перед тем корабль начинает исчезать обратно в поле зрения.

Столкновение Слушатель

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

Сразу после onCollision()функции, после его закрытия end, добавьте следующее:

Runtime:addEventListener( "collision", onCollision )

Эта команда похожа на предыдущих слушателей событий , где мы добавили "tap"или "touch"тип слушателя к конкретному объекту. Здесь мы просто добавить "collision"тип слушателя к глобальному Runtimeобъекту.

Действие!

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

!

Только в случае , если вы пропустили что - то, полная программа доступна здесь . Этот проект является немного более сложным , чем предыдущий, так что это может быть полезно , чтобы загрузить исходный код ,чтобы сравнить с тем, что вы создали.

 

Глава Концепции

Мы рассмотрели довольно много понятий в этой главе. Вот краткий обзор:

Команда / Недвижимость Описание
table.insert () Вставляет заданное значение в таблице .
table.remove () Удаляет элемент из таблицы по указанному индексу.
math.random () Возвращает псевдослучайное число из последовательности с равномерным распределением.
Объект: setLinearVelocity () Устанавливает х и Y компоненты для линейной скорости у тел.
Объект: applyTorque () Область применения вращающей силы к физическому телу.
object.isBullet Boolean для следует ли тело рассматриваться как "пуля".
Объект: Тобэк () Перемещение целевого объекта к визуальной обратной стороне материнской группы.
transition.to () Анимирует (переходы) экранного объекта с использованием дополнительного ослабления алгоритма.
display.remove () Удаляет объект или группу , если не nil.
Объект: SetFocus () Задает конкретный объект отображения в качестве цели для всех будущих хитов событий ( "touch"и "tap") .
timer.performWithDelay () Вызывает указанную функцию задержки.
object.isBodyActive Устанавливает или получает текущее активное состояние физического тела.

Language