1. 程式人生 > >Love2D遊戲引擎制作貪吃蛇遊戲

Love2D遊戲引擎制作貪吃蛇遊戲

時間 背景 速度 oss 類型 options 暫停 rectangle lua腳本

代碼地址如下:
http://www.demodashi.com/demo/15051.html

Love2D遊戲引擎制作貪吃蛇遊戲

內附有linux下的makefile,windows下的生成方法請查看:

for windows

預覽遊戲

技術分享圖片

技術分享圖片

技術分享圖片

love2d遊戲引擎重要函數

詳情:

love2d wiki

  • love.load:當遊戲開始時被調用且僅調用一次

  • love.draw:回調函數,每幀更新一次遊戲畫面

  • love.update:回調函數,每幀更新一次遊戲狀態

  • love.keypressed:回調函數,當有按鍵被按下時觸發

  • love.filesystem.load:加載一個lua腳本文件但不執行

!其他的函數在用到時再做解釋

版本區別以及初始化資源

!首先要註意的是,本次使用的遊戲引擎時love 0.9版本,與最新的love 11.x版本稍有區別。在0.9版本中顏色使用0~255來表示,而在11.x版本中是0~1來表示。

因為需要制作的遊戲非常小,所以我們將所用到的資源在第一時間將其初始化並加載到內存中,以便使用。使用到的資源主要有:

  • 字體

  • 顏色

  • 聲音

  • 窗口大小與塊大小

  • 標題

  • 邊框

所用到的函數:

  • love.window.setMode:設置窗口大小,以及樣式

  • love.window.setTitle:設置標題

  • love.graphics.newFont:加載字體文件,大小自定義,返回Font類型

  • love.audio.newSource:加載音效文件

代碼如下:

function love.load ()
    -- 塊大小,窗口寬高,標題
    cellSize = 20
    width = 20 * 40
    height = 20 * 25
    title = 'SNAKE !'

    -- 設置窗口大小和標題
    love.window.setMode (width, height)
    love.window.setTitle (title)

    -- 加載不同大小字體
    fonts = {
        pixies100 = love.graphics.newFont ('Fonts/Pixies.TTF', 100),
        pixies30 = love.graphics.newFont ('Fonts/Pixies.TTF', 30),
        pixies10 = love.graphics.newFont ('Fonts/Pixies.TTF', 10)
    }

    -- 加載音效資源
    sounds = {
        showMenu = love.audio.newSource ('Sounds/showMenu.wav', 'stream'),
        switchOption = love.audio.newSource ('Sounds/switchOption.wav', 'stream'),
        eatFood = love.audio.newSource ('Sounds/eatFood.wav', 'stream'),
        collided = love.audio.newSource ('Sounds/collided.wav', 'stream'),
        gameOver = love.audio.newSource ('Sounds/gameOver.wav', 'stream')
    }

    -- 邊框數據
    border = {
        1, 1,
        width-1, 1,
        width-1, height-1,
        1, height-1,
        1, 1
    }

    -- 顏色數據
    colors = {
        darkGray = { 0.3, 0.3, 0.3, 1 },
        beiga = { 0.98, 0.91, 0.76, 1 },
        white = { 1, 1, 1, 1 },
        paleTurquoise = { 0.7, 1, 1, 1 },
    }

    SwitchScence ('Menu')
end

場景與其切換

!首先我們需要實現一個簡單的場景切換函數,因為一個遊戲總是有多個場景

  1. 先將love2d引擎的主要回調函數賦值nil以免之後出現錯誤
  2. 加載新場景的lua腳本
  3. 執行新場景的lua腳本

代碼如下:

function SwitchScence (scence)
    -- 將重要的函數賦予空值,以免沖突
    love.update = nil
    love.draw = nil
    love.keypressed = nil

    -- 將需要的場景加載進來,並執行load函數
    love.filesystem.load ('Scences/'..scence..'.lua') ()
    love.load ()
end

-- 切換到初始化場景
SwitchScence ('Init')

繪制開始界面

在這裏我們需要認識一些繪圖函數:

  • love.graphics.setFont:設置當期字體

  • love.graphics.setColor:設置當前顏色

  • love.graphics.rectangle:繪制矩形

  • love.graphics.line:繪制直線

  • love.graphics.print:在窗口上輸出

!繪制比較簡單,其他詳情都在代碼裏有詳細註釋,要註意的是我繪制選項的方法。options的有效長度並不是#options,而是options.count記錄的選項數量

代碼如下:

-- 遊戲標題,以及繪制位置
local gameName = {
    text = title,
    textX = cellSize * 12,
    textY = cellSize * 6
}

-- 選項:開始和退出
local options = {
    {
        text = "START",

        textX = cellSize * 18,
        textY = cellSize * 15 - 5,

        border = {
            cellSize*16, cellSize*14,
            cellSize*24, cellSize*14,
            cellSize*24, cellSize*17,
            cellSize*16, cellSize*17,
            cellSize*16, cellSize*14
        }
    },
    {
        text = "QUIT",

        textX = cellSize * 19 - 10,
        textY = cellSize * 19 - 5,

        border = {
            cellSize*16, cellSize*18,
            cellSize*24, cellSize*18,
            cellSize*24, cellSize*21,
            cellSize*16, cellSize*21,
            cellSize*16, cellSize*18
        }
    },

    -- 一些其他屬性
    count = 2,
    selected = 1
}

function love.load ()
    -- 加載並播放背景音樂
    sounds.showMenu:play ()

    -- 設置米色和藍色的透明程度為0,為了之後的動畫效果
    colors.beiga[4] = 0
    colors.paleTurquoise[4] = 0
end

function love.draw ()
    -- 灰色背景
    love.graphics.setColor (colors.darkGray)
    love.graphics.rectangle (
        'fill',
        0,
        0,
        width,
        height
    )

    -- 白色邊框
    love.graphics.setColor (colors.white)
    love.graphics.line (border)

    -- 漸顯效果
    if colors.beiga[4] < 1 then
        colors.beiga[4] = colors.beiga[4] + 0.01
        colors.paleTurquoise[4] = colors.paleTurquoise[4] + 0.01
    end

    -- 設置字體,在指定位置畫出米色標題
    love.graphics.setFont (fonts.pixies100)
    love.graphics.setColor (colors.beiga)
    love.graphics.print (gameName.text, gameName.textX, gameName.textY)

    -- 設置字體
    love.graphics.setFont (fonts.pixies30)

    -- 繪制所有選項
    for i = 1, options.count do
        if i == options.selected then
            love.graphics.setColor (colors.paleTurquoise)
        else
            love.graphics.setColor (colors.beiga)
        end

        -- 繪制選項邊框和字體
        love.graphics.line (options[i].border)
        love.graphics.print (options[i].text, options[i].textX, options[i].textY)
    end
end

function love.keypressed (key)
    -- 上下箭頭選擇選項,回車按鍵確認選項
    if key == 'up' then
        -- 關閉切換選項的聲音並重新播放
        if sounds.switchOption.isPlaying then
            sounds.switchOption:stop ()
        end
        sounds.switchOption:play ()

        -- 切換當前選項索引
        options.selected = options.selected - 1
        if options.selected <= 0 then
            options.selected = options.count
        end
    elseif key == 'down' then
        -- 同上
        if sounds.switchOption.isPlaying then
            sounds.switchOption:stop ()
        end
        sounds.switchOption:play ()

        options.selected = options.selected + 1
        if options.selected > options.count then
            options.selected = 1
        end
    elseif key == 'return' then
        -- 關閉顯示界面聲音
        if sounds.showMenu.isPlaying then
            sounds.showMenu:stop ()
        end

        -- 對應不同選項作出不同回應
        if options.selected == 1 then
            SwitchScence ('GameStart')
        elseif options.selected == 2 then
            love.event.quit ()
        end
    end
end

實現遊戲主體

遊戲的實現方法,主要知道兩個方面:

  • 蛇的移動方式:根據方向獲取下一個頭的位置,若沒有吃到食物就將蛇尾刪除,達到移動效果
-- 下一個蛇頭位置
local nextX = snake.body[1].x
local nextY = snake.body[1].y

-- 當方向隊列中的方向大於1時除去第一個方向(當前方向)
if #directionQueue > 1 then
    table.remove (directionQueue, 1)
end

-- 根據方向作出改動
if directionQueue[1] == 'right' then
    nextX = nextX + 1
    if nextX > limit.x then
        nextX = 0
    end
    elseif directionQueue[1] == 'left' then
        nextX = nextX - 1
        if nextX < 0 then
            nextX = limit.x
        end
    elseif directionQueue[1] == 'down' then
       nextY = nextY + 1
       if nextY > limit.y then
          nextY = 0
       end
    elseif directionQueue[1] == 'up' then
        nextY = nextY - 1
        if nextY < 0 then
            nextY = limit.y
        end
    end

    -- 蛇是否可以移動(沒有與自身相撞)
    local canMove = true
    for index, pair in ipairs (snake.body) do
        if index ~= #snake.body
        and nextX == pair.x
        and nextY == pair.y then
            canMove = false
        end
    end

    -- 當蛇可以移動時
    if canMove then
        -- 將新位置加在蛇身的頭,並檢測是否吃到了食物
        table.insert (snake.body, 1, { x = nextX, y = nextY })
        if nextX == food.x and nextY == food.y then
            -- 播放吃到食物的音效(關閉之前的音效)
            if sounds.eatFood.isPlaying then
                sounds.eatFood:stop ()
            end
            sounds.eatFood:play ()

            -- 分數加一,並生成新的食物位置
            currentScore.score = currentScore.score + 1
                CreateFood ()
            else
            -- 沒有吃到食物則刪去蛇身的尾部,達到移動的目的
            table.remove (snake.body)
        end
    else
    -- 蛇死亡,並播放相撞的音效
        snake.alive = false
        sounds.collided:play ()
    end
end
  • 方向隊列的引入:主要是解決鍵位沖突的問題
function love.keypressed (key)
    -- 空格鍵暫停遊戲
    if key == 'space' then
        paused = not paused
    end

    -- 沒有暫停時
    if not paused then
        -- 記錄方向鍵的按下順序,同方向或相反方向的不記錄
        if key == 'right'
        and directionQueue[#directionQueue] ~= 'right'
        and directionQueue[#directionQueue] ~= 'left' then
            table.insert (directionQueue, 'right')
        elseif key == 'left'
        and directionQueue[#directionQueue] ~= 'left'
        and directionQueue[#directionQueue] ~= 'right' then
            table.insert (directionQueue, 'left')
        elseif key == 'down'
        and directionQueue[#directionQueue] ~= 'down'
        and directionQueue[#directionQueue] ~= 'up' then
            table.insert (directionQueue, 'down')
        elseif key == 'up'
        and directionQueue[#directionQueue] ~= 'up'
        and directionQueue[#directionQueue] ~= 'down' then
            table.insert (directionQueue, 'up')
        end
    end
end

代碼如下:

-- 遊戲窗口與記分窗口的分界線
local boundary = {
    cellSize*30, 0,
    cellSize*30, height
}

-- 當前分數的信息
local currentScore = {
    text = 'SCORE',
    score = 0,

    -- 文字的繪圖位置
    textX = cellSize * 33,
    textY = cellSize * 2,

    -- 分數的繪圖位置
    scoreX = cellSize * 34,
    scoreY = cellSize * 5
}

-- 最高分的信息
local highScore = {
    text = 'HIGH SCORE',
    score = 0,

    -- 同上
    textX = cellSize * 31,
    textY = cellSize * 12,

    scoreX = cellSize * 34,
    scoreY = cellSize * 15
}

-- 提示信息
local notes = {
    {
        text = 'ARROW KEY TO MOVE',
        textX = cellSize * 34,
        textY = cellSize * 22
    },
    {
        text = 'ENTER KEY TO PAUSE',
        textX = cellSize * 34,
        textY = cellSize * 23
    }
}

-- 遊戲窗口的限制
local limit = { x = 29, y = 24 }

-- 蛇的初始化信息
local snake = {
    -- 蛇身
    body = {
        { x = 2, y = 0 },
        { x = 1, y = 0 },
        { x = 0, y = 0 }
    },

    -- 速度與狀態
    speed = 0.1,
    alive = true,
}

-- 食物的位置
local food = { x = nil, y = nil }

-- 方向隊列,用於記錄鍵盤按下的順序以免產生沖突
local directionQueue = { 'right' }

-- 計時器,暫停狀態以及最高分文件
local timer = 0
local paused = false
local file = nil

-- 用於生成食物的可存在位置
local function CreateFood ()
    local foodPosition = {}

    -- 遍歷整個窗口,將可生成食物的位置記錄在foodPosition表裏
    for i = 0, limit.x do
        for j = 0, limit.y do
            local possible = true

            -- 是否與蛇身沖突
            for index, pair in ipairs (snake.body) do
                if i == pair.x and j == pair.y then
                    possible = false
                end
            end

            if possible then
                table.insert (foodPosition, { x = i, y = j })
            end
        end
    end

    -- 生成隨機食物位置
    local index = love.math.random (#foodPosition)
    food.x, food.y = foodPosition[index].x, foodPosition[index].y
end

function love.load ()
    file = love.filesystem.newFile ('HighScore.txt')
    file:open ('r')
    highScore.score = file:read ()
    file:close ()

    -- 沒有透明度
    colors.beiga[4] = 1
    colors.paleTurquoise[4] = 1

    CreateFood ()
end

function love.draw ()
    -- 繪制背景
    love.graphics.setColor (colors.darkGray)
    love.graphics.rectangle (
        'fill',
        0,
        0,
        width,
        height
    )

    -- 繪制白色邊框和邊界線
    love.graphics.setColor (colors.white)
    love.graphics.line (border)
    love.graphics.line (boundary)

    -- 設置字體和顏色,並在指定位置繪制當前分數信息和最高分信息
    love.graphics.setFont (fonts.pixies30)
    love.graphics.setColor (colors.beiga)
    love.graphics.print (currentScore.text, currentScore.textX, currentScore.textY)
    love.graphics.print (currentScore.score, currentScore.scoreX, currentScore.scoreY)
    love.graphics.setColor (colors.paleTurquoise)
    love.graphics.print (highScore.text, highScore.textX, highScore.textY)
    love.graphics.print (highScore.score, highScore.scoreX, highScore.scoreY)

    -- 蛇生存和死亡時使用不同的顏色繪制
    if snake.alive then
        love.graphics.setColor (colors.paleTurquoise)
    else
        love.graphics.setColor (colors.beiga)
    end

    -- 繪制蛇身,蛇頭另繪
    for index, pair in ipairs (snake.body) do
        if index == 1 then
            love.graphics.rectangle (
                'fill',
                cellSize*pair.x,
                cellSize*pair.y,
                cellSize,
                cellSize
            )
        end
        love.graphics.rectangle (
            'fill',
            cellSize*pair.x+1,
            cellSize*pair.y+1,
            cellSize-1*2,
            cellSize-1*2
        )
    end

    -- 繪制食物
    love.graphics.setColor (colors.beiga)
    love.graphics.rectangle (
        'fill',
        cellSize*food.x+1,
        cellSize*food.y+1,
        cellSize-1*2,
        cellSize-1*2
    )

    -- 如果是暫停狀態,則繪制暫停字樣
    if paused then
        love.graphics.print ('PAUSED !', cellSize*12, cellSize*11)
    end

    -- 設置字體和顏色並繪制提示信息
    love.graphics.setFont (fonts.pixies10)
    love.graphics.setColor (colors.beiga)
    for i = 1, #notes do
        love.graphics.print (notes[i].text, notes[i].textX, notes[i].textY)
    end
end

function love.update (dt)
    -- 使用計時器
    timer = timer + dt

    -- 當蛇生存時
    if snake.alive then
        -- 根據蛇的速度更新遊戲
        if timer > snake.speed then
            timer = timer - snake.speed

            -- 沒有暫停時
            if not paused then
                -- 下一個蛇頭位置
                local nextX = snake.body[1].x
                local nextY = snake.body[1].y

                -- 當方向隊列中的方向大於1時除去第一個方向(當前方向)
                if #directionQueue > 1 then
                    table.remove (directionQueue, 1)
                end

                -- 根據方向作出改動
                if directionQueue[1] == 'right' then
                    nextX = nextX + 1
                    if nextX > limit.x then
                        nextX = 0
                    end
                elseif directionQueue[1] == 'left' then
                    nextX = nextX - 1
                    if nextX < 0 then
                        nextX = limit.x
                    end
                elseif directionQueue[1] == 'down' then
                    nextY = nextY + 1
                    if nextY > limit.y then
                        nextY = 0
                    end
                elseif directionQueue[1] == 'up' then
                    nextY = nextY - 1
                    if nextY < 0 then
                        nextY = limit.y
                    end
                end

                -- 蛇是否可以移動(沒有與自身相撞)
                local canMove = true
                for index, pair in ipairs (snake.body) do
                    if index ~= #snake.body
                    and nextX == pair.x
                    and nextY == pair.y then
                        canMove = false
                    end
                end

                -- 當蛇可以移動時
                if canMove then
                    -- 將新位置加在蛇身的頭,並檢測是否吃到了食物
                    table.insert (snake.body, 1, { x = nextX, y = nextY })
                    if nextX == food.x and nextY == food.y then
                        -- 播放吃到食物的音效(關閉之前的音效)
                        if sounds.eatFood.isPlaying then
                            sounds.eatFood:stop ()
                        end
                        sounds.eatFood:play ()

                        -- 分數加一,並生成新的食物位置
                        currentScore.score = currentScore.score + 1
                        CreateFood ()
                    else
                        -- 沒有吃到食物則刪去蛇身的尾部,達到移動的目的
                        table.remove (snake.body)
                    end
                else
                    -- 蛇死亡,並播放相撞的音效
                    snake.alive = false
                    sounds.collided:play ()
                end
            end
        end
    -- 等待一秒
    elseif timer >= 1 then
        -- 存儲最高分
        if currentScore.score > tonumber (highScore.score) then
            file:open ('w')
            file:write (tostring (currentScore.score))
            file:close ()
        end

        -- 切換到遊戲結束場景
        SwitchScence ('GameOver')
    end
end

function love.keypressed (key)
    -- 回車鍵暫停遊戲
    if key == 'return' then
        paused = not paused
    end

    -- 沒有暫停時
    if not paused then
        -- 記錄方向鍵的按下順序,同方向或相反方向的不記錄
        if key == 'right'
        and directionQueue[#directionQueue] ~= 'right'
        and directionQueue[#directionQueue] ~= 'left' then
            table.insert (directionQueue, 'right')
        elseif key == 'left'
        and directionQueue[#directionQueue] ~= 'left'
        and directionQueue[#directionQueue] ~= 'right' then
            table.insert (directionQueue, 'left')
        elseif key == 'down'
        and directionQueue[#directionQueue] ~= 'down'
        and directionQueue[#directionQueue] ~= 'up' then
            table.insert (directionQueue, 'down')
        elseif key == 'up'
        and directionQueue[#directionQueue] ~= 'up'
        and directionQueue[#directionQueue] ~= 'down' then
            table.insert (directionQueue, 'up')
        end
    end
end

實現最高分的保存與讀取

遊戲存檔目錄:

  • Windows XP: C:\Documents and Settings\user\Application Data\LOVE?or %appdata%\LOVE
  • Windows Vista and 7,8: C:\Users\user\AppData\Roaming\LOVE or %appdata%\LOVE
  • Linux: $XDG_DATA_HOME/love/ or ~/.local/share/love/

  • Mac: /Users/user/Library/Application Support/LOVE/

!寫文件只能在存檔目錄

最高分讀取:

file = love.filesystem.newFile ('HighScore.txt')
file:open ('r')
highScore.score = file:read ()
file:close ()

最高分保存:

file:open ('w')
file:write (tostring (currentScore.score))
file:close ()

繪制遊戲結束界面

遊戲結束界面的繪制與開始界面大致相同,這裏不再贅述

代碼如下:

local gameOver = {
    text = 'GAME OVER !',
    textX = cellSize * 6,
    textY = cellSize * 6
}

-- 選項:開始和退出
local options = {
    {
        text = "BACK",

        textX = cellSize * 13 - 15,
        textY = cellSize * 17 - 5,

        border = {
            cellSize*10, cellSize*16,
            cellSize*18, cellSize*16,
            cellSize*18, cellSize*19,
            cellSize*10, cellSize*19,
            cellSize*10, cellSize*16
        }
    },
    {
        text = "RETRY",

        textX = cellSize * 24,
        textY = cellSize * 17 - 5,

        border = {
            cellSize*22, cellSize*16,
            cellSize*30, cellSize*16,
            cellSize*30, cellSize*19,
            cellSize*22, cellSize*19,
            cellSize*22, cellSize*16
        }
    },

    -- 一些其他屬性
    count = 2,
    selected = 1
}

function love.load ()
    sounds.gameOver:play ()

    -- 設置米色和藍色的透明程度為0,為了之後的動畫效果
    colors.beiga[4] = 0
    colors.paleTurquoise[4] = 0
end

function love.draw ()
    -- 灰色背景
    love.graphics.setColor (colors.darkGray)
    love.graphics.rectangle (
        'fill',
        0,
        0,
        width,
        height
    )

    -- 白色邊框
    love.graphics.setColor (colors.white)
    love.graphics.line (border)

    -- 漸顯效果
    if colors.beiga[4] < 1 then
        colors.beiga[4] = colors.beiga[4] + 0.01
        colors.paleTurquoise[4] = colors.paleTurquoise[4] + 0.01
    end

    -- 設置字體,在指定位置畫出米色標題
    love.graphics.setFont (fonts.pixies100)
    love.graphics.setColor (colors.beiga)
    love.graphics.print (gameOver.text, gameOver.textX, gameOver.textY)

    -- 設置字體
    love.graphics.setFont (fonts.pixies30)

    for i = 1, options.count do
        if i == options.selected then
            love.graphics.setColor (colors.paleTurquoise)
        else
            love.graphics.setColor (colors.beiga)
        end

        love.graphics.line (options[i].border)
        love.graphics.print (options[i].text, options[i].textX, options[i].textY)
    end
end

function love.keypressed (key)
    -- 上下箭頭選擇選項,回車按鍵確認選項
    if key == 'left' then
        if sounds.gameOver.isPlaying then
            sounds.gameOver:stop ()
        end

        if sounds.switchOption.isPlaying then
            sounds.switchOption:stop ()
        end
        sounds.switchOption:play ()

        options.selected = options.selected - 1
        if options.selected <= 0 then
            options.selected = options.count
        end
    elseif key == 'right' then
        if sounds.gameOver.isPlaying then
            sounds.gameOver:stop ()
        end

        if sounds.switchOption.isPlaying then
            sounds.switchOption:stop ()
        end
        sounds.switchOption:play ()

        options.selected = options.selected + 1
        if options.selected > options.count then
            options.selected = 1
        end
    elseif key == 'return' then
        if sounds.gameOver.isPlaying then
            sounds.gameOver:stop ()
        end

        if options.selected == 1 then
            SwitchScence ('Menu')
        elseif options.selected == 2 then
            SwitchScence ('GameStart')
        end
    end
end

項目結構

項目結構圖如下

技術分享圖片

Love2D遊戲引擎制作貪吃蛇遊戲

代碼地址如下:
http://www.demodashi.com/demo/15051.html

註:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權

Love2D遊戲引擎制作貪吃蛇遊戲