用Lua实现Unity一笔画小游戏要点记录:
游戏玩法简介
选择不同难度关卡开始游戏,每一关有多个方块小格,可以从任意一个方块格作为起点开始连接到下一个方块格,下一个方块格必须与起始格上下左右相邻。同一个方块格不能重复连接,成功连接后,目标方块格又成为新的起始方块格。以此类推直至将所有方块格连完。
功能实现简述
关卡难度
关卡难度选择,不同关卡制作不同prefab预设。prefab预设摆放所有要连接的格子位置信息节点。选择关卡开始游戏时,在每一个位置信息节点下生成一个游戏连接格实例对象。创建方格数据结构 AmusementParkOneConnectBlockData 和 视图显示控件AmusementParkOneConnectBlockCell
AmusementParkOneConnectBlockData
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
---@class AmusementParkOneConnectBlockData
local m = {
---@type number
Index = 0,
---@type number
X = 0,
---@type number
Y = 0,
---@type number 0 未连接,1 已连接
State = 0,
---@type boolean 最新连通
IsFirst = false,
}
function m.New(index,x,y)
local o = Clone(m)
o:Init(index,x,y)
return o
end
function m:Init(index,x,y)
self.Index = index
self.X = tonumber(x)
self.Y = tonumber(y)
end
return m
AmusementParkOneConnectBlockCell
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
---@class AmusementParkOneConnectBlockCell
local m = {
View = nil,
---@type AmusementParkOneConnectBlockData
BlockData = nil,
---@type Vector3
StartPos = nil,
---@type function
OnBeginDragCallBack = nil,
---@type function
OnDragCallBack = nil,
---@type UnityEngine.Collider2D
BlockCollider = nil,
---@type UnityEngine.Collider2D
DrgCollider = nil,
}
function m.New(view)
local obj = Clone(m)
obj:Init(view)
return obj
end
function m:Init(view)
self.View = view
LuaCodeInterface.BindOutlet(self.View, self)
self.DelegateBtnSelf = function() self:OnClickBtnSelf() end
self.BlockCollider = self.View:GetComponent(typeof(UnityEngine.Collider2D))
self.DrgCollider = self.ImgDrag:GetComponent(typeof(UnityEngine.Collider2D))
self.StartPos = self.ImgDrag.transform.position
self.ImgDrag:GetComponent("UIDrag").onBeginDrag = function(eventData) self:OnBeginDrag(eventData) end
self.ImgDrag:GetComponent("UIDrag").onDrag = function(pointerEventData) self:OnDrag(pointerEventData) end
self.ImgDrag:GetComponent("UIDrag").onReachTargetSuccess = function(eventData) self:OnDragSuccess(eventData) end
self.ImgDrag:GetComponent("UIDrag").onReachTargetFailure = function(eventData) self:OnFailure(eventData) end
self:AddListener()
end
function m:AddListener()
self.View:GetComponent("Button").onClick:AddListener(self.DelegateBtnSelf)
end
function m:RemoveListener()
self.View:GetComponent("Button").onClick:RemoveListener(self.DelegateBtnSelf)
end
function m:OnClickBtnSelf()
if self.OnBeginDragCallBack then
self.OnBeginDragCallBack(self)
end
end
function m:OnBeginDrag()
if self.OnBeginDragCallBack then
self.OnBeginDragCallBack(self)
end
end
function m:OnDrag(eventData)
if self.OnDragCallBack then
self.OnDragCallBack(self)
end
end
function m:OnDragSuccess(eventData)
self.ImgDrag.transform.position = self.StartPos
end
function m:OnFailure(eventData)
self.ImgDrag.transform.position = self.StartPos
end
function m:UpdateView()
self.ImgDefault:SetActive(self.BlockData.State == 0)
self.ImgCheck:SetActive(self.BlockData.State == 1)
self.ImgTag:SetActive(self.BlockData.IsFirst)
end
---@param blockData AmusementParkOneConnectBlockData
function m:SetData(blockData)
self.BlockData = blockData
self.View.name = self.BlockData.Index
self:UpdateView()
end
function m:Dispose()
AssetUtil.UnloadAsset(self)
self:RemoveListener()
self.OnBeginDragCallBack = nil
self.OnDragCallBack = nil
LuaCodeInterface.ClearOutlet(self.View, self)
UnityEngine.Object.Destroy(self.View)
self.View = nil
end
return m
连接功能实现
方格连接有两种连接方式,点击目标格或者从起始格位置在屏幕上向目标格连续拖动。
AmusementParkOneConnectBlockCell 控件包含 Button 组件,且子节点”ImgDrag”包含 UIDrag 组件 和 Collider2D,分别实现点击,和拖动操作。当拖动ImgDrag节点时,通过Collider2D检测,有没有接接到其它方块格上。
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
---@class AmusementParkGameConnectUI
local AmusementParkGameConnectUI = {
---@type UnityEngine.Canvas
UICanvas = nil,
---@type AmusementParkOneConnectBlockCell[]
OneConnectBlockCells = {},
}
AmusementParkGameConnectUI = Base:Extend("AmusementParkGameConnectUI", "IQIGame.Onigao.UI.AmusementParkGameConnectUI", AmusementParkGameConnectUI)
local AmusementParkOneConnectBlockData = require("IQIGame.Module.CommonActivity.AmusementPark.AmusementParkOneConnectGame.AmusementParkOneConnectBlockData")
local AmusementParkOneConnectBlockCell = require("IQIGame.UI.ExploreHall.AmusementPark.AmusementParkOneConnectGame.AmusementParkOneConnectBlockCell")
---界面初始化。
---@return void
function AmusementParkGameConnectUI:OnInit()
self.UICanvas = self.UIController:GetComponent(typeof(UnityEngine.Canvas))
self.DelegateOnClickBtnClose = function() self:OnClickBtnClose() end
self.DelegateOnClickBtnRest = function() self:OnClickBtnRest() end
self.DelegateOnClickBtnChooseLevel = function() self:OnClickBtnChooseLevel() end
self.DelegateUpdateOneConnectGame = function() self:OnUpdateOneConnectGame() end
self.DelegateOnGameEnd = function() self:OnGameEnd() end
end
---OnInit预加载资源路径列表。
---@return table
function AmusementParkGameConnectUI:GetPreloadAssetPaths()
return nil
end
---OnOpen预加载资源路径列表。
---@return table
function AmusementParkGameConnectUI:GetOpenPreloadAssetPaths(userData)
return nil
end
---OnOpen时调用,判断当前UI是否在卡顿结束后手动显示
---@return boolean
function AmusementParkGameConnectUI:IsManualShowOnOpen(userData)
return false
end
---UI是否有独立的背景音乐,如果有则返回,否则返回nil,注意一个UI同时只能有一个背景音乐,如果需要切换,调用self.UIController:ChangeBGM()方法
---@return number @SoundCid
function AmusementParkGameConnectUI:GetBGM(userData)
return nil
end
---界面打开。
---@param userData table
---@return void
function AmusementParkGameConnectUI:OnOpen(userData)
AmusementParkModule.SetAmusementParkSceneActive(false)
self:CreateGameMap()
self:UpdateView()
end
---界面关闭。
---@param userData table
---@return void
function AmusementParkGameConnectUI:OnClose(userData)
AmusementParkModule.SetAmusementParkSceneActive(true)
end
---添加侦听。
---@return void
function AmusementParkGameConnectUI:OnAddListeners()
self.BtnClose:GetComponent("Button").onClick:AddListener(self.DelegateOnClickBtnClose)
self.BtnRest:GetComponent("Button").onClick:AddListener(self.DelegateOnClickBtnRest)
self.BtnChooseLevel:GetComponent("Button").onClick:AddListener(self.DelegateOnClickBtnChooseLevel)
self.ResultView:GetComponent("Button").onClick:AddListener(self.DelegateOnGameEnd)
EventDispatcher.AddEventListener(EventID.APUpdateOneConnectGameEvent,self.DelegateUpdateOneConnectGame)
end
---移除侦听。
---@return void
function AmusementParkGameConnectUI:OnRemoveListeners()
self.BtnClose:GetComponent("Button").onClick:RemoveListener(self.DelegateOnClickBtnClose)
self.BtnRest:GetComponent("Button").onClick:RemoveListener(self.DelegateOnClickBtnRest)
self.BtnChooseLevel:GetComponent("Button").onClick:RemoveListener(self.DelegateOnClickBtnChooseLevel)
self.ResultView:GetComponent("Button").onClick:RemoveListener(self.DelegateOnGameEnd)
EventDispatcher.RemoveEventListener(EventID.APUpdateOneConnectGameEvent,self.DelegateUpdateOneConnectGame)
end
---界面暂停。
---@return void
function AmusementParkGameConnectUI:OnPause()
end
---界面暂停恢复。
---@return void
function AmusementParkGameConnectUI:OnResume()
end
---界面遮挡。
---@return void
function AmusementParkGameConnectUI:OnCover()
end
---界面遮挡恢复。
---@return void
function AmusementParkGameConnectUI:OnReveal()
end
---界面激活。
---@param userData table
---@return void
function AmusementParkGameConnectUI:OnRefocus(userData)
end
---界面轮询。
---@param elapseSeconds number
---@param realElapseSeconds number
---@return void
function AmusementParkGameConnectUI:OnUpdate(elapseSeconds, realElapseSeconds)
end
---界面深度改变。
---@param uiGroupDepth number
---@param depthInUIGroup number
---@return void
function AmusementParkGameConnectUI:OnDepthChanged(uiGroupDepth, depthInUIGroup)
end
---加载资源成功
---该方法委托类型为底层框架类型
---@param assetName string
---@param asset table
---@param duration number
---@param userData table
---@return void
function AmusementParkGameConnectUI:OnLoadSucceed(assetName, asset, duration, userData)
local type = userData.type
if type == "LoadMap" then
self:CleanBlocks()
AmusementParkOneConnectGameModule.oneConnectGameData.LevelData:RestLevelBlock()
local mapObj = UnityEngine.Object.Instantiate(asset)
mapObj.transform:SetParent(self.RootLevel.transform, false)
for i = 0, mapObj.transform.childCount - 1 do
local node = mapObj.transform:GetChild(i)
local posTab = string.split(node.gameObject.name,"_")
local blockObj = UnityEngine.Object.Instantiate(self.BlockMould)
blockObj:SetActive(true)
blockObj.transform:SetParent(node,false)
local blockData = AmusementParkOneConnectBlockData.New(i+1,posTab[1],posTab[2])
---@type AmusementParkOneConnectBlockCell
local blockCell = AmusementParkOneConnectBlockCell.New(blockObj)
blockCell.OnBeginDragCallBack = function(cell) self:StartDrag(cell) end
blockCell.OnDragCallBack = function(cell) self:OnDrag(cell) end
blockCell:SetData(blockData)
table.insert(self.OneConnectBlockCells,blockCell)
AmusementParkOneConnectGameModule.oneConnectGameData.LevelData:Create(blockData)
end
end
end
---加载资源失败
---该方法委托类型为底层框架类型
---@param assetName string
---@param status number
---@param errorMessage string
---@param userData table
---@return void
function AmusementParkGameConnectUI:OnLoadFailed(assetName, status, errorMessage, userData)
end
---销毁
function AmusementParkGameConnectUI:OnDestroy()
AssetUtil.UnloadAsset(self)
self:CleanBlocks()
if self.oneConnectGameMapView then
self.oneConnectGameMapView:Dispose()
end
self.oneConnectGameMapView = nil
end
function AmusementParkGameConnectUI:OnClickBtnClose()
UIModule.CloseSelf(self)
end
function AmusementParkGameConnectUI:RefreshMap()
for i, v in pairs(self.OneConnectBlockCells) do
v:UpdateView()
end
local result = AmusementParkOneConnectGameModule.oneConnectGameData:CheckComplete()
if result then
AmusementParkOneConnectGameModule.OneStroke(AmusementParkOneConnectGameModule.oneConnectGameData.LevelData.cid)
self.ResultView:SetActive(true)
end
end
---开始连格子
---@param cell AmusementParkOneConnectBlockCell
function AmusementParkGameConnectUI:StartDrag(cell)
local blockData = cell.BlockData
local result = AmusementParkOneConnectGameModule.oneConnectGameData:CheckIsConnect(blockData)
if result then
AmusementParkOneConnectGameModule.oneConnectGameData:AddBlockData(blockData)
self:RefreshMap()
end
end
---拖动方块位置连续连接
---@param cell AmusementParkOneConnectBlockCell
function AmusementParkGameConnectUI:OnDrag(cell)
local top,blockData = self:GetBlockOccupiedAreas(cell)
if top then
local result = AmusementParkOneConnectGameModule.oneConnectGameData:CheckIsConnect(blockData)
if result then
AmusementParkOneConnectGameModule.oneConnectGameData:AddBlockData(blockData)
self:RefreshMap()
end
end
end
---检测两个格子是否有交集
---@return boolean
---@param collider2D1 UnityEngine.Collider2D 初拖动的格子
---@param collider2D2 UnityEngine.Collider2D 要检测的目标格
function AmusementParkGameConnectUI:CheckBoundsIntersects(collider2D1,collider2D2)
return collider2D1.bounds:Intersects(collider2D2.bounds)
end
---返回方块格经过的格子
---@param cell AmusementParkOneConnectBlockCell
---@return boolean,AmusementParkOneConnectBlockData
function AmusementParkGameConnectUI:GetBlockOccupiedAreas(cell)
local res = false
for i, v in pairs(self.OneConnectBlockCells) do
if v.BlockData.Index ~= cell.BlockData.Index then
res = self:CheckBoundsIntersects(cell.DrgCollider,v.BlockCollider)
if res then
return true,v.BlockData
end
end
end
return false,nil
end
function AmusementParkGameConnectUI:OnUpdateOneConnectGame()
self:ResetGame()
end
function AmusementParkGameConnectUI:ResetGame()
self.ResultView:SetActive(false)
AmusementParkOneConnectGameModule.oneConnectGameData:RestGame()
self:CreateGameMap()
self:UpdateView()
end
function AmusementParkGameConnectUI:UpdateView()
UGUIUtil.SetText(self.TextLevel,AmusementParkOneConnectGameModule.oneConnectGameData.LevelData:GetCfgData().Level)
end
---创建游戏
function AmusementParkGameConnectUI:CreateGameMap()
local levelMap = AmusementParkOneConnectGameModule.oneConnectGameData.LevelData:GetCfgData().GamePrefab
local path = UIGlobalApi.UIPath..levelMap
AssetUtil.LoadAsset(self,path,self.OnLoadSucceed,nil,{type = "LoadMap"})
end
function AmusementParkGameConnectUI:CleanBlocks()
for i, v in pairs(self.OneConnectBlockCells) do
v:Dispose()
end
self.OneConnectBlockCells = {}
end
function AmusementParkGameConnectUI:OnClickBtnRest()
self:ResetGame()
end
---点击关闭结算界面自动开启下一关
function AmusementParkGameConnectUI:OnGameEnd()
self.ResultView:SetActive(false)
AmusementParkOneConnectGameModule.CreateNewGame()
self:CreateGameMap()
self:UpdateView()
end
function AmusementParkGameConnectUI:OnClickBtnChooseLevel()
UIModule.Open(Constant.UIControllerName.AmusementParkGameSelectLevelUI,Constant.UILayer.UI)
end
return AmusementParkGameConnectUI