Lua实现Unity连连看小游戏摘要

用Lua实现Unity连连看游戏要点记录:

游戏功能描述,通过游戏关卡配置从水果类型中,生成指定类型水果及其数量,位置随机。连线要求最多支持两个拐点,三段线相连通才能消除。

- 创建游戏关卡数据结构

创建关卡水果数据结构 ROFruitData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
---水果连连看水果数据
---@class ROFruitData
local m = {
---@type number 配置ID
cid = nil,
---@type boolean 是否消除
isClean = false,
---@type number 所在位置
pos = 0,
---@type number 行
x = 0,
---@type number 列
y = 0,
}

function m.New(cid,clean)
local o = Clone(m)
o:Init(cid,clean)
return o
end

function m:Init(cid,clean)
self.cid = cid
self.isClean = clean
end

---@param pos number
function m:SetPos(pos)
self.pos = pos
local x,y = self:GetX_Y()
self.x = x
self.y = y
end

---@return CfgRestaurantOperationFruitListData
function m:GetCfgData()
if self.cid > 0 then
return CfgRestaurantOperationFruitListTable[self.cid]
end
return nil
end

---通过Pos获取格子位置 (每行10个格子)
---@return number,number 行,列
function m:GetX_Y()
local x = math.ceil(self.pos / 10)
local y = self.pos - 10 * (x-1)
return x,y
end

return m

创建游戏关卡数据结构Lua文件 ROFruitCleanGameData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
---水果连连看当前关卡游戏数据
---@class ROFruitCleanGameData
local m = {
---@type number 当前关卡ID
cid = nil,
---@type number 关卡开始时间
startTime = 0,
---@type ROFruitData[] 当前关卡水果
fruits = {},
---@type number 正确消除水果数量
rightCount = 0,
}

local ROFruitData = require("IQIGame.Module.CommonActivity.RestaurantOperation.ROFruitCleanGame.ROFruitData")

function m.New(cid)
local o = Clone(m)
o:Init(cid)
return o
end

function m:Init(cid)
self.cid = cid
self.fruits = self:CreateFruits()
self:RandomPosData()
self.startTime = 0
self.rightCount = 0
end

---开启新的关卡,创建新关卡数据
---@return ROFruitData[]
function m:CreateFruits()
local tab = {}
local num = 0
local fruitGroup = table.clone(self:GetCfgData().FruitGroup)
for i = 1, #self:GetCfgData().FruitComposition,2 do
local type = self:GetCfgData().FruitComposition[i]
local typeCount = self:GetCfgData().FruitComposition[i+1]
local typeTab = self:GetRandomElements(fruitGroup,type)
for j, cid in pairs(typeTab) do
for count = 1, typeCount do
num = num + 1
local fruitData = ROFruitData.New(cid,false)
table.insert(tab,fruitData)
end
log("水果连连看 卡组..="..cid.." 数量: "..typeCount.." -- type ="..type)
end
end
local emptyNum = ROFruitCleanGameModule.maxNum - num
---创建空格子
for i = 1, emptyNum do
local fruitData = ROFruitData.New(-1,true)
table.insert(tab,fruitData)
end
return tab
end

---执行洗牌操作 随调整位置
function m:RandomPosData()
for i = #self.fruits, 2, -1 do
local random_index = math.random(i)
self.fruits[i], self.fruits[random_index] = self.fruits[random_index], self.fruits[i]
end
end

---从指定表中随机不重复的元素
---@param randTab number[]
---@param n number
---@return number[]
function m:GetRandomElements(randTab, n)
local randomElements = {}
--- 随机选择n个索引,并将对应的元素添加到新表中
for i = 1, n do
local randomIndex = math.random(1,#randTab)
local value = randTab[randomIndex]
table.remove(randTab, randomIndex)
table.insert(randomElements, value)
end
return randomElements
end

---@return CfgRestaurantOperationFruitCleanControlData
function m:GetCfgData()
return CfgRestaurantOperationFruitCleanControlTable[self.cid]
end

return m

通过调用RandomPosData()方法,执行洗牌操作 随机调整水果位置

- 连线检测

定义直线连接检测函数StraightConnection(startX,startY,endX,endY);
一次转向连接函数OneTurnConnection(startX,startY,endX,endY);
二次转向连接函数TwoTurnConnection(startX,startY,endX,endY)。优先检测直线连通,其次检测一次转向连通,再次检测二次转向连通,如果能连通,返回连通路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
--region 连线检测----------------------------------
---
---找到连通路径
---@param startX number
---@param startY number
---@param endX number
---@param endY number
---@return boolean
---@return ROFruitUIPathData[]
function RestaurantGameConnectUI:FindConnectPath(startX,startY,endX,endY)
---先找直连
local result,path = self:StraightConnection(startX,startY,endX,endY)
if result then
return result,path
end
---一次转向连接检测
result,path = self:OneTurnConnection(startX,startY,endX,endY)
if result then
return result,path
end
---二次转向连接检测
result,path = self:TwoTurnConnection(startX,startY,endX,endY)
if result then
return result,path
end
return false,{}
end

---获取地图行,列数
---@return number,number
function RestaurantGameConnectUI:GetMapSize()
return #self.mapBoard,#self.mapBoard[1]
end

---二次转向连接检测
---@param startX number 开始行
---@param startY number 开始列
---@param endX number 目标行
---@param endY number 目标列
---@return boolean
---@return ROFruitUIPathData []
function RestaurantGameConnectUI:TwoTurnConnection(startX,startY,endX,endY)
---找出能一次连通到目标点的所有空格
local emptyPos = self:GetStraightConnectionPos(endX,endY)
---再从找出的空格能通过一次转向连接起始位置的路径
local result = false
local tempPos
---从起点通过一次转向连接到第二个拐点的路径
local path1 = {}
---从第二个拐点到终点直连的路径
local path2 = {}
for i, v in pairs(emptyPos) do
result,path1 = self:OneTurnConnection(startX,startY,v.x,v.y)
if result then
tempPos = v
break
end
end
---如果找到第二个转点
if tempPos then
result,path2 = self:StraightConnection(endX,endY,tempPos.x,tempPos.y)
end
local tab = {}
if result then
for i = 1, #path1 do
tab[#tab + 1] = path1[i]
end
---加入拐点
tab[#tab + 1] = ROFruitUIPathData.New(tempPos.x,tempPos.y,{})
for i = 1, #path2 do
tab[#tab + 1] = path2[i]
end
end
return result,tab
end

---找出能通过直线连通到指定位置的所有格子
---@param endX number 行
---@param endY number 列
---@return table
function RestaurantGameConnectUI:GetStraightConnectionPos(endX,endY)
local tempMap = {}
local m,n = self:GetMapSize()
---从上,下,左,右四个方向找出能直连到(endX,endY)的空格
for i = endX-1, 1,-1 do
if self.mapBoard[i][endY] > 0 then
break
end
tempMap[#tempMap + 1] = {x = i,y = endY}
end
for i = endX+1, m do
if self.mapBoard[i][endY] > 0 then
break
end
tempMap[#tempMap + 1] = {x = i,y = endY}
end
for i = endY-1, 1,-1 do
if self.mapBoard[endX][i] > 0 then
break
end
tempMap[#tempMap + 1] = {x = endX,y = i}
end
for i = endY+1, n do
if self.mapBoard[endX][i] > 0 then
break
end
tempMap[#tempMap + 1] = {x = endX,y = i}
end
return tempMap
end

---一次转向连接检测
---@param startX number 开始行
---@param startY number 开始列
---@param endX number 目标行
---@param endY number 目标列
---@return boolean
---@return ROFruitUIPathData []
function RestaurantGameConnectUI:OneTurnConnection(startX,startY,endX,endY)
local path = {}
---找出能一次连通到目标点的所有空格
local emptyPos = self:GetStraightConnectionPos(endX,endY)
---再从找出的空格能直连起始位置的路径
local result2 = false
---拐点位置
local tempPos = nil
---从起点到拐点的有效路径
local path2 = {}
for i, v in pairs(emptyPos) do
result2,path2 = self:StraightConnection(startX,startY,v.x,v.y)
if result2 then
tempPos = v
break
end
end
---如果找到拐点
if tempPos then
local result1,path1 = self:StraightConnection(tempPos.x,tempPos.y,endX,endY)
if result1 and result2 then
for i = 1, #path2 do
path[#path + 1] = path2[i]
end
path[#path + 1] = ROFruitUIPathData.New(tempPos.x,tempPos.y,{})
for i = 1, #path1 do
path[#path + 1] = path1[i]
end
return true,path
end
end
return false,{}
end


---直线连接
---@param startX number 开始行
---@param startY number 开始列
---@param endX number 目标行
---@param endY number 目标列
---@return boolean
---@return ROFruitUIPathData[]
function RestaurantGameConnectUI:StraightConnection(startX,startY,endX,endY)
local path = {}
if startX == endX then ---同一行
local minY, maxY = math.min(startY, endY), math.max(startY, endY)
for pathY = minY + 1, maxY - 1 do
if self.mapBoard[startX][pathY] > 0 then
return false,{}
end
local uiPathData = ROFruitUIPathData.New(startX,pathY,{})
table.insert(path,uiPathData)
end
if endY < startY then
table.reverse(path)
end
return true,path
elseif startY == endY then ---同一列
local minX, maxX = math.min(startX, endX), math.max(startX, endX)
for pathX = minX + 1, maxX - 1 do
if self.mapBoard[pathX][startY] > 0 then
return false,{}
end
local uiPathData = ROFruitUIPathData.New(pathX,startY,{})
table.insert(path,uiPathData)
end
if endX < startX then
table.reverse(path)
end
return true,path
end
return false,{}
end

--endregion 连线检测----------------------------------
---

- 播放连线效果

根据找出的连通路径播放对应格子上的连线效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
---播放连线
---@param paths ROFruitUIPathData[] 路径集合
---@param startPos UnityEngine.Vector2
---@param endPos UnityEngine.Vector2
---@return ROFruitUIPathData[],FruitGameItemCell[]
function RestaurantGameConnectUI:PlayLine(paths,startPos,endPos)
local tempPath = {}
local fruitItemCells = {}
local lastPos = startPos
for i = 1, #paths do
local checkPath = paths[i]
local nextPath = paths[i+1]
if nextPath == nil then
nextPath = endPos
end
tempPath[#tempPath + 1] = self:RefreshPathDir(lastPos,checkPath,nextPath)
lastPos = checkPath
end

for i = 1, #tempPath do
local pa = tempPath[i]
local cell = self:GetFruitCell(pa.x,pa.y)
cell:ShowLine(pa.dir)
table.insert(fruitItemCells,cell)
end
return tempPath,fruitItemCells
end

---@param lastPos
---@param pathData ROFruitUIPathData
---@param nextPos
function RestaurantGameConnectUI:RefreshPathDir(lastPos,pathData,nextPos)
local newPas = ROFruitUIPathData.New(pathData.x,pathData.y,{})
local dir1,dir2
local dirTab = {}
---方向1
if pathData.x > lastPos.x then
dir1 = 1
elseif pathData.x < lastPos.x then
dir1 = 2
end
if pathData.y > lastPos.y then
dir1 = 3
elseif pathData.y < lastPos.y then
dir1 = 4
end
dirTab[#dirTab + 1] = dir1
---方向2
if pathData.x > nextPos.x then
dir2 = 1
elseif pathData.x < nextPos.x then
dir2 = 2
end
if pathData.y > nextPos.y then
dir2 = 3
elseif pathData.y < nextPos.y then
dir2 = 4
end
dirTab[#dirTab + 1] = dir2
newPas.dir =dirTab
return newPas
end