UI 관한 로직 - UIManager.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
public class UI_Manager : SingletonLocal<UI_Manager>
{
public GameUI GameUI_Obj;
public ResultUI ResultUI_Obj;
int mLayer;
bool isCreatedResUI = false;
// 파이프 UI 용도
public Canvas pipeUI_CanvasPrefab = null;
Canvas pipeUI_Canvas;
// 서브 UI 용도 (결과창)
public Canvas subUI_CanvasPrefab = null;
Canvas subUI_Canvas;
// Start is called before the first frame update
void Start()
{
Init();
}
private void Update()
{
}
public void Init()
{
mLayer = LayerMask.NameToLayer("UI");
// 서브캔버스 결과 (UI 전용) 세팅 및 카메라 세팅
pipeUI_Canvas = GameObject.Instantiate(pipeUI_CanvasPrefab);
pipeUI_Canvas.worldCamera = Global.GetUI_Camera();
subUI_Canvas = GameObject.Instantiate(subUI_CanvasPrefab);
subUI_Canvas.worldCamera = Global.GetUI_Camera();
GameObject.Instantiate(GameUI_Obj, transform);
}
public void ResetLevel()
{
// Init();
// GetComponent<Canvas>().worldCamera = Global.GetUI_Camera();
}
public Slot CheckForHoveredItem()
{
return CheckForHoveredItem(GetEventSystemRaycastResults());
}
public Slot CheckForHoveredItem(List<RaycastResult> listRaycastResult)
{
Slot tmpSlot = null;
for (int index = 0; index < listRaycastResult.Count; index++)
{
RaycastResult res = listRaycastResult[index];
if (res.gameObject.layer == mLayer)
tmpSlot = res.gameObject.GetComponentInChildren<Slot>();
}
return tmpSlot;
}
List<RaycastResult> GetEventSystemRaycastResults()
{
PointerEventData eventData = new PointerEventData(EventSystem.current);
eventData.position = Input.mousePosition;
List<RaycastResult> listRes = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, listRes);
return listRes;
}
public void ClearedGame()
{
if (isCreatedResUI ||
subUI_Canvas == null)
return;
GameObject.Instantiate(ResultUI_Obj, subUI_Canvas.transform);
isCreatedResUI = true;
}
}
각 슬롯들 즉 드래그 종료할 한 시점에서 다시 그리는 용도
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class Slot : MonoBehaviour
{
RectTransform rect;
public Image img;
// ¼± °ü·Ã Á¤º¸
public Color color;
public Vector3 pipePos;
public int idx = 0;
private void Awake()
{
img = GetComponent<Image>();
rect = GetComponent<RectTransform>();
}
public void SetColor(Color color)
{
this.color = color;
img.color = color;
}
public void DeleteLineEndPoint()
{
if (IsLineEndPoint())
{
img.sprite = null;
img.color = Color.white;
}
}
public bool IsEmpty() { return img.sprite == null; }
public bool IsEndPoint() { return rect.sizeDelta == new Vector2(150f, 150f); }
//
public bool IsLineEndPoint() { return rect.sizeDelta == new Vector2(100f, 100f); }
public bool IsEndPoint(Color color) { return img.color == color && img.sprite.name == "Knob"; }
public Sprite GetSprite() { return (img) ? img.sprite : null; }
public Vector3 GetSize() { return rect.sizeDelta; }
public PipeInfo GetPipeInfo() { return new PipeInfo(color, pipePos, idx); }
}
맵 생성 - GameManager.cs
char형 2차원 배열에 색상 동그라미와 설정함. 텍스트 파일 또는 서버로부터 갖고 올 수 있음.
플레이어의 입력을 제어함.
아래는 Global.cs에 있을 색상에 대한 정
public static Color[] arrColor = new Color[]
{
red,
blue,
green,
yellow,
purple,
cyan,
orange,
pink,
brown
};
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using Color = UnityEngine.Color;
public class GameManager : SingletonLocal<GameManager>, IPointerUpHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public Dictionary<Color, bool> dicCompleted = new Dictionary<Color, bool>();
Slot mCurSlot = null;
char[,] mLvl1 = new char[,]
{
{ '\0', '\0', '\0','\0','\0','6' },
{ '\0', '\0', '\0','\0','\0','\0' },
{ '\0', '3', '0','\0','\0','\0' },
{ '\0', '\0', '\0','2','\0','3' },
{ '\0', '2', '\0','6','0','1' },
{ '\0', '\0', '\0','1','\0','\0' }
};
public int curPipes = 0, totalPipes = 0, movements = 0, best = 0, percent = 0;
public int size = 0;
// Start is called before the first frame update
void Start()
{
GridManager.instance.SetLevel(mLvl1);
size = mLvl1.Length;
}
// Update is called once per frame
void Update()
{
CheckIfGameEnded();
}
public void OnBeginDrag(PointerEventData eventData)
{
mCurSlot = TryToGetSlot(eventData);
Slot hoveredSlot = UI_Manager.instance.CheckForHoveredItem();
// 시작 지점일때만 초기화
if (mCurSlot &&
!mCurSlot.IsLineEndPoint())
{
var inst = PipeManager.instance;
inst.InitPipe(new PipeInfo(mCurSlot.color, mCurSlot.pipePos, mCurSlot.idx));
inst.canMove = true;
}
}
// 선 업데이트
public void OnDrag(PointerEventData eventData)
{
CheckIfDragging(eventData, UI_Manager.instance.CheckForHoveredItem());
}
public void OnPointerUp(PointerEventData eventData)
{
ResetPipe();
}
public void OnEndDrag(PointerEventData eventData)
{
ResetPipe();
}
bool IsMouseOverUI()
{
return EventSystem.current.IsPointerOverGameObject();
}
void CheckIfGameEnded()
{
int tmpCurPipes = 0;
foreach (var item in dicCompleted)
{
if (item.Value)
tmpCurPipes++;
}
curPipes = tmpCurPipes;
if (curPipes == totalPipes)
{
UI_Manager.instance.ClearedGame();
return;
}
}
Slot TryToGetSlot(PointerEventData eventData)
{
List<RaycastResult> listRayRes = new List<RaycastResult>();
if (eventData.button == PointerEventData.InputButton.Left)
{
EventSystem.current.RaycastAll(eventData, listRayRes);
foreach (var item in listRayRes)
{
var slot = item.gameObject.GetComponent<Slot>();
if (slot &&
slot.IsEndPoint(slot.color))
return slot;
}
}
return null;
}
void CheckIfDragging(PointerEventData eventData, Slot nextSlot)
{
var pipeInst = PipeManager.instance;
if (nextSlot &&
mCurSlot)
{
if(nextSlot != mCurSlot)
{
var curColor = mCurSlot.color;
// 이동 가능한 칸이면 즉시 이동
if (!nextSlot.GetSprite())
{
if (!pipeInst.canMove)
return;
movements++;
// 다른 선일 때 이동 가능
if (nextSlot.IsEndPoint(curColor) &&
nextSlot.IsLineEndPoint())
pipeInst.canMove = true;
// 유효하면 끝부분에 작은 동그라미 생성
if (pipeInst.canMove &&
!dicCompleted[curColor])
{
if (!pipeInst.CanDeletePoint(mCurSlot.color, nextSlot.pipePos))
pipeInst.AddPos(new PipeInfo(curColor, nextSlot.pipePos, nextSlot.idx));
if (!pipeInst.canMove)
return;
mCurSlot.DeleteLineEndPoint();
nextSlot.SetColor(curColor);
nextSlot.img.sprite = GridManager.instance.knobSprite;
mCurSlot = nextSlot;
}
else
{
}
}
// 다른 색상의 도착 지점인지 확인
else
{
if (!nextSlot.IsEndPoint(curColor))
{
pipeInst.canMove = false;
if (pipeInst.dicLine.ContainsKey(nextSlot.color))
{
pipeInst.DestroyPipe(nextSlot.color);
pipeInst.canMove = true;
}
}
else
{
// 원위치 시키면 제거
if (pipeInst.dicStartPos[curColor] == nextSlot.pipePos)
pipeInst.DestroyPipe(curColor);
// 시작지점이 아닐 때만 끝부분 설정
if (!dicCompleted[curColor] &&
!pipeInst.IsStartPoint(curColor, nextSlot.GetSize()))
{
pipeInst.AddPos(new PipeInfo(mCurSlot.color, nextSlot.pipePos, nextSlot.idx));
mCurSlot.DeleteLineEndPoint();
mCurSlot = nextSlot;
}
else
pipeInst.canMove = false;
}
}
}
else
{
pipeInst.canMove = true;
}
}
}
public void ResetLevel()
{
SceneManager.LoadScene("PLAY");
// curPipes = totalPipes = movements = best = percent = 0;
GridManager.instance.ResetLevel(mLvl1);
UI_Manager.instance.ResetLevel();
}
void ResetPipe()
{
if (mCurSlot)
{
dicCompleted[mCurSlot.color] = false;
mCurSlot = null;
PipeManager.instance.curPipePos = Vector3.zero;
PipeManager.instance.canMove = true;
}
}
}
그리드 (타일) 생성 - GridManager.cs
ContextMenu 어트리뷰트를 응용해서 해당되는 레벨 그리드를 생성함.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GridManager : SingletonLocal<GridManager>
{
public GameObject gridObj = null;
public GameObject backObj = null;
// 전체 리스트 담당
public List<Slot> listSlot = new List<Slot>();
// 시작과 끝 지점을 저장
public Dictionary<Vector3, Slot> dicEntryPointSlot = new Dictionary<Vector3, Slot>();
public Sprite knobSprite;
// ------- 크기 종류 -------
Vector2 mPos, mSlotSize, mMainGridPos;
float mCellSize = 0f;
int mLeftPadding = 0;
public int emptyCount = 0;
public GridManager()
{
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void InitGrid(int size)
{
gridObj = Instantiate(new GameObject(), transform);
var layoutGroup = gridObj.AddComponent<GridLayoutGroup>();
gridObj.name = $" Grid {size}x{size} ";
var rect = gridObj.transform as RectTransform;
rect.localPosition = mPos;
rect.sizeDelta = mSlotSize;
layoutGroup.padding.left = mLeftPadding;
layoutGroup.spacing = new Vector2(15f, 15f);
layoutGroup.cellSize = new Vector2(mCellSize,mCellSize);
Vector2 pipePos = new Vector2(5.32f, 2.675f);
int idx = 1;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
// 배경 슬롯 설정
var tmpObj = Instantiate(backObj, gridObj.transform);
tmpObj.name = $" Background Grid {idx} ";
// 메인 슬롯 설정
var childObj = tmpObj.transform.GetChild(0);
childObj.name = $" Main Grid {idx} ";
// 메인 슬롯 위치 설정
var childRect = childObj.transform as RectTransform;
childRect.localPosition = mMainGridPos;
childRect.sizeDelta = new Vector2(mCellSize, mCellSize);
// 선 위치 설정
var slot = childObj.GetComponent<Slot>();
slot.idx = idx - 1;
slot.pipePos = pipePos;
pipePos.x += 0.86f;
listSlot.Add(slot);
idx++;
}
pipePos.y -= 0.86f;
pipePos.x = 5.32f;
}
}
public void SetLevel(char[,] map)
{
if (listSlot.Count == 0)
return;
int pipeIdx = 0;
for (int i = 0; i < map.GetLength(0); i++)
{
for (int j = 0; j < map.GetLength(1); j++)
{
var letter = map[i, j];
var slot = listSlot[i + j + pipeIdx];
if (letter == '\0')
{
emptyCount++;
RectTransform rect = slot.GetComponent<RectTransform>();
float size = 100.0f;
rect.sizeDelta = new Vector2(size, size);
continue;
}
var color = Global.arrColor[letter - '0'];
GameManager.instance.dicCompleted.TryAdd(color, false);
dicEntryPointSlot.TryAdd(slot.pipePos, slot);
slot.img.sprite = knobSprite;
slot.img.color = color;
slot.color = slot.img.color;
GameManager.instance.totalPipes++;
}
pipeIdx += 5;
}
GameManager.instance.totalPipes /= 2;
}
[ContextMenu("5x5 생성")]
public void Create5x5()
{
mPos = new Vector2(237f, 100f);
mSlotSize = new Vector2(1080f, 975f);
mMainGridPos = new Vector2(92.5f, -92.5f);
mLeftPadding = 50;
mCellSize = 185f;
InitGrid(5);
}
[ContextMenu("6x6 생성")]
public void Create6x6()
{
mPos = new Vector2(237f, 100f);
mSlotSize = new Vector2(1080f, 975f);
mMainGridPos = new Vector2(75f, -75f);
mLeftPadding = 50;
mCellSize = 150f;
InitGrid(6);
}
[ContextMenu("7x7 생성")]
public void Create7x7()
{
mPos = new Vector2(237f, 100f);
mSlotSize = new Vector2(1080f, 1000f);
mMainGridPos = new Vector2(65f, -65f);
mLeftPadding = 40;
mCellSize = 130f;
InitGrid(7);
}
[ContextMenu("8x8 생성")]
public void Create8x8()
{
mPos = new Vector2(237f, 87f);
mSlotSize = new Vector2(1080f, 1000f);
mMainGridPos = new Vector2(57.5f, -57.5f);
mLeftPadding = 30;
mCellSize = 115f;
InitGrid(8);
}
[ContextMenu("9x9 생성")]
public void Create9x9()
{
mPos = new Vector2(237f, 76f);
mSlotSize = new Vector2(1080f, 1020f);
mMainGridPos = new Vector2(50f, -50f);
mLeftPadding = 30;
mCellSize = 100f;
InitGrid(9);
}
[ContextMenu("삭제")]
public void DeleteGrid()
{
#if UNITY_EDITOR
// 삭제 후 클리어
DestroyImmediate(gridObj);
listSlot.Clear();
#endif
}
public void ResetLevel(char[,] map)
{
// 초기화
PipeManager.instance.Reset();
dicEntryPointSlot.Clear();
DeleteGrid();
// 크기에 따른 게임판 초기화
switch (map.GetLength(0))
{
case 5: Create5x5(); break;
case 6: Create6x6(); break;
case 7: Create7x7(); break;
case 8: Create8x8(); break;
case 9: Create9x9(); break;
}
SetLevel(map);
}
}
2) 연결 파이프 (판정 및 점수 획득) - PipeManager.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
using Color = UnityEngine.Color;
public struct PipeInfo : IEquatable<PipeInfo>
{
public Color color;
public Vector3 pos;
public int idx;
public PipeInfo(Color color, Vector3 pos, int idx)
{
this.color = color;
this.pos = pos;
this.idx = idx;
}
public PipeInfo(Slot slot) : this(slot.color, slot.pipePos, slot.idx)
{
}
public static bool operator ==(PipeInfo obj1, PipeInfo obj2)
{
if (ReferenceEquals(obj1, obj2))
return true;
if (ReferenceEquals(obj1, null) ||
ReferenceEquals(obj2, null))
return false;
return obj1.Equals(obj2);
}
public static bool operator !=(PipeInfo obj1, PipeInfo obj2) => !(obj1 == obj2);
public bool Equals(PipeInfo other)
{
if (ReferenceEquals(other, null))
return false;
if (ReferenceEquals(this, other))
return true;
return color.Equals(other.color) &&
pos.Equals(other.pos) &&
idx.Equals(other.idx);
}
}
public class PipeManager : SingletonLocal<PipeManager>
{
public Dictionary<Color, Pipe> dicLine = new Dictionary<Color, Pipe>();
public Dictionary<Vector3, Color> dicLinePos = new Dictionary<Vector3, Color>();
// 시작지점 위치랑 비교용
public Dictionary<Color, Vector3> dicStartPos = new Dictionary<Color, Vector3>();
public Pipe pipe = null;
public Vector3 curPipePos;
public bool canMove = false;
// Update is called once per frame
void Update()
{
CheckIfCompleted();
//if (Global.IsNullOrEmpty(mArrPoints))
// return;
//for (int i = 0; i < mArrPoints.Length; i++)
// mLineRenderer.SetPosition(i, mArrPoints[i].position);
}
void UpdatePipeLocations()
{
// 위치 갱신
dicLinePos.Clear();
foreach (var line in dicLine)
{
var lineRenderer = line.Value.lineRenderer;
for (int i = 0; i < lineRenderer.positionCount; i++)
{
if (!dicLinePos.ContainsKey(lineRenderer.GetPosition(i)))
dicLinePos.Add(lineRenderer.GetPosition(i), line.Key);
}
}
}
public void InitPipe(PipeInfo info)
{
// 비어있을 시 초기화 해줄 필요 없음
if (info == new PipeInfo())
return;
curPipePos = info.pos;
// 파이프가 존재함
if (dicLine.ContainsKey(info.color))
DestroyPipe(info.color);
CheckForOtherPipe(info.color);
Pipe tmpPipe = Instantiate(pipe, transform);
var lineRenderer = tmpPipe.lineRenderer;
lineRenderer.startColor = info.color;
lineRenderer.endColor = info.color;
dicLine.Add(info.color, tmpPipe);
dicStartPos.Add(info.color, info.pos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(tmpPipe.idx, info.pos);
}
public void DestroyPipe(Color color)
{
// 지점 초기화
var dicCompleted = GameManager.instance.dicCompleted;
if (dicCompleted.ContainsKey(color))
dicCompleted[color] = false;
// 색상 라인 오브젝트 제거
GridManager.instance.listSlot[dicLine[color].slotIdx].DeleteLineEndPoint();
DestroyImmediate(dicLine[color].gameObject);
dicLine.Remove(color);
dicStartPos.Remove(color);
UpdatePipeLocations();
}
public void AddPos(PipeInfo info)
{
if (!canMove)
return;
// 파이프가 존재함
CheckIfDiagonalMove(info);
Pipe pipe = null;
dicLine.TryGetValue(info.color, out pipe);
if (!pipe)
return;
// 직선 확인
curPipePos = info.pos;
CheckForOtherPipe(info.color);
// 다른 선이 존재하는지 확인 (예 ) 시작지점 클릭 후 다른 시작 지점에서 클릭)
var lineRenderer = pipe.lineRenderer;
var lineCount = lineRenderer.positionCount;
if (lineRenderer.GetPosition((lineCount > 0) ? lineCount - 1 : 0) == info.pos)
return;
if (!dicLinePos.ContainsKey(info.pos))
dicLinePos.Add(info.pos, info.color);
lineRenderer.positionCount++;
lineRenderer.SetPosition(++dicLine[info.color].idx, info.pos);
pipe.slotIdx = info.idx;
canMove = true;
}
// 다른 색상의 선이 존재하는지 확인
void CheckForOtherPipe(Color color)
{
if (dicLinePos.ContainsKey(curPipePos))
{
var colorToCheck = dicLinePos[curPipePos];
if (dicLine.ContainsKey(colorToCheck) &&
colorToCheck != color)
DestroyPipe(colorToCheck);
}
}
// 대각선에 다른 선이 있는지 확인
void CheckForOtherPipe(Vector3 linePos)
{
if (dicLinePos.ContainsKey(linePos))
DestroyPipe(dicLinePos[linePos]);
}
// 대각선마다 체크
public void CheckIfDiagonalMove(PipeInfo info)
{
var slotList = GridManager.instance.listSlot;
int idx = info.idx;
var size = GameManager.instance.size;
float y = curPipePos.y, x = curPipePos.x, val = 0.86f;
Vector3 upLeft = new Vector3(x - val, y + val);
Vector3 upRight = new Vector3(x + val, y + val);
Vector3 downLeft = new Vector3(x - val, y - val);
Vector3 downRight = new Vector3(x + val, y - val);
Pipe pipe = null;
dicLine.TryGetValue(info.color, out pipe);
if (!pipe)
return;
var lineRenderer = pipe.lineRenderer;
// 좌상 또는 좌하
if (info.pos == upLeft ||
info.pos == downLeft)
{
if (idx + 1 < size)
{
var nextSlot = slotList[idx + 1];
var nextPipePos = nextSlot.pipePos;
CheckForOtherPipe(nextPipePos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(++dicLine[info.color].idx, nextPipePos);
}
}
// 우상 또는 우하
else if (info.pos == upRight ||
info.pos == downRight)
{
if (idx - 1 >= 0)
{
var nextSlot = slotList[idx - 1];
var nextPipePos = nextSlot.pipePos;
CheckForOtherPipe(nextPipePos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(++dicLine[info.color].idx, nextPipePos);
}
}
}
// 두 점이 이어졌는지 확인
void CheckIfCompleted()
{
foreach (var item in dicLine)
{
var lineRenderer = item.Value.lineRenderer;
var startPos = lineRenderer.GetPosition(0);
var endPos = lineRenderer.GetPosition((lineRenderer.positionCount > 0) ? lineRenderer.positionCount - 1 : 9);
if (startPos == endPos)
continue;
var pointSlot = GridManager.instance.dicEntryPointSlot;
Slot startSlot, endSlot;
pointSlot.TryGetValue(startPos, out startSlot);
pointSlot.TryGetValue(endPos, out endSlot);
if (startSlot &&
endSlot)
GameManager.instance.dicCompleted[item.Key] = startSlot.IsEndPoint(item.Key) && endSlot.IsEndPoint(item.Key);
}
}
// 시작 또는 도착 지점인지 확인
bool IsEntryPoint(PipeInfo info)
{
return false;
}
// 도착 지점이 현재 색상하고 맞는지 체크
public bool OtherPointExists(Slot curSlot, Slot nextSlot)
{
return false;
}
public bool CanDeletePoint(Color color, Vector3 pos)
{
if (dicLine.ContainsKey(color))
{
var lineRenderer = dicLine[color].lineRenderer;
var lineCount = lineRenderer.positionCount;
Vector3 lastPos = lineRenderer.GetPosition(lineCount - 1);
for (int i = 0; i < lineCount; i++)
{
if (pos == lineRenderer.GetPosition(i))
{
lineRenderer.positionCount = i + 1;
dicLine[color].idx = i;
UpdatePipeLocations();
return true;
}
}
}
return false;
}
public bool IsStartPoint(Color color, Vector3 size)
{
if (dicLine.ContainsKey(color))
{
var lineRenderer = dicLine[color].lineRenderer;
if (lineRenderer.positionCount == 2 &&
size == new Vector3(150f, 150f))
{
DestroyPipe(color);
return true;
}
}
return false;
}
// 비활성화 함수
public void Reset()
{
foreach (var line in dicLine)
DestroyImmediate(line.Value.gameObject);
//line.Value.gameObject.SetActive(false);
dicLine.Clear();
dicLinePos.Clear();
dicStartPos.Clear();
}
}
'게임엔진 > 개인 플젝' 카테고리의 다른 글
[Outdated] 농장 시뮬레이터 개인작 (0) | 2024.04.30 |
---|---|
플레이어 방향에 따른 발사 위치 변경 (0) | 2024.04.23 |
Flow Free 3 - UI (0) | 2024.03.27 |
Flow Free 2 - 슬롯(그리드)과 파이프(연결선) (0) | 2024.03.27 |
Flow Free 1 - 그리드 (맵) 생성 (0) | 2024.03.27 |