你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
首页
热门
推荐
精选
登录
|
注册
Love2D游戏引擎制作贪吃蛇游戏
立即下载
用AI写一个
金额:
5
元
支付方式:
友情提醒:源码购买后不支持退换货
立即支付
我要免费下载
发布时间:2019-03-09
9人
|
浏览:11096次
|
收藏
|
分享
技术:love2d + lua
运行环境:linux mint + love2d 11.2
概述
使用love2d游戏引擎制作完整的贪吃蛇游戏
详细
# Love2D游戏引擎制作贪吃蛇游戏 内附有linux下的makefile,windows下的生成方法请查看: [for windows](https://love2d.org/wiki/Game_Distribution#Create_a_.love-file) ## 预览游戏 ![](/contentImages/image/20190308/1SEmBaJZj1Ciu4hy2Ex.png) ![](/contentImages/image/20190308/n8EAJ6texJlRA4oFVyq.png) ![](/contentImages/image/20190308/C6FMgRhwtoI37gNyesC.png) ## love2d游戏引擎重要函数 详情: [love2d wiki](https://love2d.org/wiki/Main_Page) + 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 ``` ## 项目结构 > 项目结构图如下 ![](/contentImages/image/20190309/CkGJGlnAaisMJjsLKHL.jpg)
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
感谢
2
手机上随时阅读、收藏该文章 ?请扫下方二维码
相似例子推荐
评论
作者
Aaron
2
例子数量
24
帮助
2
感谢
评分详细
可运行:
4.5
分
代码质量:
4.5
分
文章描述详细:
4.5
分
代码注释:
4.5
分
综合:
4.5
分
作者例子
Love2D游戏引擎制作贪吃蛇游戏
基于优先级的时间片轮转调度算法(C语言实现)