Chapter 5 — Converting the Game to Composer / Глава 5 – Преобразование игры в Composer

Chapter 5 — Converting the Game to Composer / Глава 5 - Преобразование игры в Composer

В предыдущей главе мы изучили основы управления сцены. В этой главе мы собираемся преобразовать исходный файл игры, в настоящее времяmain_original.lua , в нашейgame.luaсцене.

Структура Сцена

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

Композитор требует от нас думать немного по- другому. Доступность становится все более важным , и это означает , что вещи должны быть закодированы в более строгом порядке. Используя Composer должным образом требует , чтобы вы считаете жизненные функции сцены цикла - scene:create(), scene:show(), scene:hide(), и scene:destroy()- и запускать команды в зависимости от того , сцена на экране или по- прежнему собран запределами экрана. Например, ранее мы использовали команды , которые начали бы непрерывное создание астероидов и положить их в движение. В нашей модифицированной game.luaсцене, эти команды будут отложены до тех пор , пока сцена полностью на экране.

 

Удобный доступ код

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

Теперь откройте как main_original.luaи game.luaв отдельных окнах редактора / вкладок. Вы будете двигаться несколько блоков кода от main_original.luaдо game.lua, так что это будет удобно иметь оба файла открываются одновременно.

Настройка физики

Так как эта игра будет , очевидно , по- прежнему использовать физический движок, нет никаких причин , чтобы отложить эту установку до позже. Скопируйте команды настройки физики из main_original.luaи вставить их всцены доступных пространстве game.lua, сразу после инициализации сцены:

-- -----------------------------------------------------------------------------------
-- Code outside of the scene event functions below will only be executed ONCE unless
-- the scene is removed entirely (not recycled) via "composer.removeScene()"
-- -----------------------------------------------------------------------------------

local physics = require( "physics" )
physics.start()
physics.setGravity( 0, 0 )
Заметка

Вслед за этим, мы первоначально добавили math.randomseed () команду , чтобы установить «семя» для псевдослучайного генератора чисел. Тем не менее, вы можете вспомнить , что мы вновь заявили эту команду в нашем модифицированном main.luaфайле, поэтому нет никаких причин , чтобы скопировать его на game.lua.

изображение Sheet

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

-- Configure image sheet
local sheetOptions =
{
    frames =
    {
        {   -- 1) asteroid 1
            x = 0,
            y = 0,
            width = 102,
            height = 85
        },
        {   -- 2) asteroid 2
            x = 0,
            y = 85,
            width = 90,
            height = 83
        },
        {   -- 3) asteroid 3
            x = 0,
            y = 168,
            width = 100,
            height = 97
        },
        {   -- 4) ship
            x = 0,
            y = 265,
            width = 98,
            height = 79
        },
        {   -- 5) laser
            x = 98,
            y = 265,
            width = 14,
            height = 40
        },
    }
}
local objectSheet = graphics.newImageSheet( "gameObjects.png", sheetOptions )

Исходные переменные

После установки изображения листа, вставьте весь блок исходных переменных из main_original.lua:

-- Initialize variables
local lives = 3
local score = 0
local died = false

local asteroidsTable = {}

local ship
local gameLoopTimer
local livesText
local scoreText

Показать группы

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

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

Для облегчения этой задачи , мы будем отложить фактическое создание наших трех групп , пока мы не создадим сцену. Тем не менее, мы по- прежнему необходимо определить переменные теперь используют упреждающее объявление метода , который вы узнали в главе 2. Таким образом, вместо того , чтобы связать каждую переменную с display.newGroup () , просто оставить их неопределенными на данный момент:

local backGroup
local mainGroup
local uiGroup
примечания
  • В нашей оригинальной версии, мы создали фон, корабль, живет текст, и оценка текста сразу после инициализации групп отображения. В этом новом Composer с поддержкой версии, мы будем отложить эти действия до тех пор, позже внутри scene:create()функции.
  • Мы добавили display.setStatusBar( display.HiddenStatusBar )команду в наш модифицированный main.luaфайл , чтобы убедиться , что строка состояния остается скрытой по всему приложению, так что нет никаких причин , чтобы повторить его в game.lua.

Игровые функции

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

local function updateText()
    livesText.text = "Lives: " .. lives
    scoreText.text = "Score: " .. score
end


local function createAsteroid()

    local newAsteroid = display.newImageRect( mainGroup, objectSheet, 1, 102, 85 )
    table.insert( asteroidsTable, newAsteroid )
    physics.addBody( newAsteroid, "dynamic", { radius=40, bounce=0.8 } )
    newAsteroid.myName = "asteroid"

    local whereFrom = math.random( 3 )

    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

    newAsteroid:applyTorque( math.random( -6,6 ) )
end
Важный

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

ship:addEventListener( "tap", fireLaser )

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

Как скопировать и вставить в течение следующих нескольких функций, пропустить команды , которые следуют непосредственно за ними. В частности, опустить следующие строки при копировании над кодом отmain_original.luaдо game.lua:

  • ship:addEventListener( "tap", fireLaser )
  • ship:addEventListener( "touch", dragShip )
  • gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )
  • Runtime:addEventListener( "collision", onCollision )

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

local function fireLaser()

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

    newLaser.x = ship.x
    newLaser.y = ship.y
    newLaser:toBack()

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


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


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


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


local function onCollision( event )

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

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

        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

        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
        end
    end
end


-- -----------------------------------------------------------------------------------
-- Scene event functions
-- -----------------------------------------------------------------------------------

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

 

Создание сцены

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

Давайте начнем с физики. Внутри scene:create()функции, добавьте команду physics.pause()следующим образом :

function scene:create( event )

    local sceneGroup = self.view
    -- Code here runs when the scene is first created but has not yet appeared on screen

    physics.pause()  -- Temporarily pause the physics engine
end

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

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

    -- Set up display groups
    backGroup = display.newGroup()  -- Display group for the background image
    sceneGroup:insert( backGroup )  -- Insert into the scene's view group

    mainGroup = display.newGroup()  -- Display group for the ship, asteroids, lasers, etc.
    sceneGroup:insert( mainGroup )  -- Insert into the scene's view group

    uiGroup = display.newGroup()    -- Display group for UI objects like the score
    sceneGroup:insert( uiGroup )    -- Insert into the scene's view group

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

Как вы помните, большинство API для отображения объектов Corona принимают действительную переменную дисплея группу в качестве удобного встроенного ярлыка для вставки объекта в эту группу. Тем неменее, display.newGroup () является одним из исключений из этого ярлыка , и вы не можете просто поставить ссылку на группу рядный , чтобы вставить новую группу в эту существующую группу. Вместо этого вы должны использовать object:insert()команду. Это приводит к тому же результату, что и встроенного метода и могут быть взаимозаменяемыми с другими API для экранных объектов , если вы предпочитаете.

С группами в месте, давайте сначала создать фон:

    -- Load the background
    local background = display.newImageRect( backGroup, "background.png", 800, 1400 )
    background.x = display.contentCenterX
    background.y = display.contentCenterY

Если вы осмотрите сцену доступную область кода в верхней части game.lua, вы заметите , что мы не не включают прямой ссылки на backgroundпомощью local background. Это происходит потому , что, как только мы создаем и вставить фоновый объект в поле зрения сцены, мы никогда не должны получить доступ к нему в другом месте в коде. Таким образом, мы просто создать его в качестве localобъекта внутриscene:create()функции.

Теперь давайте создадим корабля и текстовых объектов:

    ship = display.newImageRect( mainGroup, objectSheet, 4, 98, 79 )
    ship.x = display.contentCenterX
    ship.y = display.contentHeight - 100
    physics.addBody( ship, { radius=30, isSensor=true } )
    ship.myName = "ship"

    -- Display lives and score
    livesText = display.newText( uiGroup, "Lives: " .. lives, 200, 80, native.systemFont, 36 )
    scoreText = display.newText( uiGroup, "Score: " .. score, 400, 80, native.systemFont, 36 )

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

Помните , как мы отсроченный добавление слушателей событий корабля в сцене доступной секции , потому что shipеще не существовало в качестве фактического объекта? Теперь, когда она действительно существует, давайте добавим ИТС "tap"и "touch"слушателей событий:

    ship:addEventListener( "tap", fireLaser )
    ship:addEventListener( "touch", dragShip )

Это оно! Наши первоначальные объекты сцены теперь будут созданы - хотя и за кадром - непосредственно перед Composer переходит к следующей функции в жизненном цикле сцены, scene:show().

 

Отображение сцены

В сцене меню, нам не нужно ничего делать с scene:show(), ни с его функцией компаньон scene:hide(), но в этой сцене игры мы делаем. Помните , что на данный момент мы еще не настроили обнаружение столкновений,вновь начал физический движок, или начал нашу игру петлю на нерест астероиды. К счастью, scene:show()можно использовать , чтобы положить все в движении!

Этапы и переходы

Важным отличием scene:create()и scene:show()является то , что композитор вызывает scene:show()функцию дважды . Первый вызов происходит , когда сцена готова показать и второй вызов происходит сразу же после того, как сцена показала. В то время как мы не использовали его для сцены меню, то composer.gotoScene () вызова позволяет определить сцены эффектов перехода , таких как выцветания, затенение , увеличения и уменьшения масштаба, и многое другое. Естественно, есть продолжительность времени , связанное с началом и концом таких переходов и это, когда два вызова вступают в игру - первый происходит до того , как начинается переход , а второе имеет место , когда переход заканчивается.

Конечно, мы должны знать , когда каждый из этих вызовов происходит. Это может быть сделано путем проверки event.phase, свойство , связанное с eventпараметром , передаваемым в scene:show(). Для первого вызова ,сделанного к scene:show(), event.phaseэто "will", по существу , это означает , что сцена "покажет" и эффект перехода может произойти. Для второго вызова , сделанного к scene:show(), event.phaseэто "did", имея в виду сцену "сделал шоу" и эффект переход завершен.

Шаблон сцены уже есть условный код на месте , чтобы проверить какой фазе scene:show()мы находимся, так что вы можете вставить код из main_original.luaфайла в соответствующий условный блок. Для этой игры, мы восновном хотят , чтобы начать игру работает - нерест астероиды, обнаружения столкновений и т.д. - когда сцена полностью на экране, то есть "did"фаза. Итак, в вашем game.luaфайле, в scene:show()функции, добавьте три команды:

function scene:show( event )

    local sceneGroup = self.view
    local phase = event.phase

    if ( phase == "will" ) then
        -- Code here runs when the scene is still off screen (but is about to come on screen)

    elseif ( phase == "did" ) then
        -- Code here runs when the scene is entirely on screen
        physics.start()
        Runtime:addEventListener( "collision", onCollision )
        gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )
    end
end

По существу, внутри elseif ( phase == "did" ) thenусловного блока, вы заметите , что мы:

  1. Повторно запустить двигатель физики с physics.start().
  2. Начать обнаружение столкновений.
  3. Начало цикла игры с нашим оригинальным timer.performWithDelay () команды.
Действие!

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

 

Скрытие сцены

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

Когда мы намерены покинуть игровую сцену, помните , что gameLoopTimerтаймер будет по- прежнему работает. Кроме того, физический движок по- прежнему будет двигаться астероиды о. И, наконец, астероиды будут продолжать появляться, но , очевидно , нам нужно , чтобы остановить это. Все эти вещи могут быть обработаны внутри scene:hide()функции.

Аналогично scene:show(), scene:hide()будет вызываться дважды, один раз , когда сцена собирается быть скрыты и снова после того, как сцена полностью выключен экран. На этот раз, в "will"и "did"фазовых условияхscene:hide(), мы будем "отменить" некоторые вещи, добавив три команды:

function scene:hide( event )

    local sceneGroup = self.view
    local phase = event.phase

    if ( phase == "will" ) then
        -- Code here runs when the scene is on screen (but is about to go off screen)
        timer.cancel( gameLoopTimer )

    elseif ( phase == "did" ) then
        -- Code here runs immediately after the scene goes entirely off screen
        Runtime:removeEventListener( "collision", onCollision )
        physics.pause()
    end
end

Эти команды , по существу обратное , что мы делали в scene:show()функции:

  1. Остановить цикл игры, отменяя таймер , связанный с gameLoopTimer.
  2. Остановить обнаружение столкновения путем удаления слушателя событий во время выполнения.
  3. Пауза физический движок с physics.pause().
Заметка

Обратите внимание , как мы намеренно используя обе фазы scene:hide(). Первая команда может произойти до того , как сцена начинает скрывать ( phase == "will") , так как нам не нужно генерировать новые астероиды , как только игра закончена. Для двух других команд, мы отложим их до тех пор , после того, как сцена полностью выключен экран , потому что мы не хотим , чтобы остальные астероиды внезапно прекращают движение , как начинается переход сцены.

Сцена Cleanup

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

В зависимости от вашей игры, очистки сцену , чтобы перезагрузить свежий может включать в себя какую - то работу. В этом случае мы должны были бы "отменить" некоторые вещи , которые мы делали в scene:create(), а также удалить ссылки на старые астероидов , содержащихся в asteroidsTableтаблице. Мы также должны были бы сбросить score, livesи видимость судна в пределах scene:show(). Ничего из этого не является исключительно сложной, но не было бы удобно , если бы там был простой способ сбросить все на сцене? К счастью, композитор имеет команду , чтобы сделать именно это:

composer.removeScene( "game" )

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

Хотя эта команда удобно, вы можете не удалять сцены , которые Вы находились в данный момент. Для нашей игры, это до , menu.luaчтобы выполнить эту задачу, так что давайте откроем этот файл и внести небольшие изменения к нему. Найти функцию gotoGame()и, непосредственно перед вызовом composer.gotoScene () , добавьте composer.removeScene( "game" )команду. Кроме того, добавить "crossFade"эффект перехода кcomposer.gotoScene () линии после него.

local function gotoGame()
    composer.removeScene( "game" )
    composer.gotoScene( "game", { time=800, effect="crossFade" } )
end

Хотя мы еще не написали его, highscores.luaсцена, скорее всего , меняться каждый раз, так что удаление его таким же образом , будет полезным. В то время как у нас есть menu.luaоткрытый, давайте изменимgotoHighScores()функцию сразу после gotoGame():

local function gotoHighScores()
    composer.removeScene( "highscores" )
    composer.gotoScene( "highscores", { time=800, effect="crossFade" } )
end
Действие!

Время , чтобы проверить наши изменения! Сохраните измененные menu.luaи game.luaфайлы , а затем повторно запустить Simulator. Теперь вы должны быть в состоянии играть в эту игру повторно и получить чистый сбрасывается каждый раз.

 

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

Вот краткое изложение понятий мы описанных в этой главе:

Команда / Недвижимость Описание
physics.pause () Приостановка физический движок.
объект: вставить () Вставляет объект в группу.
timer.cancel () Отменяет операцию таймера , инициированный с timer.performWithDelay () .
Объект: removeEventListener () Удаляет прослушиватель событий от объекта.
composer.removeScene () Удаляет конкретную Composer сцену.

Translate »