'Unity3D/Project'에 해당되는 글 36건
- 2018.03.14 [Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리2
- 2018.03.09 [Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리. 2
- 2018.02.24 [Unity3D] 헥사게임 만들기 세 번째 - 블럭처리2. 2
- 2018.02.18 [Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리.
- 2018.02.13 [Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2
- 2018.02.13 [Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기.
- 2018.02.11 [Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정.
- 2017.04.24 [Unity3D] 인벤토리 [Part 4] - Load 3
- 2017.04.23 [Unity3D] 인벤토리 [Part 4] - Save 3
- 2017.04.13 [Unity3D] 인벤토리 [Part 3] - Item Drag & Swap 5
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리2
※ 소스 출처 : 네이버 카페 - 유니티 허브 - 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
3). 선택된 블럭을 마우스가 이동한 방향에 있는 블럭과 교환(이동)시키는 함수 만들기.
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
|
private float fMoveSpeed = 5f; // 블럭의 이동 속력.
private float fBlockMoveStep = 0f;
// 마우스로 블럭이동 처리.
void DragToMoveBlock()
{
if (!bBlockChange)
return;
if (TargetBlock.transform.position != StartPos1)
{
fBlockMoveStep += fMoveSpeed * Time.deltaTime;
SelectBlock.transform.position = Vector3.MoveTowards(StartPos1, EndPos1, fBlockMoveStep);
TargetBlock.transform.position = Vector3.MoveTowards(StartPos2, EndPos2, fBlockMoveStep);
}
else
{
// 이동 완료
bBlockChange = false;
fBlockMoveStep = 0f;
// 블럭 교환.
SwitchBoard();
// 블럭 매치 시작.
StartCoroutine(BlockCheck());
}
}
|
cs |
별거 없습니다.
DragToMoveBlock()함수는
bBlockChange 플래그가 활성화(true)되지 않으면 수행하지 않고 곧 바로 종료해 버립니다.
10라인의 if문은
전 글의 밑의 이미지를 보셨다면 대충 감이 오실겁니다.
TargetBlock은 마우스가 이동한 방향에 있던 블럭 객체 입니다.
그림으로 보자면
본 이미지의 파란색 동그라미에 존재하는 블럭이 TargetBlock이라고 생각하면 됩니다.
단순한 이미지로 표현하자면
노란색 박스는 현재 마우스로 선택한 박스고
초록색 박스는 현재 마우스로 드래그 방향에 있던 박스입니다.
이 초록색 박스가 TargetBlock입니다.
전 글부터 게속 똑같은 내용을 강조하는 이유는
헷갈리면 안돼는 부분이기 때문입니다.
교환이 시작되면 위 이미지를 기준으로
노란색 박스는 초록색 박스의 위치로
초록색 박스는 노란색 박스의 위치로 이동해야 합니다.
10라인의 if문에서 StartPos1는 전 글을 잘보셨던 분이시라면 자신이 선택한 블럭
즉, 노란색 박스의 위치라는걸 아실 겁니다.
각설하고 10라인 if문을 주석을 달아보자면
'초록색 박스의 위치가 노란색 박스의 위치와 같지 않으면'. 정도가 되겠습니다.
'초록색 박스의 위치가 노란색 박스의 위치와 같을 때까지' 라고 생각해도 좋습니다.
여기서 한 가지 의문을 느낄수도 있는데,
초록색 박스가 노란색 박스의 위치로 움직여야 한다면
노란색 박스도 초록색 박스의 위치로 움직여야 하는 구문이 있어야 하는거 아닌가?
라는 의문을 가질 수 있습니다.
하지만 13라인, 14라인을 보면 그런 의문은 지우실 수 있을 겁니다.
두 블럭의 움직이는 타이밍과 이동 속력이 완전히 똑같기 때문에
한 블럭의 도착만을 체크하면 됩니다.
그러니까 10라인은 if(SelectBlock.transform.position != EndPos1) 으로도 대체 할 수 있습니다.
'초록색 박스의 위치가 노란색 박스의 위치와 같을 때' 10라인의 if문은 거짓으 되므로
16라인부터 시작하는 else문이 수행됩니다.
19라인에 bBlockChange플래그를 false로 바꿔주어 DragToMoveBlock()함수가 수행되지 않게 바꿔주고
20라인에 fBlockMoveStep을 0으로 초기화 해줍니다.
이 값을 0으로 초기화 해주지 않으면 블럭을 교환 시킬때 마다 블럭의 이동 속력이 증가되는 것을 볼 수 있을 것입니다.
23라인의 새로운 함수가 나왔군요.
이 함수의 내용도 별거 없습니다.
블럭의 위치가 서로 교환되었다면,
그 블럭이 가지고 있는 위치정보의 갱신이 이루어 져야 합니다.
쉽게 말해서 노란색 박스가 초록색 박스의 위치로 이동하면
노란색 박스가 가지고 있던 위치 정보인 ( 3, 3 )이 ( 4, 3 ) 으로 바뀌어야 한다는 것입니다.
초록색 박스도 마찬가지겠죠?
이 위치 정보는 블럭이 가지고 있는 Block스크립트가 가지고 있습니다.
그리고 한 가지 더
위치 정보 뿐만이 아니라 타입도 서로 바꿔워야 합니다.
만약 바꿔주지 않는다면 똑같은 모양의 블럭이 3개 이상 모여도 있어도 매치가 되지않는 기이한 현상을 목격할 수 있을 겁니다.
4). 선택된 블럭과 이동 방향에 존재하는 블럭의 위치 교환 및 타입교환하는 함수 만들기.
아무튼 코드로 보자면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 블럭의 위치가 바뀌었으므로 보드에서의 블럭의 셋팅을 교환 해준다.
void SwitchBoard()
{
Block sBlock = SelectBlock.GetComponent<Block>();
sBlock.iX = (int)EndPos1.x;
sBlock.iY = (int)EndPos1.y;
sBlock = TargetBlock.GetComponent<Block>();
sBlock.iX = (int)EndPos2.x;
sBlock.iY = (int)EndPos2.y;
// 타입 스왑
int Tmptype = BlockBoard[(int)StartPos1.x][(int)StartPos1.y];
BlockBoard[(int)StartPos1.x][(int)StartPos1.y] = BlockBoard[(int)EndPos1.x][(int)EndPos1.y];
BlockBoard[(int)EndPos1.x][(int)EndPos1.y] = Tmptype;
}
|
cs |
이정도가 되겠군요.
단순한 대입과 스왑이므로 따로 설명은 하지 않겠습니다.
다시 3). 선택된 블럭을 마우스가 이동한 방향에 있는 블럭과 교환(이동)시키는 함수 만들기. 으로 돌아와서.
한 번의 블럭 교환이 이루어 졌으므로
26라인에 보이는것처럼 BlockCheck() 함수를 수행합니다.
그리고 BlockCheck() 함수로 들어가 새로운 코드를 추가합시다.
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
|
IEnumerator BlockCheck()
{
// 가로체크.
// 세로체크.
// 매치된 블럭이 없으면.
if (!NoMatch())
{
Debug.Log("매치된게 없음.");
if (bReCheck)
{
bReCheck = false;
bDrag = true;
}
else
{
Vector3 TmpStartPos = StartPos1;
StartPos1 = StartPos2;
StartPos2 = TmpStartPos;
Vector3 TmpEndPos = EndPos1;
EndPos1 = EndPos2;
EndPos2 = TmpEndPos;
// 블럭 원위치.
bBlockReChange = true;
}
}
// 매치된 블럭이 있다면.
else
{
yield return new WaitForSeconds(0.2f);
Debug.Log("매치된게 있음.");
SelectBlock = null;
TargetBlock = null;
BlockDelete();
}
}
|
cs |
7라인의 if문은 매치된 블럭이 없을 때고,
31라인의 else문은 매치된 블럭이 있을 때 입니다.
11라인의 bReCheck 플래그의 활용 용도는 뒤에서 설명하겠습니다.
아무튼 이 플래그를 기준으로
13 ~ 14라인 : 다시 드래그가 가능하게 해준다.
18 ~ 27라인 : 다시 블럭 교환을 시도하기 위한 위치 값 스왑이 이루어 지고 bBlockReChange플래그 값을 변환 시킨다.
※ bBlockReChange플래그 값이 true가 되면 Update() 구문에서 실제로 블럭교환을 수행한다.
else문 안에 36라인, 37라인 부분을 추가시켜 줍니다.
블럭 교환 시 매치된 블럭이 존재하기 때문에
SelectBlock이나 TargetBlock은 객체를 게속 가지고 있을 필요가 없어졌습니다.
그리고 20라인의 BlockDelete() 함수가 수행되고,
1
2
3
4
5
6
7
8
9
|
// 매치된 블럭 삭제하기.
void BlockDelete()
{
// 매치된 블럭이 생겨 빈 공간이 생겼는지 판단하는 플래그.
// 빈 공간이 생겼을 경우.
if (bMatch)
BlockDown();
}
|
cs |
다시 8라인의 구문이 수행되면서
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private bool bReCheck = false;
// 블럭을 내려가게 셋팅한다.
// * 한 번 호출에 한 칸씩만 내려간다.
void BlockDown()
{
bool bBlockDown = false;
// 신경쓸 필요 없는 구문임.
// 빈 공간이 있다면 true
if (bBlockDown)
bBlockMoveDown = true;
else
{
bReCheck = true;
StartCoroutine(BlockCheck());
}
}
|
cs |
1라인에 보이는것 처럼 bReCheck 플래그가 추가되고
14라인에 빈 공간이 없을 때인 else문 안 16라인에 true로 초기화 됩니다.
bReCheck 플래그는 언제 필요하냐면
1. 마우스로 블럭 교환 시도.
2. 블럭 교환결과 매치된 블럭이 있음.
3. 매치된 블럭들이 삭제 됨.
4. 빈 공간에 다른 블럭들이 떨어짐.
5. 다시 매치 시작.
6. 매치된 블럭이 없음
=> 드래그가 가능하게 bDrag 플래그를 true로 바꿔줌
이 때 StartPos1, EndPos1, StartPos2, EndPos2 값의 스왑은 이루어 지지 않게 한다.
(왜냐하면 마우스 드래그로 인한 매치함수가 수행된게 아니기 때문이다.)
즉, 어떤 기준으로 갈라지게 되는거냐면 마우스 드래그로 인해 BlockCheck()함수가 수행되게 된다면
bReCheck플래그는 false가 되어 블럭간의 위차 값 스왑을 하게 되는 것이고,
매치된 블럭이 삭제되어 빈 공간을 채우게 됐을 때 다시 BlockCheck()함수를 수행하게 된다면
bReCheck플래그는 true가 되어 블럭을 다시금 드래그 할 수 있게 해주는 것이다.
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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
enum BLOCK
{
BLANK = -1,
}
public class GameManager : MonoBehaviour {
public GameObject OriginBlock; // 블럭의 원본.
public Sprite[] BlockType; // 블럭 이미지 배열.
public int iBlockX, iBlockY; // 블럭보드의 가로 세로 크기.
public int[][] BlockBoard; // 블럭보드.
private bool bBlockMoveDown = false; // 블럭을 움직여주게 해주는 플래그.
private bool bBlockChange = false; // 블럭과 블럭을 바꾼다.
private bool bBlockReChange = false; // 블럭과 블럭이 바뀐 상태에서 다시 바꿔준다.
private bool bReCheck = false;
private Vector3 MouseStartPos; // 자신이 첫 번째로 선택한 블럭의 위치.
private Vector3 MouseEndPos; // 현재 드래그 중인 마우스의 위치.
private Vector3 MouseOffset; // 첫 번째로 선택한 블럭의 위치로부터 현재 마우스 위치 까지의 거리(?)
private Vector3 StartPos1, StartPos2;
private Vector3 EndPos1, EndPos2;
public GameObject SelectBlock; // 자신이 첫 번째로 선택한 블럭.
public GameObject TargetBlock;
private float fMouseMoveDis = 30f; // 첫 번째 블럭의 위치로부터 현재 마우스 까지의 허용 거리.
private float fMoveSpeed = 5f; // 블럭의 이동 속력.
private float fBlockMoveStep = 0f;
private bool bDrag = true; // 드래그 가능, 불가능 플래그.
void Awake()
{
BlockBoard = new int[iBlockX][];
for (int i = 0; i < iBlockX; i++)
BlockBoard[i] = new int[iBlockY];
CreateBlock();
}
void Update()
{
// 블럭을 삭제 후 움직이는지 체크.
BlockDeleteToMoveCheck();
// 마우스 클릭 처리.
MouseClick();
// 마우스로 블럭 이동.
DragToMoveBlock();
// 블럭을 다시 바꿔준다.
ReChangeBlock();
}
// 블럭이 이동중인지 체크.
void BlockDeleteToMoveCheck()
{
// 블럭 삭제 후 블럭이 이동중인지 체크.
if (bBlockMoveDown)
{
// 블럭이 움직인다: true
// 블럭이 움직이지 않는다 : false
bool bBlockMoveEnd = false;
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block in blocks)
{
// 움직이는 블럭이 있는지 bDown변수를 체크해여 확인.
// 조건 : 움직이는 블럭이 있다면.
if (block.GetComponent<Block>().bDown)
{
// 움직이는 블럭이 아직 있으므로 bBlockMoveEnd을 true로 변환.
bBlockMoveEnd = true;
break;
}
}
// 블럭이 움직이지 않는다면.
if (!bBlockMoveEnd)
{
bBlockMoveDown = false;
BlockDown();
}
}
}
// 마우스 클릭 이동.
void MouseClick()
{
// 마우스 왼쪽버튼 클릭.
if (Input.GetMouseButtonDown(0))
{
RaycastHit Hit;
MouseStartPos = Input.mousePosition; // 마우스의 현재 위치.
Ray ray = Camera.main.ScreenPointToRay(MouseStartPos); // 스크린에서의 마우스 위치를 레이로 변환.
// 레이발사.
if (Physics.Raycast(ray, out Hit, Mathf.Infinity))
{
// 레이에 맞은 오브젝트의 태그가 BLOCK이라면.
if (bDrag && Hit.collider.CompareTag("BLOCK"))
{
// 맞은 블럭의 오브젝트를 SelectBlock에 초기화.
SelectBlock = Hit.collider.gameObject;
StartPos1 = SelectBlock.transform.position;
}
}
}
// 마우스 드래그.
if (Input.GetMouseButton(0))
{
MouseEndPos = Input.mousePosition;
MouseOffset = MouseStartPos - MouseEndPos;
// 드래그가 활성화 중이고 선택한 블럭이 비어있지 않으면.
if (bDrag && SelectBlock != null)
{
// 왼쪽
if (MouseOffset.x > fMouseMoveDis)
{
if (SelectBlock.transform.position.x > 0)
MouseDirection(-1, 0);
}
// 오른쪽
if (MouseOffset.x < -fMouseMoveDis)
{
if (SelectBlock.transform.position.x < iBlockX - 1)
MouseDirection(1, 0);
}
// 위
if (MouseOffset.y < -fMouseMoveDis)
{
if (SelectBlock.transform.position.y < iBlockY - 1)
MouseDirection(0, 1);
}
// 아래
if (MouseOffset.y > fMouseMoveDis)
{
if (SelectBlock.transform.position.y > 0)
MouseDirection(0, -1);
}
}
}
}
// 마우스 방향에 따른 블럭 가져오기.
void MouseDirection(float _x, float _y)
{
EndPos1 = new Vector3(StartPos1.x + _x, StartPos1.y + _y, 0);
bDrag = false;
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block in blocks)
{
Block sBlock = block.GetComponent<Block>();
if (sBlock.iX == EndPos1.x && sBlock.iY == EndPos1.y)
TargetBlock = block;
}
StartPos2 = EndPos1;
EndPos2 = StartPos1;
bBlockChange = true;
}
// 마우스로 블럭이동 처리.
void DragToMoveBlock()
{
// 교환 플래그가 활성화 중이 아니라면 실행하지 않음.
if (!bBlockChange || TargetBlock == null)
return;
if (TargetBlock.transform.position != StartPos1)
{
fBlockMoveStep += fMoveSpeed * Time.deltaTime;
SelectBlock.transform.position = Vector3.MoveTowards(StartPos1, EndPos1, fBlockMoveStep);
TargetBlock.transform.position = Vector3.MoveTowards(StartPos2, EndPos2, fBlockMoveStep);
}
else
{
// 이동 완료
bBlockChange = false;
fBlockMoveStep = 0f;
// 블럭 교환.
SwitchBoard();
// 블럭 매치 시작.
StartCoroutine(BlockCheck());
}
}
// 블럭의 위치가 바뀌었으므로 보드에서의 블럭의 셋팅을 교환 해준다.
void SwitchBoard()
{
Block sBlock = SelectBlock.GetComponent<Block>();
sBlock.iX = (int)EndPos1.x;
sBlock.iY = (int)EndPos1.y;
sBlock = TargetBlock.GetComponent<Block>();
sBlock.iX = (int)EndPos2.x;
sBlock.iY = (int)EndPos2.y;
int Tmptype = BlockBoard[(int)StartPos1.x][(int)StartPos1.y];
BlockBoard[(int)StartPos1.x][(int)StartPos1.y] = BlockBoard[(int)EndPos1.x][(int)EndPos1.y];
BlockBoard[(int)EndPos1.x][(int)EndPos1.y] = Tmptype;
}
// 블럭과 블럭을 다시 교환해준다.
void ReChangeBlock()
{
if (bBlockReChange && TargetBlock)
{
if (TargetBlock.transform.position != EndPos2)
{
fBlockMoveStep += fMoveSpeed * Time.deltaTime;
SelectBlock.transform.position = Vector3.MoveTowards(StartPos1, EndPos1, fBlockMoveStep);
TargetBlock.transform.position = Vector3.MoveTowards(StartPos2, EndPos2, fBlockMoveStep);
}
else
{
fBlockMoveStep = 0f;
bBlockReChange = false;
SwitchBoard();
SelectBlock = null;
TargetBlock = null;
bDrag = true;
}
}
}
// 블럭을 만들어주는 함수.
void CreateBlock()
{
for (int y = 0; y < iBlockY; y++)
{
for (int x = 0; x < iBlockX; x++)
{
GameObject block = Instantiate(OriginBlock, new Vector3(x, y, 0), Quaternion.identity);
block.transform.SetParent(transform);
int iType = Random.Range(0, BlockType.Length);
Block sBlock = block.GetComponent<Block>();
sBlock.iX = x;
sBlock.iY = y;
sBlock.iType = iType;
sBlock.SetBlockImg(BlockType[iType]);
BlockBoard[x][y] = iType;
}
}
StartCoroutine(BlockCheck());
}
IEnumerator BlockCheck()
{
// 가로체크.
for (int y = 0; y < iBlockY; y++)
{
for (int x = 0; x < iBlockX - 2; x++)
{
if (BlockBoard[x][y] == BlockBoard[x + 1][y])
{
if (BlockBoard[x][y] == BlockBoard[x + 2][y])
{
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block in blocks)
{
Block sBlock = block.GetComponent<Block>();
if (sBlock.iY != y)
continue;
if (sBlock.iX == x && sBlock.iY == y)
{
sBlock.bDead = true;
continue;
}
if (sBlock.iX == x + 1 && sBlock.iY == y)
{
sBlock.bDead = true;
continue;
}
if (sBlock.iX == x + 2 && sBlock.iY == y)
sBlock.bDead = true;
}
}
}
}
}
// 세로체크.
for (int x = 0; x < iBlockX; x++)
{
for (int y = 0; y < iBlockY - 2; y++)
{
if (BlockBoard[x][y] == BlockBoard[x][y + 1])
{
if (BlockBoard[x][y] == BlockBoard[x][y + 2])
{
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block in blocks)
{
Block sBlock = block.GetComponent<Block>();
if (sBlock.iX != x)
continue;
if (sBlock.iX == x && sBlock.iY == y)
{
sBlock.bDead = true;
continue;
}
if (sBlock.iX == x && sBlock.iY == y + 1)
{
sBlock.bDead = true;
continue;
}
if (sBlock.iX == x && sBlock.iY == y + 2)
sBlock.bDead = true;
}
}
}
}
}
// 매치된 블럭이 없으면.
if (!NoMatch())
{
Debug.Log("매치된게 없음.");
if (bReCheck)
{
bReCheck = false;
bDrag = true;
}
else
{
Vector3 TmpStartPos = StartPos1;
StartPos1 = StartPos2;
StartPos2 = TmpStartPos;
Vector3 TmpEndPos = EndPos1;
EndPos1 = EndPos2;
EndPos2 = TmpEndPos;
// 블럭 원위치.
bBlockReChange = true;
}
}
// 매치된 블럭이 있다면.
else
{
yield return new WaitForSeconds(0.2f);
Debug.Log("매치된게 있음.");
SelectBlock = null;
TargetBlock = null;
BlockDelete();
}
}
// 블럭중 매치된 블럭이 있는지.
bool NoMatch()
{
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block in blocks)
{
if (block.GetComponent<Block>().bDead)
return true;
}
return false;
}
// 매치된 블럭 삭제하기.
void BlockDelete()
{
// 매치된 블럭이 생겨 빈 공간이 생겼는지 판단하는 플래그.
bool bMatch = false;
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block in blocks)
{
Block sBlock = block.GetComponent<Block>();
if (sBlock.bDead)
{
// 삭제된 블럭 칸을 -1로.
BlockBoard[sBlock.iX][sBlock.iY] = (int)BLOCK.BLANK;
Destroy(block);
bMatch = true;
}
}
// 빈 공간이 생겼을 경우.
if (bMatch)
BlockDown();
}
// 블럭을 내려가게 셋팅한다.
// * 한 번 호출에 한 칸씩만 내려간다.
void BlockDown()
{
bool bBlockDown = false;
for (int x = 0; x < iBlockX; x++)
{
bool BlankCheck = false;
for (int y = iBlockY - 1; y > -1; y--)
{
// x,y의 위치에 블럭이 비어있다면.
if (!BlankCheck && BlockBoard[x][y] == (int)BLOCK.BLANK)
{
BlankCheck = true;
bBlockDown = true;
//--------- (x, iBlockY) 위치에 블럭 생성.-------------
GameObject block = Instantiate(OriginBlock, new Vector3(x, iBlockY, 0), Quaternion.identity);
block.transform.SetParent(transform);
int iType = Random.Range(0,BlockType.Length);
Block sBlock = block.GetComponent<Block>();
sBlock.iX = x;
sBlock.iY = iBlockY;
sBlock.iType = iType;
sBlock.SetBlockImg(BlockType[iType]);
//-----------------------------------------------------
// 블럭을 내리기 위한 위치 셋팅.
for (int z = y + 1; z < iBlockY+1; z++)
{
GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK");
foreach (GameObject block2 in blocks)
{
sBlock = block2.GetComponent<Block>();
if (sBlock.iX == x && sBlock.iY == z)
{
sBlock.StartPos = new Vector3(x, z, 0);
sBlock.TartgetPos = new Vector3(x, z - 1, 0);
sBlock.bDown = true;
break;
}
}
}
}
}
}
if (bBlockDown)
bBlockMoveDown = true;
else
{
bReCheck = true;
StartCoroutine(BlockCheck());
}
}
}
|
cs |
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리. (2) | 2018.03.09 |
---|---|
[Unity3D] 헥사게임 만들기 세 번째 - 블럭처리2. (2) | 2018.02.24 |
[Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리. (0) | 2018.02.18 |
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2 (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리.
※ 소스 출처 : 네이버 카페 - 유니티 허브 - 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
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 | private Vector3 MouseStartPos; // 자신이 첫 번째로 선택한 블럭의 위치. private Vector3 StartPos1; private GameObject SelectBlock; // 자신이 첫 번째로 선택한 블럭. // 마우스 클릭처리. void MouseClick() { // 마우스 왼쪽버튼 클릭. if (Input.GetMouseButtonDown(0)) { RaycastHit Hit; MouseStartPos = Input.mousePosition; Ray ray = Camera.main.ScreenPointToRay(MouseStartPos); // 스크린에서의 마우스 위치를 레이로 변환. // 레이발사. if (Physics.Raycast(ray, out Hit, Mathf.Infinity)) { // 레이에 맞은 오브젝트의 태그가 BLOCK이라면. if (Hit.collider.CompareTag("BLOCK")) { // 맞은 블럭의 오브젝트를 SelectBlock에 초기화. SelectBlock = Hit.collider.gameObject; StartPos1 = SelectBlock.transform.position; } } } } | cs |
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 | private Vector3 MouseStartPos; // 자신이 첫 번째로 선택한 블럭의 위치. private Vector3 MouseEndPos; // 현재 드래그 중인 마우스의 위치. private Vector3 MouseOffset; // 첫 번째로 선택한 블럭의 위치로부터 현재 마우스 위치 까지의 거리(?) private Vector3 StartPos1; private GameObject SelectBlock; // 자신이 첫 번째로 선택한 블럭. private float fMouseMoveDis = 30f; // 첫 번째 블럭의 위치로부터 현재 마우스 까지의 허용 거리. private bool bDrag = true; // 드래그 가능, 불가능 플래그. // 마우스 클릭처리. void MouseClick() { // 마우스 왼쪽버튼 클릭. if (Input.GetMouseButtonDown(0)) { RaycastHit Hit; MouseStartPos = Input.mousePosition; Ray ray = Camera.main.ScreenPointToRay(MouseStartPos); // 스크린에서의 마우스 위치를 레이로 변환. // 레이발사. if (Physics.Raycast(ray, out Hit, Mathf.Infinity)) { // 레이에 맞은 오브젝트의 태그가 BLOCK이라면. if (bDrag && Hit.collider.CompareTag("BLOCK")) { // 맞은 블럭의 오브젝트를 SelectBlock에 초기화. SelectBlock = Hit.collider.gameObject; StartPos1 = SelectBlock.transform.position; } } } // 마우스 드래그. if (Input.GetMouseButton(0)) { MouseEndPos = Input.mousePosition; MouseOffset = MouseStartPos - MouseEndPos; // 드래그가 활성화 중이고 선택한 블럭이 비어있지 않으면. if (bDrag && SelectBlock != null) { // 왼쪽 if (MouseOffset.x > fMouseMoveDis) { if (SelectBlock.transform.position.x > 0) MouseDirection(-1, 0); } // 오른쪽 if (MouseOffset.x < -fMouseMoveDis) { if (SelectBlock.transform.position.x < iBlockX - 1) MouseDirection(1, 0); } // 위 if (MouseOffset.y < -fMouseMoveDis) { if (SelectBlock.transform.position.y < iBlockY - 1) MouseDirection(0, 1); } // 아래 if (MouseOffset.y > fMouseMoveDis) { if (SelectBlock.transform.position.y > 0) MouseDirection(0, -1); } } } } | cs |
빨간색의 네모난 박스가 현재 선택한 블럭입니다. MouseStartPos에 해당하죠.
그 옆에 파란색 동그라미는 현재 마우스의 위치 입니다. MouseEndPos에 해당합니다.
오른쪽 방향에 가까운지 if (MouseOffset.x > fMouseMoveDis)
왼쪽 방향에 가까운지 if (MouseOffset.x < -fMouseMoveDis)
위 방향에 가까운지 if (MouseOffset.y < -fMouseMoveDis)
아래 방향에 가까운지 if (MouseOffset.y > fMouseMoveDis)
비교하게 됩니다.
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 | // 드래그가 활성화 중이고 선택한 블럭이 비어있지 않으면. if (bDrag && SelectBlock != null) { // 왼쪽 if (MouseOffset.x > fMouseMoveDis) { if (SelectBlock.transform.position.x > 0) MouseDirection(-1, 0); } // 오른쪽 if (MouseOffset.x < -fMouseMoveDis) { if (SelectBlock.transform.position.x < iBlockX - 1) MouseDirection(1, 0); } // 위 if (MouseOffset.y < -fMouseMoveDis) { if (SelectBlock.transform.position.y < iBlockY - 1) MouseDirection(0, 1); } // 아래 if (MouseOffset.y > fMouseMoveDis) { if (SelectBlock.transform.position.y > 0) MouseDirection(0, -1); } } | cs |
한 가지 확실히 알아둬야 될것이 있는데 월드상의 거리와
스크린 상의 거리를 헷갈리면 안된다는 것입니다.
월드상의 거리로 했을 때 블럭과 블럭의 거리는 1입니다.
하지만 fMouseMoveDis은 30이죠. 이것은 스크린상의 거리로, 픽셀단위 거리라고 생각하시면 됩니다.
맨 처음 스크린을 구성했을 때 셋팅했던 가로 720 세로 1280은 스크린의 크기 입니다.
빨간색 원의 반 지름이 fMouseMoveDis입니다.
즉, 빨간색 원은 허용 범위가 되는 것이죠.
빨간색 원을 넘지 않으면 방향 확정은 일어나지 않습니다.
2). 마우스 방향에 따른 블럭 가져오는 함수 만들기.
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 | EndPos1 = new Vector3(StartPos1.x + _x, StartPos1.y + _y, 0); private bool bBlockChange = false; // 블럭과 블럭을 바꾼다. private Vector3 MouseStartPos; // 자신이 첫 번째로 선택한 블럭의 위치. private Vector3 MouseEndPos; // 현재 드래그 중인 마우스의 위치. private Vector3 MouseOffset; // 첫 번째로 선택한 블럭의 위치로부터 현재 마우스 위치 까지의 거리(?) private Vector3 StartPos1, StartPos2; private Vector3 EndPos1, EndPos2; private GameObject SelectBlock; // 자신이 첫 번째로 선택한 블럭. private GameObject TargetBlock; // 교환할 블럭. private float fMouseMoveDis = 30f; // 첫 번째 블럭의 위치로부터 현재 마우스 까지의 허용 거리. private bool bDrag = true; // 드래그 가능, 불가능 플래그. // 마우스 방향에 따른 블럭 가져오기. void MouseDirection(float _x, float _y) { EndPos1 = new Vector3(StartPos1.x + _x, StartPos1.y + _y, 0); bDrag = false; GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK"); foreach (GameObject block in blocks) { Block sBlock = block.GetComponent<Block>(); if (sBlock.iX == EndPos1.x && sBlock.iY == EndPos1.y) TargetBlock = block; } StartPos2 = EndPos1; EndPos2 = StartPos1; bBlockChange = true; } | cs |
별거 없습니다.
이 함수의 매개 변수는 두 개가 있는데, 이 두개의 매개 변수는 처음에 선택된 블럭을 기준으로
특정 방향에 있는 블럭을 가져오는데 사용됩니다.
20라인에 보면 StartPos1이 사용되는데,
위 내용을 자세히 읽어봤다면 알겠지만 처음 선택된 블럭의 위치가 StartPos1에 들어가 있습니다.
이 값의 x값 또는 y값을 더해주어 특정 방향에 있는 블럭의 위치를 가져올 수 있게 됩니다.
※ 매개변수
(x, y)
(1, 0) 오른쪽
(-1, 0) 왼쪽
(0, 1) 위
(0, -1) 아래
그 다음 22라인에 보면
bDrag값을 false로 바꿔주는데, 이 값을 false로 바꿔주지 않으면 MouseDirection(x,y)함수가
게속 수행되면서 TargetBlock의 값이 게속 바뀌게 됩니다.
그렇게 되면 처음 선택된 블럭과 교환해줄 다음 블럭이 게속 바뀌게 되면서 꼬이게 됩니다.
그냥 단순하게 생각해서 교환시킬 블럭을 구했으니 드래그를 못하게 하겠다 라고 생각하시면 됩니다.
위치 값(EndPos1)을 알았으니 24라인에 있는 구문처럼 BLOCK태그를 가진 모든 오브젝트(블럭 오브젝트)를 가져와서
위치 값을 검사하여 EndPos1과 같은 위치에 있는 블럭을 가져오면 됩니다.
=> TargetBlock = block;
다음 33, 34라인은 그림을 봅시다.
블럭이 교환 됐을 때 같은 타입의 블럭이 3개가 있다면 문제 없겠지만,
없을 때는 다시 교환해주어야 합니다. 그걸 위한 StartPos2, EndPos2 변수를 선언하여 초기화 했습니다.
다음 36라인의 bool변수인 bBlockChange을 true로 바꿔주면서
블럭과 블럭의 교환 수행을 허락해 주면 됩니다.
GameManager 스크립트의 여태까지 한 부분에 대한 풀소스 입니다.
글이 길어지니 끊고 다음 게시물로 이어가겠습니다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리2 (0) | 2018.03.14 |
---|---|
[Unity3D] 헥사게임 만들기 세 번째 - 블럭처리2. (2) | 2018.02.24 |
[Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리. (0) | 2018.02.18 |
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2 (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 세 번째 - 블럭처리2.
※ 소스 출처 : 네이버 카페 - 유니티 허브 - 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
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 | // 매치된 블럭 삭제하기. void BlockDelete() { // 매치된 블럭이 생겨 빈 공간이 생겼는지 판단하는 플래그. bool bMatch = false; GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK"); foreach (GameObject block in blocks) { Block sBlock = block.GetComponent<Block>(); if (sBlock.bDead) { // 삭제된 블럭 칸의 을 -1로. BlockBoard[sBlock.iX][sBlock.iY] = (int)BLOCK.BLANK; Destroy(block); bMatch = true; } } // 빈 공간이 생겼을 경우. if (bMatch) BlockDown(); } | cs |
블럭삭제 함수안에 bool 변수인 bMatch가 새로 추가되었습니다.
이 변수의 초기값은 false로 하는 일은 삭제된 블럭이 하나라도 있는가 판단하는 일입니다.
bMatch가 true가 된다면 삭제한 블럭이 있다는 것이며, 삭제된 블럭이 있던 공간은 빈 공간이 됩니다.
빈 공간이 생겼을 경우 블럭은 떨어져야 합니다.
즉, 변수 bMatch는 BlockDown()함수를 호출 할 것인지 하지 않을것인지 판단하는 재료가 됩니다.
4). 떨어지는 블럭 함수 만들기.
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 | void BlockDown() { bool bBlockDown = false; for (int x = 0; x < iBlockX; x++) { bool BlankCheck = false; for (int y = iBlockY - 1; y > -1; y--) { // x,y의 위치에 블럭이 비어있다면. if (!BlankCheck && BlockBoard[x][y] == (int)BLOCK.BLANK) { BlankCheck = true; bBlockDown = true; //--------- (x, iBlockY) 위치에 블럭 생성.------------- GameObject block = Instantiate(OriginBlock, new Vector3(x, iBlockY, 0), Quaternion.identity); block.transform.SetParent(transform); int iType = Random.Range(0,BlockType.Length); Block sBlock = block.GetComponent<Block>(); sBlock.iX = x; sBlock.iY = iBlockY; sBlock.iType = iType; sBlock.SetBlockImg(BlockType[iType]); //----------------------------------------------------- // 블럭을 내리기 위한 위치 셋팅. for (int z = y + 1; z < iBlockY+1; z++) { GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK"); foreach (GameObject block2 in blocks) { sBlock = block2.GetComponent<Block>(); if (sBlock.iX == x && sBlock.iY == z) { sBlock.StartPos = new Vector3(x, z, 0); sBlock.TartgetPos = new Vector3(x, z - 1, 0); sBlock.bDown = true; break; } } } } } } if (bBlockDown) bBlockMoveDown = true; else StartCoroutine(BlockCheck()); } | cs |
1 | bool bBlockDown = false; | cs |
bool 변수은 bBlockDown변수가 하는 일은 블럭이 떨어져야 하느냐 판단하는 일입니다.
즉, 이 변수가 true가 된다면 모종의 셋팅이 된 블럭들을 떨어뜨리는 겁니다.
1 2 3 4 5 6 7 | for (int x = 0; x < iBlockX; x++) { bool BlankCheck = false; for (int y = iBlockY - 1; y > -1; y--) { } } | cs |
2중 for문은 블럭보드를 검사하는 구문입니다.
첫 번째 for문은 x값으로 열에 해당됩니다.
2번째 for문은 행에 해당됩니다.
초기값으로 iBlockY - 1인 이유는 밑에서 위로 검사하는 것이 아닌 위에서 아래 순서로 검사하기 위해서 입니다.
※ iBlockY변수는 블럭보드의 높이(블럭의 세로 개수) 입니다.
iBlockY 변수에 - 1을 한 이유는 제로(0)베이스 이기 때문입니다.
이렇게 높이를 1씩 감소시키면서 빈 공간을 찾아 한 줄을 검사 합니다.
그림으로 보자면 다음과 같은 줄만을 검사 합니다.
x값이 1 증가한다면 밑과 같이 검사 하겠군요.
첫 번째 for문과 두 번째 for문 사이에 있는 bool 변수 BlankCheck는 현재 검사하고 있는 줄에서
빈 공간이 있을 때 블럭을 한 번만 만들어 주기 위한 플래그 입니다.
검사하고 있는 줄에서 빈 칸이 2개 이상일 때, 이 플래그가 없다면 빈칸의 숫자만큼 한 번에 블럭을 만들어 주게 됩니다.
블럭의 위치를 이용하고 있는 상황에서 같은 위치에 한 번에 여러개의 블럭을 만든다는 것은 좋지 않은 일입니다.
1. 빈칸을 찾으면 하나의 블럭을 만든다.
2. 블럭들을 움직인다.
3. 다시 빈칸을 찾는다.
즉, 함수 한 번 호출당 한 줄, 한 빈칸에 블럭을 하나씩 생성해주고 한 칸씩만 이동한다고 생각하시면 됩니다.
다음 빈 칸을 채우는 것은 다음 함수가 호출될 때 입니다.
1 2 3 | if (!BlankCheck && BlockBoard[x][y] == (int)BLOCK.BLANK) { } | cs |
조건문입니다. 현재 BlockBoard[x][y] == 빈칸이냐?
라는 뜻이군요.
이 조건을 클리어 하면 2개의 플래그를 모두 true로 바꿔줍니다.
1 2 | BlankCheck = true; bBlockDown = true; | cs |
설명은 bool값 선언 부분에서 말 한것이 전부 입니다.
1 2 3 4 5 6 7 8 9 10 | GameObject block = Instantiate(OriginBlock, new Vector3(x, iBlockY, 0), Quaternion.identity); block.transform.SetParent(transform); int iType = Random.Range(0,BlockType.Length); Block sBlock = block.GetComponent<Block>(); sBlock.iX = x; sBlock.iY = iBlockY; sBlock.iType = iType; sBlock.SetBlockImg(BlockType[iType]); | cs |
빈 칸에 따른 새로운 블럭 생성 구문 입니다.
Instantiate(원본 프리팹, 위치, 회전상태) 함수로 새로운 블럭을 만들어 줍니다.
* new Vector3(현재 검사하고 있는 열, 열의 높이, 0)
블럭을 새로 생성시켰기 때문에 그 블록이 가진 정보들 여러가지를 초기화 시켜야 합니다.
1. 현재 블럭의 위치 값.
2. 현재 블럭의 타입.
3. 현재 블럭의 타입에 따른 이미지.
등을 초기화 시킵니다.
블럭을 새롭게 만들어 정보를 셋팅 시켰으니 다음은 블럭을 떨어뜨릴 차례입니다.
1 | for (int z = y + 1; z < iBlockY+1; z++) | cs |
이 구문의 뜻은 대략 이렇습니다.
z = y + 1
현재 y의 위치는 빈 칸입니다.
빈칸의 + 1칸은 빈 칸의 바로 윗 칸이 되겠군요.
빈 칸의 바로 윗칸(y + 1) 부터 블럭이 새롭게 생성된 위치의 칸 까지(iBlockY + 1) 포함해 모두 떨어뜨려야 합니다.
1 | if (sBlock.iX == x && sBlock.iY == z) | cs |
현재 검사하고 있는 블럭의 위치가 위 그림의 파란색 칸 위치의 블럭과 같다면.
1 2 3 4 | sBlock.StartPos = new Vector3(x, z, 0); sBlock.TartgetPos = new Vector3(x, z - 1, 0); sBlock.bDown = true; break; | cs |
1라인 == 파란색 칸의 위치
2라인 == 노랜색 칸의 위치
로 셋팅.
이렇게 셋팅된 위치를 토대로 블럭을 TargetPos까지 이동 시킨다.
3라인 == 이동을 허가하는 플래그 변수 (bDown)
여기서 일단 Block 스크립트로 넘어갑시다.
왜냐하면 3라인 에서 블럭 이동에 대한 허가가 내려왔기 때문입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private float fStep = 0f; // 속력 축적 변수. private float fMoveSpeed = 10f; // 블럭 떨어지는 기본 속력. void Update() { if (bDown && transform.position != TartgetPos) { fStep += Time.deltaTime * fMoveSpeed; transform.position = Vector3.MoveTowards(StartPos, TartgetPos, fStep); if (transform.position == TartgetPos) { int X = (int)TartgetPos.x; int Y = (int)TartgetPos.y; gMar.BlockBoard[X][Y] = iType; iX = X; iY = Y; fStep = 0f; bDown = false; } } } | cs |
새롭게 추가된 구문은 위에나온 코드가 답니다.
6라인의 조건은 이렇습니다.
블럭이 떨어지는것에 대한 허가가 되었나? 그리고
현재 내 위치가 목표 위치와 다른가?
8라인 에서는 시간에 따른 블럭 이동 속도가 초기화 됩니다.
9라인 에서는 현재 내 위치를 변화 시켜줍니다.
MoveTowards() 함수를 이용하여 보간 이동 시켜주는군요.
11라인 에서의 조건은 이렇습니다.
내 위치가 목표위치에 도달했는가?
도달 했다면 목표 위치는 현재 내 위치와 같으므로
16라인에 블럭보드의 특정 위치에 현재 자신의 타입으로 초기화 시켜 줍니다.
17,18라인은 현재 자신의 위치가 목표위치(TartgetPos)와 같으므로 새롭게 정보를 갱신해 줍니다.
19 라인에서는 블럭의 떨어지는 속력을 0으로 초기화 합니다.
그렇지 않으면 다음에 블럭이 또 떨어지는 일이 생길때 너무 속력이 빨라 순간이동 수준으로 보일지도 모르기 때문입니다.
20 라인에서는 현재 내 위치가 목표 위치까지 도달했으므로 더 이상 움직일 필요가 없기 때문에 명령을 철회한다는 것과 같다고 생각 하시면 됩니다.
그럼 다시 GameManager에 BlockDown()함수로 돌아와서
1 2 3 4 | if (bBlockDown) bBlockMoveDown = true; else StartCoroutine(BlockCheck()); | cs |
이 구문은 bBlockDown변수 선언 이유와 함께 설명했으므로 생략 하겠습니다.
다만 bBlockMoveDown변수에 대해서는 설명하고자 합니다.
이 변수는 현재 블럭의 이동이 끝났다면 다음 블럭의 이동을 시작해주기 위한 플래그 변수 입니다.
현재 블럭의 이동이 끝난지 알수 없기 때문에 그에따른 체크를 해야 합니다.
Update() 함수로 이동합시다.
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 | void Update() { if (bBlockMoveDown) { // 블럭이 움직인다: true // 블럭이 움직이지 않는다 : false bool bBlockMoveEnd = false; GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK"); foreach (GameObject block in blocks) { // 움직이는 블럭이 있는지 bDown변수를 체크해여 확인. // 조건 : 움직이는 블럭이 있다면. if (block.GetComponent<Block>().bDown) { // 움직이는 블럭이 아직 있으므로 bBlockMoveEnd을 true로 변환. bBlockMoveEnd = true; break; } } // 블럭이 움직이지 않는다면. if (!bBlockMoveEnd) { bBlockMoveDown = false; BlockDown(); } } } | cs |
현재는 bBlockMoveDown변수가 true되었기 때문에 현재 블럭들이 이동을 하고있는지, 그렇지 않은지 체크가 가능해졌습니다.
모든 블럭의 bDown값을 체크 합니다. 만약 하나라도 true이라면 현재 이동하고 있는 블럭이 존재하는 것이므로,
7라인의 선언된 bool값을 true값으로 변환 시켜 줍니다.
만약 bDown값이 true인것이 하나도 없다면 이동하고 있는 블럭이 존재하지 않는다는 의미 이므로 다음 빈 칸 이동을 위한 BlockDown()함수(27라인)를 호출해 줍니다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리2 (0) | 2018.03.14 |
---|---|
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리. (2) | 2018.03.09 |
[Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리. (0) | 2018.02.18 |
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2 (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리.
※ 소스 출처 : 네이버 카페 - 유니티 허브 - 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
이번 글에서 하고자하는 내용은 체크 및 삭제 입니다.
3개이상의 연속된 블럭이 있는지 체크하고 그 블럭을 삭제하는 것입니다.
이번에 짤 스크립트는 그에 관한 내용입니다.
1,2) 블럭체크 및 블럭매치 함수 코딩.
3). 블럭삭제 함수 코딩.
1. 블럭보드 전체를 체크하면서 3개 이상의 연속된 블럭이 있는지 찾고, 연속된 블럭이면 체크(삭제해야할 블럭이라고 마킹(bDead))한다.
=> BlockCheck() 코루틴.
블럭체크 코루틴에서는 가로체크와 세로체크의 기능을 따로 분리해서 체크했습니다.
그 편이 가독성이 좋기 때문이죠.
우선 가로체크부터 보도록 합시다.
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 | // 가로체크. for (int y = 0; y < iBlockY; y++) { for (int x = 0; x < iBlockX - 2; x++) { // 기준칸과 기준칸 + 1칸을 비교 검사. if (BlockBoard[x][y] == BlockBoard[x + 1][y]) { // 기준칸과 기준칸 + 2칸을 비교 검사. if (BlockBoard[x][y] == BlockBoard[x + 2][y]) { GameObject[] blocks = GameObject.FindGameObjectsWithTag("BLOCK"); foreach (GameObject block in blocks) { Block sBlock = block.GetComponent<Block>(); if (sBlock.iY != y) continue; if (sBlock.iX == x && sBlock.iY == y) { sBlock.bDead = true; continue; } if (sBlock.iX == x + 1 && sBlock.iY == y) { sBlock.bDead = true; continue; } if (sBlock.iX == x + 2 && sBlock.iY == y) sBlock.bDead = true; } } } } } | cs |
x는 열, y는 행으로 으로 보시면 되겠습니다.
0번째 행 0번째 열에는 2라는 값이 있습니다.
이 2라는 값이 연속되는지 확인하기 위해 위치(0,0) 기준으로 +1칸 +2칸째를 검사하도록 하겠습니다.
이런식으로 한칸 한칸을 기준칸으로 해서 +1칸 +2칸째를 검사하고
같다면 3개가 연속되므로 3개의 블럭의 스크립트의 bool변수인 bDead 변수를 true로 변환 합니다.
이렇게 모든 칸들의 블럭들을 체크 해주고, 체크가 끝나면 모든 블럭을 가져와 블럭의 bDead변수를 검사해 true인 블럭들을 삭제해주면 됩니다.
P.S
연속된 칸이 3칸이 아닌 4칸 이상 이여도 문제 없습니다.
왜냐하면 기준칸이 게속 달라지기 때문입니다.
한 번 체크가 끝난 칸이라고 해서 체크를 안하는게 아닙니다.
세로체크도 가로체크와 크게 다르지 않기 때문에 설명을 생략하겠습니다.
2. 전체 블럭을 가져와 체크(마킹)된 블럭이 있는지 찾는다.
=> NoMatch() 함수.
3. 마킹된 블럭 있다면 블럭을 삭제한다.
=> DeleteBlock() 함수.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 네 번째 - 마우스 처리. (2) | 2018.03.09 |
---|---|
[Unity3D] 헥사게임 만들기 세 번째 - 블럭처리2. (2) | 2018.02.24 |
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2 (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2
※ 소스 출처 : 네이버 카페 - 유니티 허브 - 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
아무래도 배경이 없는건 심심하니
기본적인 배경부터 넣고 블럭보드를 스크립트를 통해 만들어 봅시다.
(7).
하이러키 창에 빈 오브젝트를 하나 만들어 주고,
빈 오브젝트의 이름을 Map으로 하고 Transform컴포넌트를 Reset해 기본값으로 셋팅합니다.
이 오브젝트에는 배경 이미지 및 블럭보드의 배경 이미지 등이 자식으로 들어갈 겁니다.
(8).
프로젝트창 > 마테리얼 폴더 > 마테리얼을 하나 만들어 줍니다.
이름은 'BackGround'가 좋겠군요.
(9).
마테리얼의 셰이더를 Unlit > Texture로 바꿔 줍니다.
(10).
BackGround 이미지를 마테리얼에 드래그 하여 넣습니다.
(11.)
만들어 놓은 마테리얼을 클릭 한 뒤 Ctrl + D를 눌러 복사한 후.
복사된 마테리얼의 이름을 BlcokBoad_Bg로 바꾸고
ui_BackGround 이미지를 드래그하여 넣습니다.
(12).
마테리얼 작업이 완료되면 하이러키창에 Quad객체를 만듭니다.
이 객체안에 열심히 작업한 마테리얼을 넣어 배경을 만듭시다.
그리고 배경의 크기는 적당히 조절해 줍시다.
(13).
테두리는 월드상에 있는 오브젝트들이 이동해도 영향을 받지않게 캔버스(UI)에 작업해줍시다.
(14).
이제 위와같이 블럭이 만들어지도록 스크립트를 짜봅시다.
동영상을 따라해 봅시다.
동영상에서는 실수로 코드 한 줄을 빼먹었네요.
GameManager스크립트에 CreateBlock()함수에서 밑의 구문을 추가해주세요.
1 | BlockBoard[x][y] = iType; | cs |
1 2 | public int iBlockX, iBlockY; // 블럭보드의 가로 세로 크기. public int[][] BlockBoard; // 블럭보드. | cs |
블럭보드는 가로 세로의 크기를 받아 2차원 배열로 만들어 졌습니다.
그림으로 보자면 밑과 같은 그림이 되겠군요.
그렇다면 2차원 배열에는 어떤 정보가 들어갔을까요?
바로 블럭의 종류값이 들어갔습니다. 그림으로 보자면 밑과 같습니다.
이러한 값들이 들어간 이유는 같은 블럭이 3개이상 연속됐을 때 삭제해 주기 위함입니다.
실제 삭제를 위해서는 2차원 배열에 들어있는 값들을 비교하고 연속된 숫자가 나왔을 때
해당 위치에 있는 블럭을 삭제해야 합니다. 그러면 그 위치 값은 어디에 있을까요?
바로 블럭 자신이 가지고 있습니다.
블럭 스크립트의 일부분을 보면
1 2 | public int iX, iY; // 블럭의 위치. public int iType; // 블럭의 종류. | cs |
위와 같은 정보를 가지고 있습니다.
위치 뿐만 아니라 종류까지도 비교해서 검사하면 됩니다.
이 부분은 후에 '블럭삭제'부분에서 다룰려고 합니다.
블럭의 위치를 그림으로 보자면 밑과 같이 되어있습니다.
이렇게 된 이유는 2중 for문으로 블럭을 생성해줄 때 블럭보드의 가로 및 세로의 크기가
그대로 위치 정보로 들어갔기 때문입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | for (int y = 0; y < iBlockY; y++) { for (int x = 0; x < iBlockX; x++) { GameObject block = Instantiate(OriginBlock, new Vector3(x, y, 0), Quaternion.identity); block.transform.SetParent(transform); int iType = Random.Range(0, BlockType.Length); Block sBlock = block.GetComponent<Block>(); sBlock.iX = x; sBlock.iY = y; sBlock.iType = iType; sBlock.SetBlockImg(BlockType[iType]); BlockBoard[x][y] = iType; } } | cs |
그림으로 보자면 위의 2중 for문은 밑의 그림과 같은 순서로 생성시킵니다.
즉, 월드상의 블럭의 위치 값도 ( x , y, z )일 때 (0,0,0), (1,0,0), (2,0,0) ... 와 같이 셋팅 됩니다.
위의 정보를 봤을 때 블럭과 블럭의 사이에 거리가 1이 된다는 뜻인데, 블럭 이미지의 크기가 다를 경우에
밑과 같이 딱 나뉘어 떨어지지 않습니다.
그러한 사태를 방지하기 위해 리소스 준비단계에서 블럭의 크기를 100픽셀로 맞춰 만든 것입니다.
100픽셀 == 1 Scale == 1 Position 이라고 생각하면 헷갈림이 조금 덜 할듯 합니다.
다음은 블럭의 가로 세로 체크를 해보도록 하겠습니다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 세 번째 - 블럭처리2. (2) | 2018.02.24 |
---|---|
[Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리. (0) | 2018.02.18 |
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기.
※ 소스 출처 : 네이버 카페 - 유니티 허브 - 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
아래의 이미지까지 만들어 봅시다.
블럭 이미지를 모두 블랙설정한 뒤
Texture Type : Sprite (2D and UI)
Packing Tag : BLOCK
Pixels Per Unit : 100픽셀
위와같이 셋팅 후 Apply를 눌러준다.
(2).
실제로 블럭 하나를 만들기 위해
하이러키 창에서 마우스 오른쪽 클릭 후 Sprite를 만듭니다.
(3).
만들어진 Sprite의 Transform컴포넌트에 마우스를 갖다댄뒤 오른쪽 버튼을 클릭해 Reset을 눌러
Position,
Rotation,
Scale
위의 세 값을 기본값으로 만듭니다.
그리고 Sprite의 이름을 Block으로 바꿔줍니다.
(4).
다음은 블럭의 첫 번째 이미지를 Sprite Renderer컴포넌트의 Sprite에 드래그 해줌으로써 블럭을 하나 완성 시킵니다.
(5).
다음은 블럭의 태그를 정해줘야 합니다.
상단 메뉴중 Tag옆 'Untagged'라 써져있는 박스를 눌러
Add Tag를 누른뒤 'BLOCK'이라는 태그명을 추가해 줍니다.
※ 이렇게 태그를 정해주는 이유는 스크립트 상에서 특정 태그명을 가지고있는 객체를 모두 가져올 때 사용하기 위함입니다.
(6). 이렇게 하나의 블럭이 완성되었습니다.
이제 하이러키 창에 있는 블럭을 프로젝트창에 있는 Prefeb폴더에 드래그하여 넣어주시면 프리팹으로 만들어집니다.
그뒤 하이러키 창에있는 Block객체를 지워줍니다.
이제 이 블럭의 프리팹을 이용하여 블럭보드를 만들어 봅시다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 세 번째 - 블럭 처리. (0) | 2018.02.18 |
---|---|
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2 (0) | 2018.02.13 |
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정.
※ 소스 출처 : 네이버 카페 > 유니티 허브 > 햐얀바람님.
0. 리소스 준비하기.
1). 배경 이미지.
2). 블럭보드의 배경 이미지.
3). UI테두리 이미지.
4). 블럭 이미지.
1. 캔버스 크기 설정하기.
- 게임뷰어 및 캔버스 크기 설정.
2. 블럭보드 만들기.
1). 블럭 프리팹 만들기.
-------------------------------------------------------------------------------------------
0. 리소스 준비하기.
- 배경이미지 (출처 : 구글링)
- 블럭보드의 배경이미지 (출처 : 포토샾으로 만듬)
- UI의 테두리 (하얀바람님 프로젝트 내장 리소스)
- 블럭 이미지 (하얀바람님 프로젝트 내장 리소스)
- 크기 : 가로 세로 : 100픽셀.
(출처 : 언더테일 구글링)
1. 캔버스 크기 설정하기.
1).
게임 뷰어를 720 x 1280으로 설정.
2).
마찬가지로 캔버스의 Canvas Scaler 컴포넌트의 UI Cale Mode를 Scale With Screen Size로 설정하고,
Reference Resolution을 게임뷰어와 같이 720 x 1280으로 설정한다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 두 번째- 블럭보드 만들기2 (0) | 2018.02.13 |
---|---|
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
[Unity3D] 인벤토리 [Part 4] - Load
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
5. XML문서를 이용한 인벤토리 아이템 저장. - Load
우선 흐름부터 살펴보도록 하자.
1. InventoryData.xml이라는 파일이 존재하는지 확인한다.
=> 파일이 존재하지 않으면 함수종료.
=>System.IO.File.Exists(경로) : 이 함수는 경로내의 파일이 존재하면 true 존재하지 않으면 false를 되돌려주는 함수이다.
2. xml문서를 하나 만든다.
3. 만든 xml문서에 InventoryData.xml을 불러온다.
4. 요소를 하나 만들어서 불러온 xml파일로 초기화 시킨다.
5. 필드를 하나 만들어서 InventoryData.xml문서의 최상위에 있는 필드로 초기화 시킨다.
6. 필드의 존재하는 슬롯번호를 하나 꺼내와서 슬롯의 n번째 번호를 가져와, 그 번호를 이용하여 슬롯의 'Slot'스크립트를 꺼내온다.
7. 아이템 클래스로 아이템 객체를 하나 만든다.
8. 필드에서 Name, MaxCount를 꺼내와서 새로 생성된 아이템의 내용을 초기화 시킨다.
Item Script 추가사항.
9. 필드에서 Count를 꺼내와서 Count의 수만큼 반복문을 돌린다.
=> 위에서 6에서 가져왔던 Slot스크립트를 이용하여 이 슬롯에 아이템을 AddItem()을 호출하여 집어넣는다.
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 | public static List<GameObject> Load(List<GameObject> SlotList) { if (!System.IO.File.Exists(Application.dataPath + "/Save/InventoryData.xml")) return SlotList; XmlDocument XmlDoc = new XmlDocument(); // 문서를 만듬. XmlDoc.Load(Application.dataPath + "/Save/InventoryData.xml"); // 경로상의 XML파일을 로드 XmlElement Xmlel = XmlDoc["ItemDB"]; // 속성 ItemDB에 접속. foreach (XmlElement ItemElement in Xmlel.ChildNodes) { // 슬롯의 n번째 스크립트를 가져온다. Slot slot = SlotList[System.Convert.ToInt32(ItemElement.GetAttribute("SlotNumber"))].GetComponent<Slot>(); // 아이템 생성. Item item = new Item(); // 아이템의 정보를 셋팅한다. string Name = ItemElement.GetAttribute("Name"); // 아이템 이름을 가져옴. int MaxCount = System.Convert.ToInt32(ItemElement.GetAttribute("MaxCount")); // 겹칠수 있는 한계. item.Init(Name, MaxCount); // 위의 가져온 정보를 토대로 아이템의 정보를 초기화. int Count = System.Convert.ToInt32(ItemElement.GetAttribute("Count")); // 슬롯에 아이템을 n개 집어넣기 위해서 개수를 가져옴. for (int i = 0; i < Count; i++) slot.AddItem(item); } return SlotList; } | cs |
'Inventory' 스크립트에 Awake()에서는 인벤토리와 슬롯이 만들어진다.
슬롯이 만들어지게 되면 슬롯객체 안에 존재하는 'Slot' 스크립트의 Start()가 수행되는데,
이 Start()의 수행이 끝난뒤 우리가 저장한 정보를 Load해야한다.
만약 Inventory 스크립트의 Awake()가 수행되기전에 Load()를 수행하면 아직 만들어지지도 않은 슬롯에
정보를 할당하려 했으므로 에러를 내뿜게 된다.
마찬가지로 Slot 스크립트의 Start()가 수행되기전에 Load()를 수행한다면 슬롯의 아이템을 담아놓는 Stack자체가
생성되지 않았기 때문에 에러를 내뿜게 된다.
그렇기 때문에 모든 슬롯의 Start()가 끝나게 되면 Load를 수행하면 된다.
하지만 모든 슬롯의 Start()의 수행이 언제 끝날지는 알 수 없으므로 사용자가 임의로 시작을 지연시키고 그 뒤 정보를 Load시킨다. 불안정한 방법이지만 Awake() 및 Start() 수행속도는 빠른 편이므로 시간을 아주 조금만 지연시켜주면 된다.
Inventory스크립트에 Awake()안에 Invoke를 이용하여 Init()함수를 호출해주고, 시간을 0.01f초 지연시키도록 하자.
1 2 3 4 5 6 | Invoke("Init", 0.01f); void Init() { ItemIO.Load(AllSlot); } | cs |
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
---|---|
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 4] - Save
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
미리보기
5. XML문서를 이용한 인벤토리 아이템 저장. - Save
인벤토리에 들어있는 아이템의 정보를 XML문서에 Save하여,
게임 시작 시 XML문서를 토대로 정보를 Load하려고 한다.
시작하기 전에 Save및 Load의 호출를 어디에서 할 것인가 부터 살펴보자.
Save는 인벤토리 내에 아이템에 대한 정보변환이 일어났을 때 수행해야한다.
그렇다면 정보변환는 언제 일어나는 것일까?
- 아이템을 먹었을 때
- 아이템을 드래그하여 다른 슬롯으로 옮겼을 때.
- 아이템 끼리 Swap했을 때.
이 모든 변화가 일어났을 때 Save는 호출되어야 한다.
우리가 만든 함수 중 위의 변화가 일어났을 때
어김없이 호출되는 함수가 하나 있다.
'Slot' 스크립트에 존재하는 UpdateInfo() 함수이다.
이 함수는 위의 모든 상황에서 호출되는 함수다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 슬롯에 대한 각종 정보 업데이트. public void UpdateInfo(bool isSlot, Sprite sprite) { this.isSlot = isSlot; transform.GetChild(0).GetComponent<Image>().sprite = sprite; if (slot.Count > 1) text.text = slot.Count.ToString(); else text.text = ""; ItemIO.SaveDate(); } | cs |
※ 이렇게 맨 밑에다 Save를 호출하면 된다.
그렇다면 Load는 언제 호출되어야 할까?
인벤토리가 만들어지기 전에 Load가 호출되면 안된다.
인벤토리가 생성되고.
슬롯이 생성되고(Inventory 스크립트의 Awake()함수의 호출).
슬롯객체에 붙어있는 Slot 스크립트의 Start()가 호출이 끝난 뒤.
Load가 호출되어야 한다.
Save함수를 호출할 때 아이템에대한 정보가 XML문서로 만들어지게 된다.
이 스크립트를 이해하기 위해서는 우리는 XML문서가 어떻게 구성되어 있는지 파악할 필요가 있다.
정보가 저장된 XML문서의 구성은 이렇다.
1 2 3 4 5 6 7 8 9 10 11 | <ItemDB> <Item SlotNumber="1" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="2" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="4" Name="HP" Count="3" MaxCount="3" /> <Item SlotNumber="5" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="6" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="8" Name="HP" Count="3" MaxCount="3" /> <Item SlotNumber="9" Name="HP" Count="3" MaxCount="3" /> <Item SlotNumber="17" Name="HP" Count="1" MaxCount="3" /> <Item SlotNumber="18" Name="MP" Count="1" MaxCount="3" /> </ItemDB> | cs |
<요소>
<필드이름 정보1 정보2 정보3 정보4.../>
<필드이름 정보1 정보2 정보3 정보4.../>
</요소>
이런식으로 구성이 되어있는데, 이것을 스크립트로 만들어 내보낼 것이다.
XML문서를 만드는 스크립트의 흐름은 이렇다.
1. XML문서를 하나 만든다.
2. 요소를 하나 만든다.
3. 만든 요소를 XML문서에 첨부한다.
여기까지 코드를 짜면 XML문서는
---------------------------------
<요소>
</요소>
---------------------------------
까지 생성이 된다.
4. 필드(요소)를 생성.
5. 필드(요소)의 내용을 셋팅.
6. 첫 번째 요소의 방금만든 필드(요소)를 첨부.
까지 하면 위의 완성된 XML문서가 완성되며,
마지막으로 XML를 정해진 경로로 내보내면 저장은 끝난다.
코드를 한 번 보도록 하자.
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Xml; // sealed을 사용하여 다른 클래스가 상속되지 못하도록 함. public sealed class ItemIO { public static void SaveDate() { // 인벤토리에서 슬롯을 관리해주는 리스트를 받아온다. List<GameObject> item = ObjManager.Call().IV.AllSlot; XmlDocument XmlDoc = new XmlDocument(); // XML문서 생성. XmlElement XmlEl = XmlDoc.CreateElement("ItemDB"); // 요소 생성. XmlDoc.AppendChild(XmlEl); // 요소를 XML문서에 첨부. // 리스트의 총 크기(슬롯의 개수.) int Count = item.Count; for (int i = 0; i < Count; i++) { // 슬롯 리스트에서 슬롯을 하나씩 꺼내온다. Slot itemInfo = item[i].GetComponent<Slot>(); // 슬롯이 비어있으면 저장할 필요가 없으므로 넘긴다. if (!itemInfo.isSlots()) continue; // 필드(요소)를 생성. XmlElement ElementSetting = XmlDoc.CreateElement("Item"); // 필드(요소)의 내용을 셋팅. ElementSetting.SetAttribute("SlotNumber", i.ToString()); // n번째 슬롯에 아이템. ElementSetting.SetAttribute("Name", itemInfo.ItemReturn().Name); // 아이템의 이름. ElementSetting.SetAttribute("Count", itemInfo.slot.Count.ToString()); // 아이템의 개수. (ex: 현 슬롯에 겹쳐진 아이템 10개임.) ElementSetting.SetAttribute("MaxCount", itemInfo.ItemReturn().MaxCount.ToString()); // 아이템을 겹칠수 있는 한계. XmlEl.AppendChild(ElementSetting); // ItemDB요소에 위의 셋팅한 요소를 문서에 첨부. } // XML문서로 내보낸다. 인자로는 문서를 내보낼 경로. XmlDoc.Save(Application.dataPath + "/Save/InventoryData.xml"); } } | cs |
1 | using System.Xml; | cs |
필수는 아니지만 각 함수를 사용할 때 System.Xml을 쓰는건 효율이 떨어지므로 using해둡시다.
1 | XmlDocument XmlDoc = new XmlDocument(); | cs |
Document는 문서라는 뜻으로 XML문서를 만듭니다.
1 | XmlElement XmlEl = XmlDoc.CreateElement("ItemDB"); | cs |
Element는 요소,성분이라는 뜻으로 새로운 요소를 인자의 이름으로 만든다.
이 구문에서 XML문서의 <ItemDB> </ItemDB>가 완성된다.
1 | XmlDoc.AppendChild(XmlEl); | cs |
Append는 첨부라는 뜻으로 요소를 XML문서에 첨부하는데 사용한다.
쭉 보면
1. XML문서를 만들고
2. <ItemDB> </ItemDB>라는 요소를 만들어서
3. XML문서에 첨부.
라는 것이 된다.
그 다음 for문을 돌려 필드를 생성시켜야 하는데, 이것은 무엇에 대한 for문일까?
우리는 인벤토리에 각 슬롯이 가지고있는 아이템에 대한 정보를 저장하는 것이다
만약 슬롯이 30개 존재한다고 하면 for문을 30개 돌아 각 슬롯이 가진 정보를 필드에 옮겨 저장하는 것이다.
슬롯 개수에 대한 정보는 인벤토리 스크립트가 가지고 있으므로 가져와서
슬롯의 숫자만큼 for문을 돌면 된다.
for문을 돌때 슬롯에 아무것도 존재하지 않는다면 저장할 필요가 없으므로 continue해준다.
여기서 우리가 저장할 내용은 이렇다.
1. 슬롯의 번호
2. 아이템의 이름.
3. 아이템의 개수.
4. 아이템을 겹칠수 있는 한계 개수.
이렇게 4개에 대한 정보를 저장해야 한다.
슬롯의 번호를 저장하는 이유는
아이템이 2번째 칸에 있고, 게임을 껏다 켰을 때 그 아이템은 여전히 2번째 칸에 존재 해야한다.
Load할때 슬롯의 번호를 모르고 있다면 몇번째 슬롯에 아이템정보를 가져와야할지 알지 못하기 때문이다.
아무튼
for문을 돌면서
1 | XmlElement ElementSetting = XmlDoc.CreateElement("Item"); | cs |
또 하나의 필드(요소)를 생성한다. 이름은 "Item"이다.
이 필드(요소)에 위의 4가지 정보를 셋팅해준다.
1 2 3 4 | ElementSetting.SetAttribute("SlotNumber", i.ToString()); // n번째 슬롯에 아이템. ElementSetting.SetAttribute("Name", itemInfo.ItemReturn().Name); // 아이템의 이름. ElementSetting.SetAttribute("Count", itemInfo.slot.Count.ToString()); // 아이템의 개수. (ex: 현 슬롯에 겹쳐진 아이템 10개임.) ElementSetting.SetAttribute("MaxCount", itemInfo.ItemReturn().MaxCount.ToString()); // 아이템을 겹칠수 있는 한계. | cs |
※ 모두 문자열로 저장시켜야 한다.
Item이라는 필드에 위의 4가지 정보가 추가되면
이 추가된 정보를 첫 번째 요소인 "ItemDB"에 첨부시켜야 한다.
1 | XmlEl.AppendChild(ElementSetting); | cs |
이렇게 기본적인 XML문서가 완성된다.
이제 이 XML문서를 내보내면 저장이 되는 것이다.
1 | XmlDoc.Save(Application.dataPath + "/Save/InventoryData.xml"); | cs |
Application.dataPath는 현재 자신의 유니티 프로젝트에 Assets폴더까지의 경로를 반환해준다.
필자같은경우 Assets폴더 안에 Save라는 폴더를 만들었고 그 안에 XML문서가 저장되는데,
문서가 저장될때 이름이 InventoryData.xml이다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
---|---|
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
미리보기
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 빈 이미지를 만든다.
이 이미지는 슬롯에 있는 아이템을 눌렀을 때 그 아이템의 이미지를 빈 이미지에 가져와 띄워주는 역할을 하는 이미지이다.
빈 이미지를 위와같이 Canvas의 자식으로 하나 만든다.
그리고, 태그를 하나 추가할 건데, 이 태그는 스크립트로 이 이미지를 가져오기 위함니다.
태그의 이름은 DragImg이다.
1 2 3 4 5 6 | public Transform Img; // 비어있는 이미지. void Start() { Img = GameObject.FindGameObjectWithTag("DragImg").transform; } | cs |
다음은 슬롯에 4가지 이벤트를 추가 해보자.
Event Trigger를 추가하면 된다.
드래그 앤 드롭은 4가지 형태로 볼 수 있다.
- 처음 눌렀을 때, (Pointer Down - Down)
- 누르고 드래그 할 때, (Drag - Drag)
- 드래그를 끝냈을 때, (End Drag - DragEnd)
- 마우스 버튼을 땠을 때. (Pointer Up - Up)
1). 처음 눌렀을 때.
- 슬롯에 아이템이 존재하지 않는다면 함수를 종료한다.
- 누른 버튼이 마우스 오른쪽 버튼이면 아이템을 사용한다.
- 빈 이미지 객체를 활성화 시킨다.
- 빈 이미지의 사이즈를 슬롯의 사이즈와 똑같이 변경한다.
=> 해상도가 달라졌을 때 슬롯의 크기가 바뀌므로 빈 객채의 이미지도 유동적으로 달라져야 한다.
- 빈 이미지의 sprite를 슬롯이 가지고 있는 아이템 이미지로 변경한다.
- 빈 이미지 객체의 위치를 마우스의 위치로 가져온다.
- 슬롯의 이미지를 없애준다.
- 슬롯의 텍스트 숫자를 없애준다.
2). 누르고 드래그 할 때.
- 슬롯에 아이템이 존재하지 않으면 함수를 종료한다.
- 빈 이미지의 위치를 마우스의 위치로 가져온다.
3). 드래그가 끝났을 때.
- 슬롯에 아이템이 존재하지 않으면 함수를 종료한다.
- 싱글톤을 이용해 인벤토리 내에 있는 Swap함수를 호출. 인자로 자신의 슬롯 스크립트와 빈 이미지의 현재 위치를 보낸다.
4). 마우스 버튼을 땠을 때.
- 슬롯에 아이템이 존재하지 않으면 함수를 종료한다.
- 빈 이미지 객체를 비활성화 시킨다.
- 현재 슬롯의 정보를 업데이트 시킨다.
마우스를 땠을 때 이벤트는 필요 없을것 같지만 필요하다.
빈 이미지 객체의 비활성화 및 슬롯의 이미지 업데이트에 필요하다.
빈 이미지 객체의 비활성화의 경우 드래그가 끝났을 때 수행해도 되지만,
아이템을 눌렀을 때 마우스를 움직이지 않으면 DragEnd함수는 호출되지 않는다.
그렇기 때문에 빈 이미지 객체의 활성화 조정을 이곳에서 할 필요가 있으며,
마찬가지로 없애주었던 이미지의 복구 또한 이곳에서 해 주어야 한다.
'ItemDrag' Script
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ItemDrag : MonoBehaviour { public Transform Img; // 빈 이미지 객체. private Image EmptyImg; // 빈 이미지. private Slot slot; // 현재 슬롯에 스크립트 void Start() { // 현재 슬롯의 스크립트를 가져온다. slot = GetComponent<Slot>(); // 빈 이미지 객체를 태그를 이용하여 가져온다. Img = GameObject.FindGameObjectWithTag("DragImg").transform; // 빈 이미지 객체가 가진 Image컴포넌트를 가져온다. EmptyImg = Img.GetComponent<Image>(); } public void Down() { // 슬롯에 아이템이 없으면 함수종료. if (!slot.isSlots()) return; // 아이템 사용시. if (Input.GetMouseButtonDown(1)) { slot.ItemUse(); return; } // 빈 이미지 객체를 활성화 시킨다. Img.gameObject.SetActive(true); // 빈 이미지의 사이즈를 변경한다.(해상도가 바뀔경우를 대비.) float Size = slot.transform.GetComponent<RectTransform>().sizeDelta.x; EmptyImg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Size); EmptyImg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Size); // 빈 이미지의 스프라이트를 슬롯의 스프라이트로 변경한다. EmptyImg.sprite = slot.ItemReturn().DefaultImg; // 빈 이미지의 위치를 마우스위로 가져온다. Img.transform.position = Input.mousePosition; // 슬롯의 아이템 이미지를 없애준다. slot.UpdateInfo(true, slot.DefaultImg); // 슬롯의 텍스트 숫자를 없애준다. slot.text.text = ""; } public void Drag() { // isImg플래그가 false이면 슬롯에 아이템이 존재하지 않는 것이므로 함수 종료. if (!slot.isSlots()) return; Img.transform.position = Input.mousePosition; } public void DragEnd() { // isImg플래그가 false이면 슬롯에 아이템이 존재하지 않는 것이므로 함수 종료. if (!slot.isSlots()) return; // 싱글톤을 이용해서 인벤토리의 스왑함수를 호출(현재 슬롯, 빈 이미지의 현재 위치.) ObjManager.Call().IV.Swap(slot, Img.transform.position); //slot = null; } public void Up() { // isImg플래그가 false이면 슬롯에 아이템이 존재하지 않는 것이므로 함수 종료. if (!slot.isSlots()) return; // 빈 이미지 객체 비활성화. Img.gameObject.SetActive(false); // 슬롯의 아이템 이미지를 복구 시킨다. slot.UpdateInfo(true, slot.slot.Peek().DefaultImg); } } | cs |
드래그가 끝나는 함수, 그러니까 DragEnd함수의
Swap()함수를 살펴보도록 하자.
접근 방법은 싱글톤을 이용해서 Inventory스크립트에 접근해서 Swap함수를 호출한다.
이 함수의 인자는 2개를 받고 있는데, 현재 내가 누른 슬롯의 Slot 스크립트와 빈 이미지 객체의 위치이다.
'Inventory' 스크립트의 추가된 함수들을 살펴보자.
이곳 에서는 3가지 함수가 추가되었다.
public Slot NearDisSlot(Vector3 Pos) // 빈 이미지와 가까운 슬롯을 찾아 반환해주는 함수.
public void Swap(Slot slot, Vector3 Pos) // 슬롯 끼리의 내용물을 전체적으로 교환 해주는 함수.
void Swap(Slot xFirst, Slot oSecond) // Swap함수를 보조 해주는 함수. (두 개의 슬롯의 내용물을 교환한다.)
이번엔 UpdateInfo() 함수를 살펴보자.
이 함수는 슬롯에 추가된 함수로 아이템 존재유무와 이미지 업데이트의 역할을 맡고 있다.
Slot 스크립트에 아래의 함수를 추가해 주면 된다.
1 2 3 4 5 6 7 8 9 10 11 | // 슬롯에 대한 각종 정보 업데이트. public void UpdateInfo(bool isSlot, Sprite sprite) { this.isSlot = isSlot; transform.GetChild(0).GetComponent<Image>().sprite = sprite; if (slot.Count > 1) text.text = slot.Count.ToString(); else text.text = ""; } | cs |
또한 이러한 함수가 생겼으므로 Slot 스크립트의 AddItem() 함수에 대해서도
아래와 같이 고칠수 있게 된다.
1 2 3 4 5 6 | public void AddItem(Item item) { // 스택에 아이템 추가. slot.Push(item); UpdateInfo(true, item.DefaultImg); } | cs |
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
---|---|
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
[Unity3D] 인벤토리 [Part 0] (0) | 2017.04.06 |