效果如下
代码简介
俄罗斯方块大家都玩过,不做过多赘叙。
代码思路
1. 图像生成
1.1 场景生成
遍历19*18二维数组,
value=0 即空
value =1 即可以移动的blcok
value =2 即已经不会动的block
block为30*30的白色方块。
此处有取巧的设置,可以设置 20 ∗ 20 20*20 20∗20的二维数组,但是令最外圈的value=2,因为我绘制函数只画 19 ∗ 18 19*18 19∗18的部分,则初始value=2的部分相当于隐形墙壁,我们可以通过检测碰撞的方法做到限制方块的移动范围。
1.2 不同组合的方块
例如L型,同样是设置二维数组,遍历来打印图形
L_tetrimino = {
{1,0},
{1,0},
{1,1},
x=0,
y=0
}
这里的x,y 作为L型方块的坐标,方便去计算方块的移动
2.功能实现
基本功能也就是方块的一生
2.1 活动方块的诞生
function block_new()
-- 用来旋转的
shape_num =1
-- 判断方块是否货主
Blockstate = true
-- 初始化出生坐标
Tetrimino.x=7
Tetrimino.y = 0
-- 为随机变成不同样子做准备
local num = math.random(1, 6)
local name = TetriminoName[num]
X_Tetrimino=Tetrimino[name]
return X_Tetrimino[1],X_Tetrimino
end
2.2 方块的零件一:移动功能
- 利用下述代码实现计时器功能,让后设置一秒降落一格
-- 设置时间为1秒
moveInterval=1
function love.update(dt)
timeAcumulator = timeAcumulator + dt
if timeAcumulator>=moveInterval then
L_tetrimino.y=L_tetrimino.y+1
--时间计数器减去1秒又回到初始的地方开始计数
timeAcumulator = timeAcumulator - moveInterval
Block_drop(shape)
end
end
- 利用计时器对L型方块的坐标(x,y)进行计算,利用L型方块的行数和列数计算出L型方块在Blocklist中的位置并赋值,可这样遇到了第二个问题:方块落下的拖尾
function Block_drop(shape)
for i =1, #shape,1 do
for j = 1,#shape[i],1 do
blockList[y + i][x+ j] = shape[i][j]
end
end
end
2.3 方块的零件二:清理旧状态
我们通过对问题:方块落下的拖尾的探究已经得到了如何去消除方块的旧状态
function clearOld()
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
if blockList[Tetrimino.y + i][Tetrimino.x + j] == 1 then
blockList[Tetrimino.y + i][Tetrimino.x + j] = 0
end
end
end
end
2.34 方块的零件三:碰撞检测
检测活动方块和固定方块是否碰撞,来判断活动方块是否可以进行旋转和移动
--碰撞检测
function isCollide(x, y, t)
for i = 1, #t, 1 do
for j = 1, #t[i], 1 do
if t[i][j] == 1 and blockList[y + i][x + j] == 2 then
return true
end
end
end
return false
end
2.5 方块的零件四:更新状态
只要可以移动,我们就需要更新移动后的位置
function updateNEW(x,y)
Tetrimino.x = x
Tetrimino.y = y
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
if shape[i][j]==1 then
blockList[y + i][x + j] = shape[i][j]
end
end
end
end
2.6 方块的死亡
function block_fixed(x,y)
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
if shape[i][j]==1 then
blockList[y + i-1][x + j] = 2
end
end
end
-- 标志一个方块的死亡
Blockstate =false
end
遇到的问题
1️⃣ 在main文件中使用Block文件中的全局变量const时报错
<main.lua>
-- shape = L_tetrimino.shape1 此处是错误地方一,因为还没有require 库Block
function love.load()
Object =require "Classic"
require "Block"
require "const"
--block= Block()
-- 全局变量要写在 load里,我干。可能是因为函数无序性
-- 此处是正确的地方
shape = L_tetrimino.shape1
end
-- shape = L_tetrimino.shape1 此处是错误地方二:因为具有无序性,所以可能还没有require Block 就使用了cons
function love.update(dt)
shape.y=11
Block_drop(shape)
end
function love.draw()
draw_Blcok()
end
2️⃣方块落下的拖尾
问题如图:
问题分析
每隔一秒从L型方块赋值给Blocklist,但是没有对上一秒的状态进行清理,导致Blocklist中的过去状态的位置仍然是1
解决方法
- 修改二维数组:对L型方块的二维数组进行修改,在顶部多加一行空白,这样就实现了每次下落一格时,自动清除上一格的信息。
L_tetrimino = {
{0,0},
{1,0},
{1,0},
{1,1},
x=0,
y=0
}
- 先洗碗后吃饭:既然是过去的状态信息没有清理干净,那我每次都先清理干净再去绘制图像。
function Block_drop(shape)
local x = L_tetrimino.x
local y = L_tetrimino.y
--清理信息
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
blockList[y + i-1][x + j] = 0
end
end
--绘制图像
for i =1, #shape,1 do
for j = 1,#shape[i],1 do
blockList[y + i][x+ j] = shape[i][j]
end
end
end
注意,清理信息和绘制图像两个不能颠倒顺序
同时这个方法在方块左移和右移的时候该如何去处理拖尾呢?
好吧,我承认前面写的有问题,但是基本思路是对的。对于处理左移右移和下移其实是一个方法,我们不过是想在画新状态之前清理旧状态信息,而新旧状态的辨别就是坐标(x,y),我们可以让local a =x ,localb =y
的思路,让a和b去参加运算作为新状态,而x,y做旧状态。我们利用玩x,y清理旧信息后,直接让x=a,y=b
完成状态转换。
通过下述代码我能实现方块左移
function love.keypressed(key)
local x = L_tetrimino.x
local y = L_tetrimino.y
if key == "right" then
x = x+1
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
blockList[L_tetrimino.y+ i][L_tetrimino.x + j] = 0
end
end
L_tetrimino.x=x
L_tetrimino.y=y
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
blockList[y + i][x+ j] = shape[i][j]
end
end
end
end
效果如下:
项目代码
<main.lua>
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
function love.load()
math.randomseed(os.time())
Object =require "Classic"
require "Block"
require "const"
require "Button"
buttonPause = Button("Pause",600,380)
buttonPlay = Button("Play",600,460)
-- 全局变量要写在 load里,我干。可能是因为函数无序性
shape,new_Tetrimino = block_new()
NextShape,next_Tetrimino = block_new()
---*************
end
function love.keypressed(key)
if not IsPause then
if key == "right" then
x = x + 1
elseif key == "left" then
x=x-1
elseif key == "up" then
block_rotate(x,y)
end
if x < 1 then
x = 0
elseif x > 17 then
x = 17
end
--碰撞检测
if not isCollide(x, y, shape) then
--清除信息
clearOld()
updateNEW(x, y)
end
if key=="down" then
y=y+1
if isCollide(x, y, shape) then
--清除信息
block_fixed(x,y)
else
clearOld()
updateNEW(x, y)
end
end
end
end
function love.mousepressed(x,y,button1,istouch,presses)
if button1==1 and buttonPause:isMouseHover() then
IsPause = true
elseif button1==1 and buttonPlay:isMouseHover() then
IsPause = false
end
end
function love.update(dt)
x = Tetrimino.x
y = Tetrimino.y
if not IsPause then
if not Blockstate then
shape = NextShape
new_Tetrimino = next_Tetrimino
NextShape,next_Tetrimino = block_new()
end
block_remove()
timeAcumulator = timeAcumulator + dt
if timeAcumulator>=moveInterval then
y=y+1
timeAcumulator = timeAcumulator - moveInterval
block_drop(x, y)
end
end
end
function love.draw()
love.graphics.setLineWidth(5)
love.graphics.line(562, 0, 562, 600)
-- 预览框
love.graphics.rectangle("line",600,50,180,180)
--按钮
buttonPause:draw()
buttonPlay:draw()
--分数
love.graphics.print("Playersocre:"..Playersocre,600,250)
Preblockdraw()
draw_Blcok()
end
<const.lua>
timeAcumulator = 0
moveInterval = 1
backColor = "#ffffff"
Playersocre = 0
IsPause = false
-- 其实板子可以更大,我画的时候选取一部分画就行
--19*18
blockList = {
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
{ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 },
}
Removerow = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }
newrow = { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
TetriminoName = {"L_tetrimino" ,"O_tetrimino" ,"I_tetrimino","T_tetrimino","Z_tetrimino","N_tetrimino" }
--shape = 4*4
Tetrimino=
{
L_tetrimino =
{
{
{ 0,1, 0 },
{ 0,1, 0},
{ 0,1, 1 }
},
{
{0,0,0},
{ 1, 1 ,1},
{ 1, 0, 0}
},
{
{ 1, 1 ,0},
{ 0, 1 ,0},
{ 0, 1 ,0}
},
{
{ 0, 0, 0 },
{ 0 ,0, 1 },
{ 1, 1, 1 }
}
},
O_tetrimino =
{
{
{ 0, 0, 0 },
{ 1 ,1, 0 },
{ 1, 1, 0}
}
},
I_tetrimino =
{
{
{ 0,1, 0},
{ 0,1, 0},
{ 0,1, 0},
{ 0,1, 0}
},
{
{ 0, 0, 0 },
{ 1, 1 ,1,1}
}
},
T_tetrimino=
{
{
{ 0,1, 0},
{ 1,1, 1}
},
{
{ 0,1, 0},
{ 0,1, 1},
{ 0,1, 0}
},
{ { 0,0, 0},
{ 1,1, 1},
{ 0,1, 0}
},
{
{ 0,1, 0},
{ 1,1, 0},
{ 0,1, 0}
}
},
Z_tetrimino=
{
{
{ 1,1, 0},
{ 0,1, 1}
},
{
{ 0,1},
{ 1,1},
{ 1,0}
}
},
N_tetrimino=
{
{
{ 0,1,1},
{ 1,1,0}
},
{
{ 1,0},
{ 1,1},
{ 0,1}
}
}
}
<Button.lua>
Button = Object:extend()
function Button:new(text,x,y)
self.x=x
self.y=y
self.width =180
self.height = 50
self.text =text
self.hovercolor = "3161A3"
self.textcolor = "#ffffff"
self.font = love.graphics.newFont("UNSII-2.ttf",21)
end
function Button:isMouseHover()
local x,y = love.mouse.getPosition()
return x>self.x and x<self.x+self.width and y>self.y and y <self.y+self.height
end
--600,50,180,50
function Button:draw()
if self:isMouseHover() then
love.graphics.setColor(blockSetcolor(self.hovercolor))
end
love.graphics.rectangle("line",self.x,self.y,self.width,self.height)
--love.graphics.setColor(blockSetcolor(self.textcolor))
--local font = love.graphics.setFont(self.font,21)
love.graphics.setFont(self.font)
local textwidth = self.font:getWidth(self.text)
local textheight = self.font:getHeight(self.text)
love.graphics.print(self.text,self.x+(self.width-textwidth)/2,self.y+(self.height-textheight)/2)
love.graphics.setColor(blockSetcolor(backColor))
end
<Block.lua>
Block = Object:extend()
Blockstate = true
function block_new()
shape_num =1
Blockstate = true
Tetrimino.x=7
Tetrimino.y = 0
local num = math.random(1, 6)
local name = TetriminoName[num]
X_Tetrimino=Tetrimino[name]
return X_Tetrimino[1],X_Tetrimino
end
function blockSetcolor(hex)
hex = hex:gsub("#","")
if string.len(hex)==6 then
local r = tonumber(hex:sub(1, 2), 16) / 255
local g = tonumber(hex:sub(3, 4), 16) / 255
local b = tonumber(hex:sub(5, 6), 16) / 255
return r,g,b
else
error("Invalid hex color format. Use #RRGGBB format.")
end
end
-- 方块旋转
function block_rotate(x,y)
local preshape = nil
shape_num = shape_num % #new_Tetrimino+1
preshape = new_Tetrimino[shape_num]
if not isCollide(x, y, preshape) then
--清除信息
clearOld()
shape = preshape
else
shape_num = shape_num - 1
end
end
--方块自动下落
function block_drop(x,y)
if isCollide(x, y, shape) then
--清除信息
block_fixed(x, y)
else
clearOld()
updateNEW(x, y)
end
end
--碰撞检测
function isCollide(x, y, t)
for i = 1, #t, 1 do
for j = 1, #t[i], 1 do
if t[i][j] == 1 and blockList[y + i][x + j] == 2 then
return true
end
end
end
return false
end
--清除信息
function clearOld()
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
if blockList[Tetrimino.y + i][Tetrimino.x + j] == 1 then
blockList[Tetrimino.y + i][Tetrimino.x + j] = 0
end
end
end
end
--方块死亡
function block_fixed(x,y)
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
if shape[i][j]==1 then
blockList[y + i-1][x + j] = 2
end
end
end
Blockstate =false
end
--更新信息
function updateNEW(x,y)
Tetrimino.x = x
Tetrimino.y = y
for i = 1, #shape, 1 do
for j = 1, #shape[i], 1 do
if shape[i][j]==1 then
blockList[y + i][x + j] = shape[i][j]
end
end
end
end
function Issametable(x,y)
for i=1,#x,1 do
if x[i]~=y[i] then
return false
end
end
return true
end
-- 消除函数
function block_remove()
for i = 18, 19, 1 do
if Issametable(blockList[i],Removerow) then
table.remove(blockList,i)
table.insert(blockList,1,newrow)
Playersocre = Playersocre + 10
end
end
end
function Preblockdraw()
for i = 1, #NextShape, 1 do
for j = 1, #NextShape[i], 1 do
if NextShape[i][j]==1 then
love.graphics.rectangle("fill", 650+(j-1)* 31, 80+(i - 1) * 31, 30, 30)
end
end
end
end
-- 错误想法:想单独弄tetrimino的降落动画,其实只要传递数据,最后根据二维数组,让绘制函数绘制就行
function draw_Blcok()
for i = 1 , 19 ,1 do
for j =1 ,19 ,1 do
if blockList[i][j]==1 then
love.graphics.rectangle("fill", (j - 2) * 31, (i - 1) * 31, 30, 30)
elseif blockList[i][j]==2 then
love.graphics.setColor(blockSetcolor("F94A29"))
love.graphics.rectangle("fill", (j - 2) * 31, (i - 1) * 31, 30, 30)
love.graphics.setColor(blockSetcolor(backColor))
end
end
end
end
<Classic.lua>
自行寻找🙂