Lua 学习记录
前言
啊,我真的懒得写了。明明应该好好学 Python
的,怎么学起 Lua
了……
其实,我是看到别人用 Lua
和 LÖVE
框架做了俄罗斯方块,自己也想做一个,才研究了一下 Lua
(明明 Python
也可以做啊)。
配置
Lua
Lua
在官网上下载即可,然后配置环境变量,将包含 Lua
的文件夹路径加入环境变量中。之后就可以在命令行执行:
lua54 |
显示相关信息,则说明 Lua
配置成功。要注意的是,下载的版本不同,执行的命令可能也不同,我的版本是 lua-5.4.2
。这一问题在后面配置 VSCode 时还会遇到。
LÖVE
LÖVE
在这里下载,配置环境变量。将包含 LÖVE
的文件夹路径加入环境变量中。之后在命令行执行:
love |
会启动默认的 LÖVE
程序。
VSCode
没错,我还是想用 VSCode ,所以就需要配置一下了。
VSCode 需要使用相应的拓展。我使用的是 Code Runner
和 Love2D Support
,在配置拓展时,需要对 Code Runner
的默认配置进行修改,并设置 love.exe
的路径(我也不知道为什么还要再设一次路径,明明有环境变量了)。
"code-runner.executorMap": { |
Code Runner
是用来跑 Lua
的, Love2D Support
是跑 LÖVE
的。在 Love2D Support
拓展的设置中,可以设置开启 LÖVE
的控制台,这样就能看见 print()
的输出了(草)。
测试
测试 Lua
的情况。
print("hello world!") |
如果中文输出出现乱码,很有可能因为使用的终端是 cmd
,有两种方法解决该问题。第一种方法是将使用的终端改为 powershell
;第二种方法则是将 cmd
的编码改为 UTF-8
。
"terminal.integrated.defaultProfile.windows": "PowerShell", |
测试 LÖVE
的情况。
-- 初始化矩形的一些默认值 |
按住 Alt + L,应当会有 LÖVE
程序启动,绘制一个不断变大的青色矩形。当然,拓展的这个快捷键可以在设置中进行修改。
编写游戏
基础
配置文件
在游戏目录中的 conf.lua
文件夹,会在 LÖVE
模块加载前运行,可以使用该文件重写 love.conf
函数。
love.conf
函数有一个参数,该参数是一个包含所有默认值的 table
类型参数。通过 love.conf
函数,可以修改默认值,关闭不需要的模块:
function love.conf(t) |
以下是 11.3
和 11.4
的全部默认值:
function love.conf(t) |
各配置详细的作用请参考:Config Files - LOVE (love2d.org)
运行
love.run()
是运行的主函数,它包含了运行时的主循环,不同版本的默认值不同。
-- The default function for 11.0, used if you don't supply your own. |
可以参见love.run - LOVE (love2d.org)
任务目标
做一款现代俄罗斯方块,具有以下特点:
- 配色:每个方块的颜色有要求
- 延迟锁定:方块接触到地面后,不会立刻锁定
- 旋转系统:根据踢墙表,存在踢墙情况
- 出块系统:使用
7-Bag
而不是纯随机的出块方式
可以参见俄罗斯方块心得记录。在写代码时,学习参考了 MrZ 大佬的代码:
实现
以下是我的代码,还没写完(小声)
场地参考
场地的话,计划使用 10×40 的场地,其中正常游戏只使用 10×20,剩下 10×20 用于存储顶出显示场地的方块。通过建立一个以场地为参考的坐标描述,我们可以描述方块的位置信息,并将游戏逻辑与绘图的逻辑分开。
实现起来,主要是通过一个二维数组记录 10*40 场地的情况。其中,每格不同的数值代表不同的方块。目前,0
代表无方块,1~7
代表七种方块。这样,以后既可以拓展格子的类型,又可以绘制彩色的场地。
从数据结构的角度,先消除在线性表靠后数据,再消除在线性表靠前的数据,操作要更方便一些。因此,消行的顺序就很关键。
方块表达
方块,以及方块与其他要素的交互是最麻烦的一部分。
方块表达时,采用以下的方式。
方块的边界框是包含四个方向形状的外接矩形。除了 I
和 O
的边界框是 4×4 和 2×2 以外,其他方块的边界框是 3×3 ,若要确定每个 mino
的位置,需要知道此刻边界框的位置和方块的朝向。在我们这次的编写中,我们通过边界框左上角的场地坐标,确定方块的位置。此外,在方块新生成的时候, I
和 O
是居中的,而其他方块是偏左的,它们的边界框左上角坐标也不相同。这些生成时的初始坐标,记录在一个表中。
-- 方块活动框左上角坐标 (x,y) |
为了表示各个方向下的方块形状,也采用了表结构存储,在已知方块类型和方块朝向后,就能立刻获取其形状。
-- 记录的各方向的方块形状(从下往上):1-原位(0) 2-顺时针位(R) 3-180度位(2) 4-逆时针位(L) |
在后面的实现中,还使用了不少的表,比如行状态表、列状态表、颜色表、踢墙表等等。
方块碰撞
这点是最复杂的地方,而且牵扯到踢墙的概念。
由于每个方块的边界框mino
的行或列进行特殊处理的话,就可能造成(场地下边界或场地左右边界)数组越界或是方块悬空的状况。因此,根据方块的形状表,我计算了对应的行状态与列状态表(遇事不决就打表)。在此基础上,可以实现一个判断方块是否超出场地,与场地内方块重叠的函数。通过该函数,我们可以做到判断方块是否落地(落地了就要计时,准备延迟锁定了)。通过该函数,我们可以判断踢墙的结果是否可行。
踢墙的概念这里不再多说。有踢墙表之后,只需要按顺序进行平移的尝试,成功则采用,不成功则方块不能旋转。
延迟锁定
目前我只做了延迟锁定,其表现为:
- 非落地状态下,不进行延迟锁定计时。
- 落地状态下,移动或旋转后变为非落地状态后,不进行延迟锁定计时。
- 落地状态下不会立即锁定,而是保留一段继续操作的时间。
- 不进行移动或旋转,在设定的时间(锁定延迟)过后,方块锁定。
- 落地状态下无法移动(左右卡住),或无法旋转(踢墙表检测全部失败)时,视作未进行操作。注意
O
的旋转虽然看起来没有变化,但它是确确实实成功旋转了的,所以会刷新锁定延迟。
同时,需要注意,为了避免无限刷新锁定延迟的情况,应该增加一个操作次数限制:
- 非落地状态下,不计入操作次数限制。
- 落地状态下,一次移动或旋转后,计一次操作次数。
- 落地状态下无法移动(左右卡住),或无法旋转(踢墙表检测全部失败)时,视作未进行操作,不计入操作次数限制。注意
O
的旋转虽然看起来没有变化,但它是确确实实成功旋转了的,所以会计一次操作次数。 - 当操作次数用尽后,移动和旋转将不再刷新锁定延迟。
今天(2022.8.23)我实现操作次数的时候,发现还有一个问题:
-
如果操作次数用尽后,踢墙产生的偏移不能向上。
这个规定的意义在于,避免玩家通过踢墙将方块“抬高”,然后方块下落,刷新延迟锁定,导致无限操作而不锁定的问题。
出块序列
实际上就是一个队列,当队列短于设定的数量时。按选定的随机生成器,生成一段新序列加到队尾。
如果是 bag
出块,则保证要添加的新序列中每种方块各出现一次
暂存
暂存的实现思路很简单,就是记录下暂存的方块 id 和暂存的操作次数限制而已。但是在这个过程中,我意识到关于暂存计数、锁延计数的重置时机问题。
根据游戏逻辑,在暂存之后,更换的方块会重新从场地顶部开始下落,并且方块的锁延计数会重置。暂存计数就是为了避免玩家通过无限的暂存,重置锁延计数的情况。所以暂存之后,锁延计数会重置,暂存计数 -1
。而暂存计数重置的时机,应当是一个方块成功锁定后,这是毫无疑问的。
可是,锁延计数是什么时候重置呢?在没有暂存功能的情况下,锁延计数在方块成功锁定后,或者新生成方块时重置都是没有问题的。但是引入了暂存功能后,暂存方块会导致方块的锁延计数重置。因此,如果将锁延计数在方块成功锁定后重置,会导致暂存后新方块的锁延计数未重置。所以锁延计数应当在生成方块时更新。
换个角度想想,暂存后的方块,本质就是新生成了一个方块,这和方块锁定后新生成方块的原理应当一致。而锁延计数会在方块锁定后和暂存后重置,因此在生成方块时重置锁延计数能很好地解决这两种情况。
此外,在暂存中无方块和暂存中有方块时,对序列的处理有一定的区别。前者是将当前块暂存,从序列中新取一块作为当前块;后者是将当前块与暂存块交换。
发布
创建 .love 文件
将游戏文件变为 .exe
文件的步骤比较简单:
- 将游戏文件加入
.zip
压缩文件中 - 将后缀更改为
.love
此文件就可以通过以下方式运行:
love xxx.love |
针对 windows 平台构建
- 将
love.exe
与.zip
文件合并为一个.exe
文件 - 将
.dll
文件、许可license.txt
和 合并得到的.exe
文件放在同一个目录下,即可运行游戏
网页发布
推荐使用 Davidobot/love.js,该作者目前还保持着更新,适用于 11.4
版本
通过以下方式安装:
npm i love.js |
或
npm -g i love.js |
然后构建兼容版本:
love.js game.love game -c |
如果命令无法正常执行(比如说,打开了 love.js
文件),则可以使用以下命令:
love.js.cmd game.love game -c |
即可完成构建。该命令会在当前目录生成一个文件夹:
Tetris |
其中,game.data
和 game.js
是游戏的生成文件, index.html
就是生成的网页,而 theme
目录下的文件是页面的美化相关的文件。love.js
和 love.wasm
是 love
框架对应的文件。
游戏基本上完成了,剩下的东西主要是美化辅助和提升操作手感的方面(阴影和 DAS ),大概率不会更新了……
各种发布方式(包括网页发布)具体可以请参考:Game Distribution - LOVE (love2d.org)