【LÖVE 11.3】LÖVE - 免費多平臺的2D遊戲引擎

LÖVE是一個免費多平臺的2D遊戲引擎,主體是以Lua這種手稿語言,它的語法相當簡潔,它是以C++編寫,支援多平臺輸出,在官網有介紹幾個上架的遊戲 - iOS / Android + Windows / macOS / Linux,好像都是可愛的小遊戲…XD,至於為什麼會想學這個呢?因為Unity3D太過強大,而且自己只是想寫一個小品遊戲當成生日禮物送人,工程師的浪漫嘛…XD,笑容可掬的女生,最高…

作業環境

項目 版本
CPU Apple M1
macOS Big Sur 11.2.3
LÖVE 11.3

安裝

brew install --cask love

基礎教學

程式碼範例

  • main.lua是程式一開始的進入點,使用love ./就可以開啟遊戲
  • 因為上面都有寫簡單的註解了,所以就不多做說明
  • 這裡要記錄一下,eqMAC可以讓macOS內建的錄影程式,錄到本身的聲音 (模擬麥克風?)…
-- MARK: 生命週期
-- 一開始載入的時候
function love.load()
    initSetting()
end

-- 繪製畫面的時候
function love.draw()
    drawPicture()
    drawFont(40)
end

-- 定時更新 (60fps => 0.016664166999817 (1/60))
function love.update(deltaTime)
    -- print(deltaTime)
end

-- 滑鼠按下去的時候
function love.mousepressed(x, y, button)
    
    -- button == 1 (左鍵)
    if (button == 1) then
        love.audio.play(music.error)
    end

    -- button == 2 (右鍵)
    if button == 2 then
        love.audio.play(music.bgm)
    end

    --
    print(button)
end

-- 按下鍵盤的時候
function love.keypressed(key)
    print(key)    
end

-- MARK: 小工具
-- 初始設定 (自定義的function)
function initSetting()
    screenSize()
    soundInfo()
    initTitle()
end

-- 設定標題文字
function initTitle()
    love.window.setTitle('Hello World')
end

-- 繪圖
function drawPicture()
    local imageName = 'image/love2d.png'        -- 區域變數
    image = love.graphics.newImage(imageName)   -- 全域變數
    love.graphics.draw(image, 300, 200)
end

-- 繪字
function drawFont(size)
    font = love.graphics.newFont('font/jf-openhuninn-1.1.ttf', size)                -- 產生字型 + 大小
    love.graphics.setFont(font)                                                     -- 設定字型
    love.graphics.printf('滑鼠左鍵音效 / 右鍵音樂', 0, 480, screen.width, 'center')     -- 繪出文字
end

-- 取得畫面大小
function screenSize()

    screen = {
        width = love.graphics.getWidth(),
        height = love.graphics.getHeight(),
    }
end

-- 設定音樂曲目
function soundInfo()
    
    music = {
        bgm = love.audio.newSource("sound/bgm.mp3", "stream"),          -- 一直播 => stream
        error = love.audio.newSource("sound/error.wav", "static")       -- 短聲音 => static
    }

    music.bgm:setLooping(true)  -- 一直播放
    music.bgm:setVolume(0.5)    -- 音量50%
end

生命週期

程式說明

其它常用

  • print():這個就是印在Command上的,Debug用

簡單的射擊遊戲

做一個長成這樣子的Game

基本設定

  • 首先之後都會切成好幾個檔,便於管理
  • 先設定背景 / 音樂 / 圖形物件,求能動再說
  • 使用require()這個function,可以載入其它地方的lua檔
  • 下面顯示的Code都是『非完整的』,完整內容請下載git
  • 在這裡唯一的資料結構叫Table - {},可以是Array,也可以是Map
-- ./main.lua
-- MARK: 生命週期
function love.load()
    importSource()
    initSprite()
    initSound()
    _setTitle('射擊遊戲')
    _setScreen(1024, 600)
end

function love.draw()
    _drawBackground(sprite.sky)
    local size = _getScreenSize()
    print(size.width, size.height)
end

-- 載入其它位置的檔案 => 容易維護
function importSource()
    require('source.utility')
    require('source.setting')
end
-- ./source/setting.lua
-- MARK: - 初始設定
-- 初始化圖形物件 => Table
function initSprite()
    sprite = {}
    sprite.sky = love.graphics.newImage('sprite/sky.png')
    sprite.target = love.graphics.newImage('sprite/target.png')
    sprite.crosshairs = love.graphics.newImage('sprite/crosshairs.png')
end

-- 初始化音效 => Table
function initSound()
    sound = {
        bgm = love.audio.newSource("sound/bgm.mp3", "stream"),
        shoot = love.audio.newSource("sound/shoot.wav", "static")
    }

    sound.bgm:setLooping(true)
    sound.bgm:setVolume(0.5)

    love.audio.play(sound.bgm)
end
-- ./source/utility.lua
-- MARK: - 公用工具
-- 設定遊戲的Title
function _setTitle(text)
    love.window.setTitle(text)
end

-- 畫背景 => 一定要最先畫,不然會蓋過其它的東西
function _drawBackground(image)
    love.graphics.draw(image, 0, 0)
end

-- 設定畫面大小
function _setScreen(width, height)
    love.window.setMode(width, height)
end

-- 取得畫面大小
function _getScreenSize()
    return { width = love.graphics.getWidth(), height = love.graphics.getHeight() }
end

設定文字

  • 倒數計時,其中很有趣的是,它的0是『-0』,所以文字會跑版 => -0,所以要修正一下
  • 分數 / 開始文字
-- ./main.lua
-- 繪出倒數計時的值
function printTimer()
    local time = '時間: '..math.ceil(timer.value)
    local size = _getScreenSize()
    _print(time, size.width / 2 - font.size / 2 - 40, 5)
end

-- 印出開始的一開始的標題
function printStartTitle()
    if game.isOver then
        local size = _getScreenSize()
        _printf('點這裡就開始了喲!', 0, 250, size.width, 'center')
    end
end

-- 繪出分數
function printScore()
    local text = '分數: '..game.score
    _print(text, 5, 5)
end
-- ./source/setting.lua
-- MARK: - 初始設定
-- 初始化變數
function initParameter()

    timer = {
        max = 3,
        value = 3,
    }
end
-- ./source/utility.lua
-- 時間倒數 => -0現象
function _timerCountDown(timer, deltaTime)

    local newTimer = timer - deltaTime

    if newTimer > 0 then
        return newTimer
    end

    return 0
end

繪出準心及標靶

  • 這裡有一個game.isOver的變數,用來記錄遊戲是否結束的狀態
  • 主要是準心要跟著滑鼠的位置做移動
-- 畫準心 => 跟著滑鼠移動,一定要最後畫,不然會不見
function drawCrosshairs()
    local position, size = _getMousePosition(), _getImageSize(sprite.crosshairs)
    _draw(sprite.crosshairs, position.x - size.width / 2, position.y - size.height / 2)
end

-- 畫標靶
function drawTarget()

    if game.isOver then
        local size = _getImageSize(sprite.target)
        _draw(sprite.target, target.x - size.width / 2, target.y - size.height / 2)
    end
end

標靶隨機位置

  • 最主要是要判斷滑鼠點擊的位置,是否在標靶的半徑內部
function love.mousepressed(x, y, button)

    -- button: 1 => 左鍵
    if button == 1 then

        if not game.isOver then
    
            local distance, targetRadius  = _distanceBetween(x, y, target.x, target.y), _getImageSize(sprite.target).height / 2
            
            love.audio.play(sound.shoot)

            -- 假如點在圈圈內的話 => 點到加分 + 換位置
            if distance < targetRadius then
                game.score = game.score + 1
                targetRandomPosition()
                return
            end
        end
    
        if game.isOver then
            restart()
        end
    end
end

-- 標靶隨機位置
function targetRandomPosition()
    
    local screenSize, targetSize = _getScreenSize(), _getImageSize(sprite.target)

    target.x = _random(targetSize.width / 2, screenSize.width - targetSize.width / 2)
    target.y = _random(targetSize.height / 2, screenSize.height - targetSize.height / 2)
end

遊戲發行

直接打包

  • 其實.love本身就是個.zip檔,只要把.zip => .love,在系統有安裝love的時候,就可以直接執行了。
  • 不過要注意的是,main.love一定要在第一層才可以,不含資料夾…
  • 當然用command也是滿方便的
zip -9 -r MyGame.love .

Windows => .EXE

  • 如果要送人的時候要怎麼辦呢?總不能叫別人安裝LÖVE吧,這樣子目的性太明顯了…XD
  • 簡單來說呢,就是把壓好的.love檔,跟love.exe直接合併在一起
  • 如果需要love.exe,請在這裡下載
# Windows
copy /b love.exe + MyGame.love MyGame.exe
copy /b love.exe + %1 "%~n1.exe"

#macOS
cat love.exe MyGame.love > MyGame.exe

不能動?加上.dll檔

  • 其實包完後會發現…不能動,因為它需要一些.dll檔,包完後就可以送人啦…XD

macOS => .APP

./Contents/Resources

範例程式碼下載

後記

  • 學一門新的程式語言真的是滿累人的,寫遊戲,要會的東西更是多了,學海無涯,回頭是岸…XD,難怪薪水高得可怕啊。