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

場景與其切換

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

  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大師代發,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM