ssRender+Lua开发小游戏<俄罗斯方块>(一)

引言

本篇文章是本人使用ssRender引擎,配合使用Lua脚本语言,开发一个俄罗斯方块小游戏的案例。本篇文章将会详细介绍开发初期的构想、前端界面的布局、各个按钮的功能和部分俄罗斯方块功能的实现(包括界面渲染、方块初始化、左移、右移等功能)

一:界面准备

本篇幅主要介绍了通过ssRender引擎,制作一个简易的前端游戏界面

(一)资源导入

如图,导入背景方格资源,设置按钮,旋转、左移、右移、下落按钮的图片,按照如图布局

(二)方格设置

为了易于实现渲染函数的编写,且此游戏的方格场景有限,所以使用ssRender引擎对应方格数量设置多个Item节点(如图,该场景Size为20x10)。

按照如图所示的结构与命名规则,设置行列Item。设置Color为红色,visible显隐属性均为1。

如图,这是刚进入场景时的显示样式,在通过Lua脚本与ssRender封装的Lua接口对界面上的各个小方块进行显隐的控制,就能够实现俄罗斯方块的显示,移动,消除等效果!坐稳了,要发车了!

二:设计想法

本篇幅主要介绍:将Lua语言与游戏各个元素相对应的设计想法

(一)方块设计

如图所示,俄罗斯方块一共有七中形状,分别为I型、T型、L型、反L型、S型、Z型以及O型。其他的形状均是由这其七种类型旋转变化所得!

所以可以先设计这七种类型的方块,再使用适当的旋转方法,即可得到全部形状的俄罗斯方块!

又发现,I型的方块在横向摆放时占用横向四格、竖向摆放时占用竖向四格,所以我们只需一个4X4的二维数组(在Lua中称为二维表)即可将所有的方块摆放进去!

--I型方块
Block_I_1 = {
    {0,1,0,0},
    {0,1,0,0},
    {0,1,0,0},
    {0,1,0,0}
}
--T型方块
Block_T_1 = {
    {0,0,0,0},
    {0,0,0,0},
    {1,1,1,0},
    {0,1,0,0}
}
--L型方块
Block_L_1 = {
    {0,0,0,0},
    {0,1,0,0},
    {0,1,0,0},
    {0,1,1,0}
}
--反L型方块
Block_BL_1 = {
    {0,0,0,0},
    {0,1,0,0},
    {0,1,0,0},
    {1,1,0,0}
}
--S型方块
Block_S_1 = {
    {0,0,0,0},
    {0,0,0,0},
    {0,1,1,0},
    {1,1,0,0}
}
--Z型方块
Block_Z_1 = {
    {0,0,0,0},
    {0,0,0,0},
    {1,1,0,0},
    {0,1,1,0}
}
--O型方块
Block_O_1 = {
    {0,0,0,0},
    {0,0,0,0},
    {0,1,1,0},
    {0,1,1,0}
}

(二)方块旋转设计

方案1:找方块的中心点进行旋转

可以根据基本形状所对应的二维表,根据不同方块存放对应的中心点坐标值(注意,Lua与C++的起始位置不同,C++数组从0开始,Lua表从1开始);再通过中心点交换方块的二维表内的值实现方块旋转的效果。

虽然感觉很好,占用空间的情况也很优秀,但是在随机出现不同方格时,28种方格(7种方块x4种旋转角度)相同概率出现较难实现,并且在计算不同方块的旋转算法时为了避免出现BUG,要写不同的算法,情况很复杂。

所以,本案例使用的是简单粗暴的方案二

方案2:枚举全部的方块情况

同方块设计中七种不同的二维表,将不同旋转角度的方块全部使用二维表枚举出来!

此方案虽然在空间上要多申请21个4x4的二维表,但是不用再繁琐的设计旋转算法,简单而又粗暴!

Block_T_0 = {
    {0,0,0,0},
    {0,1,0,0},
    {0,1,1,0},
    {0,1,0,0}
}
Block_T_1 = {
    {0,0,0,0},
    {0,0,0,0},
    {1,1,1,0},
    {0,1,0,0}
}
Block_T_2 = {
    {0,0,0,0},
    {0,1,0,0},
    {1,1,0,0},
    {0,1,0,0}
}
Block_T_3 = {
    {0,0,0,0},
    {0,0,0,0},
    {0,1,0,0},
    {1,1,1,0}
}

以T型方块为例,大家可以试着实现其他方块的所有旋转情况。

注意:为了防止出现如下情况:旋转之后,间隔行数变大(如下方的I_1与I_2GiveUp所示)。这种“极限续命”的情况,我们统一把方块的最低点放置在4x4二维表的最后一行!(即将下面的I_2GiveUp统一改成I_2形式)

Block_I_1 = {
    {0,1,0,0},
    {0,1,0,0},
    {0,1,0,0},
    {0,1,0,0}
}
Block_I_2GiveUp = {
    {0,0,0,0},
    {1,1,1,1},
    {0,0,0,0},
    {0,0,0,0}
}
Block_I_2 = {
    {0,0,0,0},
    {0,0,0,0},
    {0,0,0,0},
    {1,1,1,1}
}

之后,把七种方块对应的不同旋转情况均放置到一个表中,方便我们通过随机数算法,平均概率的取到每一种方块;同时我们也要按照一定规律对方块表进行排列,以实现索引+1为顺时针旋转90度,索引-1为逆时针旋转90度的效果:

BlockList={ --方块表
    [1] = {Block_I_0,Block_I_1,Block_I_2,Block_I_3},--3,2,1,0为逆时针;0123为顺时针
    [2] = {Block_T_0,Block_T_1,Block_T_2,Block_T_3},
    [3] = {Block_L_0,Block_L_1,Block_L_2,Block_L_3},
    [4] = {Block_BL_0,Block_BL_1,Block_BL_2,Block_BL_3},
    [5] = {Block_S_0,Block_S_1,Block_S_2,Block_S_3},
    [6] = {Block_Z_0,Block_Z_1,Block_Z_2,Block_Z_3},
    [7] = {Block_O_0,Block_O_1,Block_O_2,Block_O_3}

}

(三)场景刷新设计

如前端界面所显示,该场景Size为20行x10列,方块分为当前正在掉落的方块和落在下方物块的固定不动的方格。

如果当前方块每降落一行,就要把整个场景全部渲染一遍,就太粗暴、系统开销会上升很多!

所以产生了如下设计想法:

我们可以将固定不动的方块划分为一部分(称为bg部分),把当前正在掉落的方块划分为一部分(称为CurrentBlock部分),方块每次掉落的时候,只需要去渲染方格所在的4x4区域即可。

当方格不能在降落,即落到地面上之后(称为触底),再将方格添加到bg部分,执行一次渲染bg部分的操作,就能极大提高我们场景的刷新效率。bg初始化方法如下:

bg={}

function InitBG()
    for i=1,20 do
        bg[i] = {}
        for j=1,10 do
            bg[i][j] = 0
        end
    end
end

那么问题来了,我们如何确定当前方块(CurrentBlock)在整体bg上所在的位置呢?

只需要两个参数!那就是当前方块(CurrentBlock)所在的行(CurrentRow)与所在的列(CurrentColumn);同时为了方便记录方块形状、实现旋转效果,也要记录当前方块在方块表中的位置索引BlockListID(在方块表的位置,即行数),subID(形状表的位置,即方块表的列数),如下:

currentBlock = {
   ['blockListID'] = 1,  --在方块表的位置 I T L BL S Z O
   ['subID'] = 1,        --形状表的位置 旋转变化0 1 2 3
   ['curRow'] = 1,       --block[1][1]处 在主背景的行数
   ['curColumn'] = 1,    --block[1][1]处  在主背景的列数
   ['block'] = {}        -- 方块现在具体的形状{{},{},{},{}}形式
}

需要注意方块所在Bg的行数与列数是:方块二维表[1, 1]所在的位置,即此方格所在整体BG上所在的位置。

这样,我们每秒钟调用一次方块下落函数+渲染当前方格函数;当方块触底之后,我们再调用当前方块插入Bg函数 + 渲染整体Bg函数,就能够轻松实现分组刷新的效果,显著提升效率!

三:功能实现

本篇幅将会详细介绍渲染功能,方块随机生成功能,方块左侧、右侧墙壁检测以及方块左移,右移功能的详细实现

(一)渲染功能实现

1.BG渲染

Lua代码如下:

--根据bg表 渲染 整体的BackGround
function RenderBG()
    local setPropertyTable = {}
    for i = 1, #bg do
    for j = 1, #bg[1] do
        setPropertyTable["Layer0//Item0//Row"..i.."//Item"..j.."//Visible"] = ""..bg[i][j]..""
    end
    end
	ssr.setNodeProperty(setPropertyTable)
end

bg是行数20x列数10的二维表,#bg用于获取bg的行数,#bg[1]用于获取列数(即bg第一行的元素个数)。

还记得第一节中的规范的命名规则吗?就是为了在此处更方便的将前端节点与bg行列所对应;其中的ssr.setNodeProperty()方法则是ssRender引擎封装好的接口函数,根据表的索引值获取对应节点的visible属性,通过表的值来设置属性值!

其中..是Lua自带的字符串拼接的符号,由于ssr.setNodeProperty()方法所识别的表中参数需要是string字符串类型,所以我们要把bg对应的int型参数拼接为字符串(""..bg[i][j].."")才能正确渲染!

2.当前方块渲染

Lua代码如下:

function RenderCurrentBlock()
    local setPropertyTable  = {}
    local block = currentBlock['block']
    for i = 1, #block do
        for j = 1, #block[1] do
            local row = currentBlock['curRow'] + i - 1
            local cloumn = currentBlock['curColumn'] + j -1
            if block[i][j] == 0 and bg[row][cloumn] == 0 then
                setPropertyTable["Layer0//Item0//Row"..row.."//Item"..cloumn.."//Visible"] = "0"
            else
                setPropertyTable["Layer0//Item0//Row"..row.."//Item"..cloumn.."//Visible"] = "1"
                

            end
        end
    end     

    ssr.setNodeProperty(setPropertyTable)
end

已知currentBlock['block']存储的是当前方块的具体形状,是一个4x4的二维表,从上到下,从左到右的遍历当前方块的表,由于Lua的索引值是从1开始,所以currentBlock['curRow'] + i - 1就是正在遍历到的方格在整体bg所在的相对行数;currentBlock['curColumn'] + j -1就是正在遍历到的方格在整体bg所在的相对列数。

当相对位置处的bg方格为白色(不显示,即bg[row][cloumn] == 0),以及当前方块的当前方格也为白色(此处无方块,即block[i][j] == 0)才让此方格不显示,即visible = 0(都无方格则无显示);除此之外,无论是bg的对应方格处有方块,还是当前方块的当前方格有显示,均将visible 设置为1(任一有方格则显示)。

3.取消当前方块的渲染

Lua代码如下:

function CancelCurrentBlock()
    local setPropertyTable  = {}
    local block = currentBlock['block']
    for i = 1, #block do
        for j = 1, #block[1] do
            local row = currentBlock['curRow'] + i - 1
            local cloumn = currentBlock['curColumn'] + j -1
            if bg[row][cloumn] == 0 then
                setPropertyTable["Layer0//Item0//Row"..row.."//Item"..cloumn.."//Visible"] = "0"
            else
                setPropertyTable["Layer0//Item0//Row"..row.."//Item"..cloumn.."//Visible"] = "1"
            end
        end
    end
    ssr.setNodeProperty(setPropertyTable)
end

此处逻辑与渲染当前方块的逻辑一致。

为什么要写一个当前方块取消渲染的函数呢?

因为,我们左移、右移、下落的时候,为了性能考虑,没有从新渲染整体的bg!所以,在不取消当前渲染的情况下,去渲染下一个状态的方块,就会有显示上的bug(比如是方块的残留)!

所以我们在调用左移、右移、下落函数之前,先取消当前方块的渲染,能否移动的条件以及移动前后的状态有移动函数控制(左移就是当前方块所在列数 - 1,右移 + 1,下落即当前行数+1),然后再从新渲染当前方块。在降低效率的同时,也能避免BUG的出现!

(二)方块随机生成功能实现

1.方块二维表的深拷贝

由于Lua对二维表之间的赋值操作,与C++对数组的赋值操作一致,是基于引用的(也就是地址拷贝而不是值拷贝)。这意味着当你将一个表赋值给另一个变量时,你实际上是在复制这个表的引用,而不是表的内容。当更改二者之一时,另一个表也会被更改!

为了防止对当前方块的方块表currentBlock['block']操作,而改变总方块表BlockList中的元素,我们就需要写一个值拷贝的克隆函数:

function Clone( src, dest )
    for k,v in pairs(src) do
        if type(v) == 'table' then
            dest[k] = {}
            Clone(v, dest[k])
        else
            dest[k] = v
        end
    end
end

如上,是表的值拷贝的标准递归函数,将src表的值赋给dest表。

2.方块随机生成

Lua代码如下:

function CreatNewBlock()
    local current_time = os.time()  
    math.randomseed(current_time)  -- 只设置一次种子,之后调用math.random()将生成不同的随机数  

    currentBlock['blockListID'] = math.random(1,8)
    currentBlock['subID'] = math.random(1,5)
    currentBlock['curRow'] = 1  --待更改
    currentBlock['curColumn'] = 4
    --currentBlock['block'] = BlockList[currentBlock['blockListID']][currentBlock['subID']]
    Clone(BlockList[currentBlock['blockListID']][currentBlock['subID']], currentBlock['block'])

end

设置随机数种种子;

math.random(1, 8) -- 生成一个1到8之间的随机整数(包括1,不包括8),取得当前方块的形状;

math.random(1, 5) -- 生成一个1到5之间的随机整数(包括1,不包括5),取得当前方块的旋转形状;

Clone()方法,获取当前方块的具体形状。

方块生成功能测试(ssRender Edit工具端内):

1.首先先创建一个绿色的按钮

2.再新建一个Lua脚本,命名为creatBlock,双击打开

调用生成函数和渲染方块的函数

3.将绿色按钮的click事件触发调用creatBlock脚本

4.点击按钮,检查效果

效果如图所示,达到预期,我们继续往下来写左移与右移的方法!

(三)方块左移右移功能实现

1.左侧墙体检测

在方块向左移动时,要对方块左侧墙体进行预检测,假设移动完成后的方块,与现有的墙体以及固定的方格有无碰撞;有碰撞则返回true,无碰撞则返回false

function CheckLeftWall()
    local block = currentBlock['block']
    local row = currentBlock['curRow'] 
    local column = currentBlock['curColumn']
    
   
    --对应方块的正左侧有墙面 则返回true 从下到上,从左到右
    for i = #block , 1 , -1 do
        for j = 1, #block[1] do
            if block[i][j] == 1 then

                 --所在列数 == 1的时候,碰到墙壁,不可左移,左侧墙为true
                if column + j - 1 <= 1 then
                    return true
                end

                --行是当前行,列是前一列,
                if bg[row + i-1][column +j -2] == 1 then
                    return true
                end
            end
        end
    end

    return false
                    


end

解析:由于俄罗斯方块从下方到上方逐渐堆积,现在也要实现向左侧移动,所以从下到上,从左到右依次遍历当前方格;如果小方格所在的列数<= 1,则说明已经靠近左侧墙壁,不能再向左移动,返回false;比较bg上对应方格的当前行,前一列有无物块阻拦,如果有则不能向左移动返回true;

2.向左移动

function LeftMove()
    if CheckLeftWall() then
        return
    else
        currentBlock['curColumn'] = currentBlock['curColumn'] - 1
    end

end

首先,左侧墙壁预检测,如果有墙壁,函数执行结束;若没有墙壁,则将当前方块所在的列数减一;

右侧墙体检测右移功能类比左移即可,代码部分很相似,只需注意二维表的遍历顺序改为从下到上,从右到左;以及最大列数为10即可!

左移功能测试(ssRender Edit工具端内):

1.创建leftMove的Lua脚本

2.双击,打开脚本,在脚本内调用上方函数

3.左移动按钮新增click事件调用leftMove脚本

4.点击按钮检查效果

效果如图所示,我们能够控制方块的左移与右移啦!

小结:

本篇文章是本人在使用ssRenderEdit工具中的Lua脚本相关内容来制作俄罗斯方块的创作记录,本篇主要实现了渲染功能、创建方块功能、左移右移功能!下一篇文章将会主要实现方块下落功能、方块旋转功能和满行消除功能的实现!若大家感兴趣,还请持续关注哦!

  • 46
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值