Статьи

Как взорвать вещи с физическим движком Corona SDK: Часть 2

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

В первой части этой серии статей мы познакомились с тем, как настроить динамическую среду с помощью простой в использовании библиотеки физики Corona. Среда включала в себя статические объекты, такие как пол, и программно сгенерированные динамические объекты, такие как ящики. Событие прикосновения пользователя будет генерировать взрывную силу, которая приведет к полету ящиков. Если вы еще не читали первую часть , я предлагаю вам сделать это, прежде чем продолжить. У читателя должно быть понимание основных физических понятий, описанных в части I, чтобы понять часть II.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local physics = require(«physics»)
 
physics.start()
 
physics.setScale( 40 )
 
display.setStatusBar( display.HiddenStatusBar )
 
— The final «true» parameter overrides Corona’s auto-scaling of large images
local background = display.newImage( «bricks.png», 0, 0, true )
background.x = display.contentWidth / 2
background.y = display.contentHeight / 2
 
local floor = display.newImage( «floor.png», 0, 280, true )
physics.addBody( floor, «static», { friction=0.5 } )
  
local crates = {}
 
for i = 1, 5 do
    for j = 1, 5 do
        crates[i] = display.newImage( «crate.png», 140 + (i*50), 220 — (j*50) )
        physics.addBody( crates[i], { density=0.2, friction=0.1, bounce=0.5 } )
    end
end

Все действия вышеупомянутого кода полностью объяснены в Части I учебного пособия, поэтому проверьте его, если что-то кажется непонятным.

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

1
2
3
4
5
6
7
local function setBomb ( event )
    if(event.phase == «began») then
        local bomb = display.newImage( «bomb.png», event.x,event.y )
        physics.addBody( bomb, { density=0.2, friction=0.1, bounce=0.5 } )
    end
end
background:addEventListener(«touch»,setBomb)

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

Метод setBomb в его нынешнем виде делает очень мало. Он загружает симпатичную графику бомбы и добавляет ее на экран как динамический физический объект. Теперь мы интегрируем наш метод взрыва обратно в код как локальная функция с именем blast:

01
02
03
04
05
06
07
08
09
10
11
12
local circle = «»
local explosion = «»
local function blast( event )
    circle = display.newCircle( bomb.x, bomb.y, 80 )
    explosion = display.newImage( «explosion.png», bomb.x, bomb.y )
    circle:setFillColor(0,0,0, 0)
    physics.addBody( circle, «static», {isSensor = true} )
    circle.myName = «circle»
    circle.collision = onLocalCollision
    circle:addEventListener( «collision», circle )
 end
blast()

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

Если вы попробуете код на этом этапе, вы заметите, что все происходит очень быстро и некоторые артефактные изображения остаются позади. Чтобы сделать его более напряженным, мы заменим вызов функции «blast ()» таймером, который задержит взрыв бомбы на 3 секунды:

1
timer.performWithDelay(3000, blast )

Просто как тот! Класс таймера имеет функцию executeWithDelay () с двумя параметрами: количество миллисекунд ожидания и метод вызова. Таким образом, в этом случае 3000 миллисекунд равны 3 целым секундам задержки.

Поскольку после 3-секундной задержки бомба взорвется, нам нужно убрать этот объект с экрана, как только произойдет взрыв. Это можно сделать очень легко. Все объекты, присутствующие на экране, снабжены удобной функцией removeSelf (). Как только объект удаляется с экрана, физический движок становится достаточно умным, чтобы собирать мусор и удалять его из всех физических расчетов. Мы можем добавить следующую строку в конец функции взрыва:

1
bomb:removeSelf()

В дополнение к удалению бомбы мы собираемся удалить наш объект с радиусом взрыва окружности, а также график взрыва, который мы добавили для эффекта. Поскольку нам нужно дать нашему кругу радиуса взрыва немного времени для создания столкновений с нашими ящиками, мы собираемся удалить его на 1/10 секунды после вызова функции взрыва. Это может быть достигнуто путем замены нашего вызова функции timest blast следующим кодом:

1
2
3
4
5
6
local function removeStuff( event )
    circle:removeSelf()
    explosion:removeSelf()
 end
 timer.performWithDelay(3000, blast )
 timer.performWithDelay(3100, removeStuff)

Как вы можете видеть, мы вызываем функцию взрыва после 3-секундной задержки от прикосновения к экрану, как мы это делали раньше. Теперь мы добавили еще один отложенный вызов, который выполняется через 3,1 секунды после прикосновения к экрану, который очищает наши остаточные объекты с помощью функции removeSelf ().

Вся созданная нами функция теперь выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
local function setBomb ( event )
    if(event.phase == «began») then
        local bomb = display.newImage( «bomb.png», event.x,event.y )
        physics.addBody( bomb, { density=0.2, friction=0.1, bounce=0.5 } )
         
        local circle = «»
        local explosion = «»
        local function blast( event )
            media.playEventSound( explosionSound )
            circle = display.newCircle( bomb.x, bomb.y, 80 )
            explosion = display.newImage( «explosion.png», bomb.x, bomb.y )
            bomb:removeSelf()
            circle:setFillColor(0,0,0, 0)
            physics.addBody( circle, «static», {isSensor = true} )
            circle.myName = «circle»
            circle.collision = onLocalCollision
            circle:addEventListener( «collision», circle )
         end
 
         local function removeStuff( event )
            circle:removeSelf()
            explosion:removeSelf()
         end
         timer.performWithDelay(3000, blast )
         timer.performWithDelay(3100, removeStuff)
    end
end
background:addEventListener(«touch»,setBomb)

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

01
02
03
04
05
06
07
08
09
10
11
12
local function onLocalCollision( self, event )
        if ( event.phase == «began» and self.myName == «circle» ) then
            local forcex = event.other.x-self.x
            local forcey = event.other.y-self.y
            if(forcex < 0) then
                forcex = 0-(80 + forcex)-12
            else
                forcex = 80 — forcex+12
            end
            event.other:applyForce( forcex, forcey, self.x, self.y )
        end
end

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

1
2
3
4
5
6
7
8
if(math.abs(forcex) > 60 or math.abs(forcey) > 60) then
    local explosion = display.newImage( «explosion.png», event.other.x, event.other.y )
    event.other:removeSelf()
    local function removeExplosion( event )
        explosion:removeSelf()
    end
    timer.performWithDelay( 100, removeExplosion)
end

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
local function onLocalCollision( self, event )
        if ( event.phase == «began» and self.myName == «circle» ) then
            local forcex = event.other.x-self.x
            local forcey = event.other.y-self.y
            if(forcex < 0) then
                forcex = 0-(80 + forcex)-12
            else
                forcex = 80 — forcex+12
            end
            event.other:applyForce( forcex, forcey, self.x, self.y )
            if(math.abs(forcex) > 60 or math.abs(forcey) > 60) then
                local explosion = display.newImage( «explosion.png», event.other.x, event.other.y )
                event.other:removeSelf()
                local function removeExplosion( event )
                    explosion:removeSelf()
                end
 
                timer.performWithDelay( 50, removeExplosion)
            end
 
        end
end

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

1
2
local media = require(«media»)
local explosionSound = media.newEventSound( «explosion.mp3» )

Для воспроизведения звука мы добавим следующую строку в нашу функцию blast (), которая находится внутри нашей функции setBomb ():

1
2
3
4
local function blast( event )
    media.playEventSound( explosionSound )
    …
end

Теперь каждый раз, когда вызывается функция blast (), она будет использовать функцию playEventSound из библиотеки мультимедиа для воспроизведения нашего звукового файла «explo.mp3». Это не может быть проще, если мы попробуем!

И там у нас это есть! Теперь у нас есть более полный пример того, как легко создавать взрывы на платформе Corona. Не стесняйтесь загружать почтовые индексы для части I и II учебника и поиграть!