[Lua][Love Engine] 打磚塊游戲實現過程與知識點

来源:https://www.cnblogs.com/linxiaoxu/archive/2023/08/21/17645027.html
-Advertisement-
Play Games

本文旨在根據LOVE2D官方文檔和教程實現打磚塊的游戲,記錄部分實現過程和重要知識點 - 目標摧毀所有磚塊 - 玩家控制球拍左右滑動反彈小球 - 小球摧毀磚塊 - 小球保持在屏幕內 - 小球碰到屏幕底部,GAME OVER ## 引擎配置 ```lua -- conf.lua love.conf = ...


本文旨在根據LOVE2D官方文檔和教程實現打磚塊的游戲,記錄部分實現過程和重要知識點

  • 目標摧毀所有磚塊
  • 玩家控制球拍左右滑動反彈小球
  • 小球摧毀磚塊
  • 小球保持在屏幕內
  • 小球碰到屏幕底部,GAME OVER
image-20230821054659181

引擎配置

-- conf.lua
love.conf = function(t)
    t.console = true
    t.window.width = 800
    t.window.height = 600
end

在載入引擎的時候回調該函數修改引擎基本參數,預設參數可看Config Files - LOVE (love2d.org)

物理世界

-- world.lua
local begin_contact_callback = function(fixture_a, fixture_b, contact)
end

local end_contact_callback = function(fixture_a, fixture_b, contact)
end

local pre_solve_callback = function(fixture_a, fixture_b, contact)
end

local post_solve_callback = function(fixture_a, fixture_b, contact)
end

local world = love.physics.newWorld(0, 0)

world:setCallbacks(begin_contact_callback, end_contact_callback, pre_solve_callback, post_solve_callback)

return world

建立了一個無重力的物理世界,為世界的物理碰撞綁定了四個回調函數,這四個回調函數依次的作用是

  • 兩個物理實體開始接觸
  • 接觸未解除,每幀碰撞處理計算前
  • 接觸未解除,每幀碰撞處理計算後
  • 兩個物理實體結束接觸

單一職責

按單一職責角度,main.lua 只負責創建游戲運行所必須的回調函數,而以下行為不屬於這個職責範圍

  • 載入和保存所有實體
  • 解決各類實體的繪製
  • 保存按鍵映射

舉個例子:main.lualove.draw的回調函數的函數體負責瞭解釋各類實體如何繪製的操作,如

love.draw = function()    
   local ball_x, ball_y = ball.body:getWorldCenter()
   love.graphics.circle('fill', ball_x, ball_y, ball.shape:getRadius())
end

可以通過修改實體結構,讓每個實體有對自身繪製行為的描述,將love.draw的一部分職責分擔到各個實體上。如下述代碼為ball實體添加了draw方法實現對自身繪圖行為的描述。

-- entities/ball.lua
local world = require 'world'

local entity = {}
-- 設置body位置,形狀將以該位置為中心,設置動態還是靜態,即會不會受到其他物理實體的影響
entity.body = love.physics.newBody(world, 200, 200, 'dynamic')
entity.body:setMass(32) -- 設置質量kg
entity.body:setLinearVelocity(300, 300) -- 右下角勻速加速度
entity.shape = love.physics.newCircleShape(10) -- 創建一個圓形形狀
entity.fixture = love.physics.newFixture(entity.body, entity.shape) -- 將body和形狀進行綁定
entity.fixture:setRestitution(1) -- 設置彈性繫數
entity.fixture:setUserData(entity) -- 設置用戶數據,用於在碰撞回調時獲取用戶自定義信息來判斷操作

-- 實體對自身繪圖行為的描述
function entity:draw()
    pos_x, pos_y = self.body:getWorldCenter() -- 獲取body的位置(也是圓心的位置)
    love.graphics.circle('fill', pos_x, pos_y, self.shape:getRadius()) -- 繪製這個圓,還要從形狀那獲取半徑
end

return entity

實體列表

考慮到實體創建和實體管理較難維護,可以用一個實體列表來進行統一管理。

修改示例 ball.lua

可以把每個實體腳本包裹進一個函數中,給定位置參數生成並返回這個實體,上述代碼將修改為

-- entities/ball.lua
local world = require 'world'

-- 導出一個函數,這個函數需要x,y位置參數,返回一個對象
return function(x, y)
    local entity = {}
    -- 設置body位置,形狀將以該位置為中心,設置動態還是靜態,即會不會受到物理系統的影響
    entity.body = love.physics.newBody(world, x, y, 'dynamic')
    entity.body:setMass(32) -- 設置質量kg
    entity.body:setLinearVelocity(300, 300) -- 右下角勻速加速度
    entity.shape = love.physics.newCircleShape(10) -- 創建一個圓形形狀
    entity.fixture = love.physics.newFixture(entity.body, entity.shape) -- 將body和形狀進行綁定
    entity.fixture:setRestitution(1) -- 設置彈性繫數
    entity.fixture:setUserData(entity) -- 設置用戶數據,用於在碰撞回調時獲取用戶自定義信息來判斷操作

    -- 實體對自身繪圖行為的描述
    function entity:draw()
        pos_x, pos_y = self.body:getWorldCenter() -- 獲取body的位置(也是圓心的位置)
        love.graphics.circle('fill', pos_x, pos_y, self.shape:getRadius()) -- 繪製這個圓,還要從形狀那獲取半徑
    end

    return entity -- 返回對象
end

其他修改示例

再修改其他實體代碼統一成上述形式

boundary-bottom.lua

-- entities/boundary-bottom.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(800, 10)
    -- 形狀將以body的位置為中心
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(eneity)
    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end
    return entity
end

boundary-vertical.lua

-- entities/boundary-vertical.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(10, 600)
    -- 形狀將以body的位置為中心
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(eneity)
    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end
    return entity
end

boundary-top.lua

-- entities/boundary-top.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(800, 10)
    -- 形狀將以body的位置為中心
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(eneity)
    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end
    return entity
end

brick.lua

-- entities/boundary-top.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(50, 20)
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)
    function entity:draw()
        love.graphics.polygon('fill', self.body:getWorldPoints(self.shape:getPoints()))
    end
    return entity
end

paddle.lua

-- entities/boundary-paddle.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(180, 20) -- 生成一個長方體多邊形
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)

    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end
    return entity
end

創建實體列表

-- main.lua
local boundary_bottom = require('entities/boundary-bottom')
local boundary_vertical = require('entities/boundary-vertical')
local boundary_top = require('entities/boundary-top')
local paddle = require 'entities.paddle'
local ball = require 'entities.ball'
local brick = require 'entities.brick'
local world = require 'world'

local entities = {boundary_bottom(400, 606), boundary_vertical(-6, 300), boundary_vertical(806, 300), boundary_top(400, -6),
        paddle(300, 500), ball(200, 200), brick(100, 100), brick(200, 100), brick(300, 100)}

直接調用實體創建函數創建各個實體,並放在同一個實體列表內,再修改love.draw遍歷這個實體列表調用各個實體的draw行為,代碼量大大減少

love.draw = function()
    for _, entity in ipairs(entities) do
        entity:draw()
    end
end

代碼拆分 entities.lua

此時創建實體列表的相關代碼還在main.lua內,將它獨立成entities.lua

-- entities.lua
local boundary_bottom = require 'entities/boundary-bottom'
local boundary_vertical = require 'entities/boundary-vertical'
local boundary_top = require 'entities/boundary-top'
local paddle = require 'entities.paddle'
local ball = require 'entities.ball'
local brick = require 'entities.brick'

return {boundary_bottom(400, 606), boundary_vertical(-6, 300), boundary_vertical(806, 300), boundary_top(400, -6),
        paddle(300, 500), ball(200, 200), brick(100, 100), brick(200, 100), brick(300, 100)}

main.lua 只需導入 entities.lua 並使用即可

-- main.lua
local world = require 'world'
local entities = require 'entities'

love.draw = function()
    for _, entity in ipairs(entities) do
        entity:draw()
    end
end

love.update = function(dt)
     world:update(dt)
end

輸入處理

輸入系統

專門新建一個文件input.lua用於輸入處理

-- input.lua
-- 存放輸入系統一切變數和操作的表
local input = {}

-- 各個按鍵對應回調函數的映射
local press_functions = {}
local release_functions = {}

-- 初始值
input.left = false
input.right = false
input.paused = false

-- 根據key觸發對應的映射函數
input.press = function(key)
    if press_functions[key] then
        press_functions[key]()
    end
end

input.release = function(key)
    if release_functions[key] then
        release_functions[key]()
    end
end

-- 如果離開當前程式視窗則暫停
input.focused = function(f)
    if not focused then
        input.paused = true
    end
end

press_functions.left = function()
    input.left = true
end
press_functions.right = function()
    input.right = true
end
press_functions.escape = function()
    love.event.quit()
end
press_functions.space = function()
    input.paused = not input.paused
end

release_functions.left = function()
    input.left = false
end
release_functions.right = function()
    input.right = false
end

return input

然後在main.lua導入input.lua

-- main.lua
local world = require 'world'
local entities = require 'entities'
local input = require 'input'

love.draw = function()
    for _, entity in ipairs(entities) do
        entity:draw()
    end
end

love.update = function(dt)
    if not paused then
        world:update(dt)
    end
end

love.focus = input.focused

love.keypressed = input.press

love.keyreleased = input.release

更新實體位置

監測輸入後需要根據輸入系統的變數實時更新實體位置,修改love.update,查詢各個實體的update方法,若有則執行

love.update = function(dt)
    if not input.paused then
        for _, entity in ipairs(entities) do
            if entity.update then
                entity:update(dt)
            end
        end
        world:update(dt)
    end
end

修改paddle.lua的代碼

-- entities/boundary-paddle.lua
local world = require 'world'
local input = require 'input'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(180, 20) -- 生成一個長方體多邊形
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)

    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end

    function entity:update(dt)
        -- 兩個按鍵都按了或都不按是不會動的
        if input.left and input.right or not input.left and not input.right then
            return
        end
        local self_x, self_y = self.body:getPosition()
        if input.left then
            -- 用時間增量去計算位置
            self.body:setPosition(self_x - 250 * dt, self_y)
        elseif input.right then
            self.body:setPosition(self_x + 250 * dt, self_y)
        end
    end
    return entity
end

由於paddle在創建的時候是靜態實體,是不受其他物理實體影響的,兩邊空氣牆真如同空氣,需要手動代碼限制,修改如下

        if input.left then
            -- 用時間增量去計算位置
            local new_x = math.max(self_x - 400 * dt, 90)
            self.body:setPosition(new_x, self_y)
        elseif input.right then
            local new_x = math.min(self_x + 400 * dt, 710)
            self.body:setPosition(new_x, self_y)
        end

去除摩擦力

雖然小球實體的彈力繫數設置為1,但是在碰撞過程中會越來越慢,這是預設有摩擦力的問題

print(entity.fixture:getFriction())
-- 0.20000000298023

摩擦力是 fixture 的屬性,可以使用 fixture:setFriction 進行設置,修改ball.lua,在創建實體的過程中添加如下代碼

    entity.fixture:setFriction(0) -- 小球受到的摩擦力為0

暫停顯示文字

把文字當做一個實體 pause-text.lua ,當暫停時繪製這個實體

-- entities/pause-text.lua
local input = require('input')

return function()
    -- https://love2d.org/wiki/love.window.getMode
    local window_width, window_height = love.window.getMode()

    local entity = {}

    entity.draw = function(self)
        if input.paused then
            -- https://love2d.org/wiki/love.graphics.print 用到了coloredtext表
            love.graphics.print({{0.2, 1, 0.2, 1}, 'PAUSED'}, math.floor(window_width / 2) - 54,
                math.floor(window_height / 2), 0, 2, 2)
        end
    end

    return entity
end

entities.lua創建這個實體並添加至列表中。只有當游戲暫停時會執行列印函數

三大剛體

static

靜態剛體,零質量,零速度,即不會受到重力或速度影響,但是可以設置他的位置來進行移動

  • 物理引擎並不認為這種剛體在移動
  • 適用於固定位置的對象,地面、牆壁、任何你不希望角色碰撞的游戲對象

dynamic

動態剛體,有質量,可以設置速度,會受到重力影響

  • 與三種剛體都有物理效果

kinematic

運動剛體,零質量,可以設置速度,不會受到重力的影響,但是可以設置速度來進行移動

  • 這種運動剛體完全由腳本控制

球拍改為運動剛體

將body類型修改為kinematic,刪除原先的重新設置位置方式,修改為修改速度來達到移動效果

-- entities/boundary-paddle.lua
local world = require 'world'
local input = require 'input'

return function(x, y)
    local window_width = love.window.getMode()
    local entity_width = 120
    local entity_height = 20
    local entity_speed = 600
    -- 計算一次邊界
    local left_boundary = entity_width / 2 + 2
    local right_boundary = window_width - entity_width / 2 - 2

    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'kinematic')
    entity.shape = love.physics.newRectangleShape(entity_width, entity_height)
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)

    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end

    function entity:update(dt)
        -- 兩個按鍵都按是不會動的
        if input.left and input.right then
            return
        end
        local self_x = self.body:getPosition()
        if input.left and self_x > left_boundary then
            self.body:setLinearVelocity(-entity_speed, 0)
        elseif input.right and self_x < right_boundary then
            self.body:setLinearVelocity(entity_speed, 0)
        else
            self.body:setLinearVelocity(0, 0)
        end
    end
    return entity
end

防止小球速度過快

-- entities/ball.lua
local world = require 'world'

-- 導出一個函數,這個函數需要x,y位置參數,返回一個對象
return function(x, y)
    local entity_max_speed = 880

    local entity = {}
    -- 設置body位置,形狀將以該位置為中心,設置動態還是靜態,即會不會受到其他物理實體的影響
    entity.body = love.physics.newBody(world, x, y, 'dynamic')
    entity.body:setMass(32) -- 設置質量kg
    entity.body:setLinearVelocity(300, 300) -- 右下角勻速加速度
    entity.shape = love.physics.newCircleShape(10) -- 創建一個圓形形狀
    entity.fixture = love.physics.newFixture(entity.body, entity.shape) -- 將body和形狀進行綁定
    entity.fixture:setRestitution(1) -- 設置彈性繫數
    entity.fixture:setUserData(entity) -- 設置用戶數據,用於在碰撞回調時獲取用戶自定義信息來判斷操作
    entity.fixture:setFriction(0) -- 小球受到的摩擦力為0

    -- 實體對自身繪圖行為的描述
    function entity:draw()
        pos_x, pos_y = self.body:getWorldCenter() -- 獲取body的位置(也是圓心的位置)
        love.graphics.circle('fill', pos_x, pos_y, self.shape:getRadius()) -- 繪製這個圓,還要從形狀那獲取半徑
    end

    function entity:update()
        v_x, v_y = self.body:getLinearVelocity()
        local speed = math.abs(v_x) + math.abs(v_y)
        print(speed)

        -- 看看小球反彈之後的速度是否過快
        local vel_x_is_critical = math.abs(v_x) > entity_max_speed * 2
        local vel_y_is_critical = math.abs(v_y) > entity_max_speed * 2
        -- 如果反彈後某一方向移動速度過快則減慢速度
        if vel_x_is_critical or vel_y_is_critical then
            self.body:setLinearVelocity(v_x * .75, v_y * .75)
        end
        -- 如果整體速度過大,則設置阻尼
        if speed > entity_max_speed then
            self.body:setLinearDamping(0.1)
        else
            self.body:setLinearDamping(0)
        end
    end

    return entity -- 返回對象
end

銷毀磚塊

需要做以下四件事情

  • 修改world.lua處理碰撞的函數
  • brick.lua實體添加碰撞後的處理函數
  • brick.lua實體添加一個屬性用於表示生命值,如entity.health
  • main.lua 檢查並刪除生命值為0的實體

修改兩個物體實體離開接觸時的函數 end_contact_callback,檢查兩個物理實體各自是否有end_contact方法,如果有則執行

local end_contact_callback = function(fixture_a, fixture_b, contact)
    local entity_a = fixture_a:getUserData()
    local entity_b = fixture_b:getUserData()
    if entity_a.end_contact then
        entity_a.end_contact()
    end
    if entity_b.end_contact then
        entity_b.end_contact()
    end
end

修改brick.lua 添加生命值與end_contact方法,並修改draw行為,使其能在不同生命的時候顯示不同顏色

-- entities/boundary-top.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(50, 20)
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)

    entity.health = 2

    local step = 1 / entity.health
    function entity:draw()
        local r, g, b, a = love.graphics.getColor()
        love.graphics.setColor({1 - step * entity.health, step * entity.health, 0, 1})
        love.graphics.polygon('fill', self.body:getWorldPoints(self.shape:getPoints()))
        love.graphics.setColor(r, g, b, a)
    end

    function entity:end_contact()
        self.health = self.health - 1
    end

    return entity
end

然後修改 main.lua 添加對各個實體生命值的檢查

love.update = function(dt)
    if not input.paused then
        -- 運行實體的update
        for i, entity in ipairs(entities) do
            if entity.update then
                entity:update(dt)
            end
        end
        -- 檢測實體health
        local index = 1
        while index <= #entities do
            if entities[index].health == 0 then
                local entity = table.remove(entities, index)
                entity.fixture:destroy()
            else
                index = index + 1
            end
        end
        world:update(dt)
    end
end

批量生成磚塊

每生成一個磚塊實體,需要調用一次brick,可以用函數批量生成,修改entities.lua

-- entities.lua
local boundary_bottom = require 'entities/boundary-bottom'
local boundary_vertical = require 'entities/boundary-vertical'
local boundary_top = require 'entities/boundary-top'
local paddle = require 'entities.paddle'
local ball = require 'entities.ball'
local brick = require 'entities.brick'
local pause_text = require 'entities.pause-text'

local entities = {boundary_bottom(400, 606), boundary_vertical(-6, 300), boundary_vertical(806, 300),
                  boundary_top(400, -6), paddle(300, 500), ball(200, 200), pause_text()}

local row_width = love.window.getMode() - 20
for number = 0, 38 do
    local brick_x = ((number * 60) % row_width) + 40
    local brick_y = (math.floor((number * 60) / row_width) * 40) + 80
    entities[#entities + 1] = brick(brick_x, brick_y)
end

return entities

狀態管理

是否暫停、游戲勝利或失敗這種在程式生命期間會動態變化的變數都稱為狀態。由於狀態會越來越多,需要一種方式進行有效管理。需要做到以下幾點

  • 很容易就能找到和訪問狀態。就像在main.lua導入了entities實體列表一樣,輕鬆可得
  • 狀態數據只能有一份。如只有一個paused變數,而不是每個文件里都有paused
  • 狀態數據只有有明確需求時才能訪問。在ball.lua獲取paused是無意義的

現在有哪些狀態

  • entities.lua
    • 導出的實體列表裡的每個實體對各自的狀態負責,如每塊磚都存儲著自己的健康情況
  • input.lua
    • 當前暫停狀態,左右按鍵的狀態
  • world.lua
    • 導出的world負責整個物理空間的狀態

實現狀態管理,新建一個state.lua文件,用於存儲游戲的大部分狀態,比如將input.lua中的狀態遷移到此文件,使input.lua專心於捕獲和映射用戶輸入。world.luaentities.lua沒有遷移的必要,避免代碼過於臃腫

-- state.lua
return {
    left = false,
    right = false,
    game_over = false,
    palette = {{1.0, 0.0, 0.0, 1.0}, -- red
    {0.0, 1.0, 0.0, 1.0}, -- green
    {0.4, 0.4, 1.0, 1.0}, -- blue
    {0.9, 1.0, 0.2, 1.0}, -- yellow
    {1.0, 1.0, 1.0, 1.0} -- white
    },
    paused = false,
    stage_cleared = false
}

再修改 input.lua

-- input.lua
local state = require 'state'

-- 各個按鍵對應回調函數的映射
local press_functions = {}
local release_functions = {}

press_functions.left = function()
    state.left = true
end
press_functions.right = function()
    state.right = true
end
press_functions.escape = function()
    love.event.quit()
end
press_functions.space = function()
    state.paused = not state.paused
end

release_functions.left = function()
    state.left = false
end
release_functions.right = function()
    state.right = false
end

return {
    press = function(key)
        print(key)
        if press_functions[key] then
            press_functions[key]()
        end
    end,
    release = function(key)
        if release_functions[key] then
            release_functions[key]()
        end
    end,
    focused = function(f)
        if not f then
            state.paused = true
        end
    end
}

修改main.lua

love.update = function(dt)
    if state.game_over or state.paused or state.stage_cleared then
        return
    end
    -- 運行實體的update
    for i, entity in ipairs(entities) do
        if entity.update then
            entity:update(dt)
        end
    end
    -- 檢測實體health
    local index = 1
    while index <= #entities do
        if entities[index].health == 0 then
            local entity = table.remove(entities, index)
            entity.fixture:destroy()
        else
            index = index + 1
        end
    end
    world:update(dt)
end

修改pause-text.lua

-- entities/pause-text.lua
local input = require 'input'
local state = require 'state'

return function()
    -- https://love2d.org/wiki/love.window.getMode
    local window_width, window_height = love.window.getMode()

    local entity = {}

    entity.draw = function(self)
        if state.paused then
            -- https://love2d.org/wiki/love.graphics.print 用到了coloredtext表
            love.graphics.print({{0.2, 1, 0.2, 1}, 'PAUSED'}, math.floor(window_width / 2) - 54,
                math.floor(window_height / 2), 0, 2, 2)
        end
    end

    return entity
end

使用調色板為磚塊上色

        love.graphics.setColor(state.palette[entity.health + 1])
        love.graphics.polygon('fill', self.body:getWorldPoints(self.shape:getPoints()))
        love.graphics.setColor(state.palette[5])

勝利與失敗

複製pause-text.luagame-over-text.lua,修改判斷條件為 state.game_over

-- entities/game-over-text.lua
local input = require 'input'
local state = require 'state'

return function()
    -- https://love2d.org/wiki/love.window.getMode
    local window_width, window_height = love.window.getMode()

    local entity = {}

    entity.draw = function(self)
        if state.game_over then
            -- https://love2d.org/wiki/love.graphics.print 用到了coloredtext表
            love.graphics.print({{0.2, 1, 0.2, 1}, 'GAME OVER'}, math.floor(window_width / 2) - 54,
                math.floor(window_height / 2), 0, 2, 2)
        end
    end

    return entity
end

複製pause-text.luastage-clear-text.lua,修改判斷條件為 state.stage_cleared

-- entities/boundary-bottom.lua
local world = require 'world'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(800, 10)
    -- 形狀將以body的位置為中心
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)

    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end
    return entity
end

將新增的兩個實體添加至實體列表

修改boundary-bottom.lua,添加end_contact事件處理函數修改全局狀態“是否失敗”

-- entities/boundary-bottom.lua
local world = require 'world'
local state = require 'state'

return function(x, y)
    local entity = {}
    entity.body = love.physics.newBody(world, x, y, 'static')
    entity.shape = love.physics.newRectangleShape(800, 10)
    -- 形狀將以body的位置為中心
    entity.fixture = love.physics.newFixture(entity.body, entity.shape)
    entity.fixture:setUserData(entity)

    function entity:draw()
        love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
    end

    function entity:end_contact()
        state.game_over = true
    end
    return entity
end

修改brick.lua 添加 entity.type = "brick",然後修改main.lua判斷當前幀是否還有磚塊

love.update = function(dt)
    if state.game_over or state.paused or state.stage_cleared then
        return
    end
    -- 運行實體的update
    local isBrick = false
    for i, entity in ipairs(entities) do
        if entity.type == "brick" then
            isBrick = true
        end
        if entity.update then
            entity:update(dt)
        end
    end
    -- 檢測實體health
    local index = 1
    while index <= #entities do
        if entities[index].health == 0 then
            local entity = table.remove(entities, index)
            entity.fixture:destroy()
        else
            index = index + 1
        end
    end
    world:update(dt)
    if not isBrick then
        state.stage_cleared = true
    end
end
image-20230821054718520
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 通過vmstat分析性能 如上圖所示,我們在命令vmstat後面添加了兩個參數,1表示間隔一秒獲取一次,10表示總共獲取10次 我們一列一列數據來看: r:代表目前實際運行的指令隊列,很高表示CPU很繁忙通常會CPU使用率過高 這個數據如果高於伺服器CPU核數就可能出現瓶頸(需要結合後五列CPU使用 ...
  • 之前實現的c語言項目單元測試框架cutest, 功能更新, 已上傳至ubuntu ppa倉庫, 方便安裝使用, 文末附帶視頻說明. ...
  • 引言 我們都知道,Redis 的數據存儲在記憶體中, 一旦伺服器宕機,記憶體中的數據將全部丟失。因此,對 Redis 來說,實現數據的持久化,避免從後端資料庫中進行恢復,是至關重要的。本篇我們詳細講解下 Redis 的三種持久化機制,分別是 AOF(Append Only File) 日誌和 RDB 快 ...
  • > Vue2.x使用EventBus進行組件通信,而Vue3.x推薦使用`mitt.js`。 > > > 比起Vue實例上的`EventBus`,`mitt.js`好在哪裡呢?首先它足夠小,僅有200bytes,其次支持全部事件的監聽和批量移除,它還不依賴Vue實例,所以可以跨框架使用,React或 ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230817155723872-372310672.png) # 1. 基本信息 發佈!設計與部署穩定的分散式系統 第2版 Release It! Design and Dep ...
  • > 任何傻瓜都可以寫出電腦能懂的代碼,但好的程式員可以寫出人類能懂的代碼—–Martin Fowler 如果你是新手,你可能會問,為什麼代碼需要設計原則? 我想說的是肯定不是為了故作高深,存在即是合理。 如果寫了一個簡單的程式,你可能不需要設計原則。 如果你寫了一個複雜的,但是之後再也不會改,那麼 ...
  • 領域區域設計的分層架構模型其實是在不斷優化和發展的,從最早的傳統直腸子式的四層架構模型,逐漸演變成目前以依賴倒置為原則的新的四層架構模型,從而實現了各層對基礎設施層的解耦。 DDD中的分層架構很好的應用了[關註點分離原則](http://www.cnblogs.com/LittleFeiHu/p/6 ...
  • 本文通過docker快速部署elasticsearch8版本,再添加一臺組成集群,並且部署kibana用於常規查詢操作,以及一些常見的es操作 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...