Love2D遊戲引擎制作貪吃蛇遊戲
代碼地址如下:
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
場景與其切換
!首先我們需要實現一個簡單的場景切換函數,因為一個遊戲總是有多個場景
- 先將love2d引擎的主要回調函數賦值nil以免之後出現錯誤
- 加載新場景的lua腳本
- 執行新場景的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遊戲引擎制作貪吃蛇遊戲