ShovelingLife
A Game Programmer
ShovelingLife
전체 방문자
오늘
어제
  • 분류 전체보기 (1071) N
    • 그래픽스 (57)
      • 공통 (19)
      • 수학 물리 (22)
      • OpenGL & Vulkan (1)
      • DirectX (14)
    • 게임엔진 (183) N
      • Unreal (69)
      • Unity (103) N
      • Cocos2D-X (3)
      • 개인 플젝 (8)
    • 코딩테스트 (221)
      • 공통 (7)
      • 프로그래머스 (22)
      • 백준 (162)
      • LeetCode (19)
      • HackerRank (2)
      • 코딩테스트 알고리즘 (8)
    • CS (235)
      • 공통 (21)
      • 네트워크 (44)
      • OS & 하드웨어 (55)
      • 자료구조 & 알고리즘 (98)
      • 디자인패턴 (6)
      • UML (4)
      • 데이터베이스 (7)
    • 프로그래밍 언어 (347) N
      • C++ (167)
      • C# (89) N
      • Java (9)
      • Python (33)
      • SQL (30)
      • JavaScript (8)
      • React (7)
    • 그 외 (9)
      • Math (5)
      • 일상 (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • Source Code 좌측 상단에 복사 버튼 추가 완료
  • 언리얼 엔진 C++ 빌드시간 단축 꿀팁
  • 게임 업계 코딩테스트 관련
  • 1인칭 시점으로 써내려가는 글들

인기 글

태그

  • Unity
  • 티스토리챌린지
  • 클래스
  • 포인터
  • C
  • 유니티
  • c#
  • 프로그래머스
  • string
  • 그래픽스
  • 오블완
  • 언리얼
  • 백준
  • 배열
  • 파이썬
  • SQL
  • 함수
  • C++
  • 문자열
  • 알고리즘

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
ShovelingLife

A Game Programmer

Flow Free 2 - 슬롯(그리드)과 파이프(연결선)
게임엔진/개인 플젝

Flow Free 2 - 슬롯(그리드)과 파이프(연결선)

2024. 3. 27. 16:44

게임 매니저가 맨 후순위로 와야한다 

슬롯 생성

맵에 존재하는 모든 그리드들이 Slot.cs 기반이 될거다, 실제로는 임의의 위치에 동그라미 생성을 해야하기 때문에 2개의 이미지를 겹쳐놨다, 추가로 Slot.cs는 선 색상, 이어서 할 수 있는 동그라미 정보 등 Grid 관련 데이터를 들고 있을거다.

 

 

아래는 옵션

 

Slot.cs

using UnityEngine;
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() => img.sprite == null;

    public bool IsEndPoint() => rect.sizeDelta == new Vector2(150f, 150f);

    // 
    public bool IsLineEndPoint() => rect.sizeDelta == new Vector2(100f, 100f);

    public bool IsEndPoint(Color color) => img.color == color && img.sprite.name == "Knob";

    public Sprite GetSprite() => img?.sprite;

    public Vector3 GetSize() => rect.sizeDelta;

    public PipeInfo GetPipeInfo() => new PipeInfo(color, pipePos, idx);
}

파이프 생성

파이프 매니저에서 모든 선에 대한 로직을 담당한다, 하나의 선이 이어져 있는걸 판별하기 위해선 예) 빨강색 원끼리 선으로 이어져 있다. Dictionary<Color,bool>을 만들어서 선이 동일한 색상의 원에 도달했을 때 이에 대해 true로만 바꿔주면 된다.

using System;
using System.Collections.Generic;
using UnityEngine;
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>
{
    GridManager gridManager;

    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;

    private void Awake()
    {
        gridManager = GridManager.instance;
    }

    // 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.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);
        dicLine.TryGetValue(info.color, out Pipe 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.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.dicEntryPointSlot;
            pointSlot.TryGetValue(startPos, out Slot startSlot);
            pointSlot.TryGetValue(endPos, out Slot 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();
    }
}

 

마우스로 드래그 할 때 선을 생성 해줘야하므로 게임매니저가 이를 관리한다

드래그 시작할 땐 원이 있는 슬롯인지를 레이캐스트로 판별한 후 파이프를 초기화 시킨다.

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;
    }
}
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;
}

 

public void OnDrag(PointerEventData eventData) => CheckIfDragging(eventData, UI_Manager.instance.CheckForHoveredItem());

public void OnPointerUp(PointerEventData eventData) => ResetPipe();

public void OnEndDrag(PointerEventData eventData) => ResetPipe();

 

드래그 중일 땐 파이프를 연장할 지 없앨 지를 판별한다

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;

                UI_Manager.instance.gameUI.UpdateMoveTxt();

                // 다른 선일 때 이동 가능
                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;
        }
    }
}

 

파이프를 초기화 시키는 함

void ResetPipe()
{
    if (mCurSlot)
    {
        dicCompleted[mCurSlot.color] = false;
        mCurSlot = null;
        PipeManager.instance.curPipePos = Vector3.zero;
        PipeManager.instance.canMove = true;
    }
}

저작자표시 (새창열림)

'게임엔진 > 개인 플젝' 카테고리의 다른 글

[Outdated] 농장 시뮬레이터 개인작  (0) 2024.04.30
플레이어 방향에 따른 발사 위치 변경  (0) 2024.04.23
Flow Free 3 - UI  (0) 2024.03.27
Flow Free 1 - 그리드 (맵) 생성  (0) 2024.03.27
[Unity] Flow Free 게임 카피캣 만들기  (0) 2023.12.05
    '게임엔진/개인 플젝' 카테고리의 다른 글
    • 플레이어 방향에 따른 발사 위치 변경
    • Flow Free 3 - UI
    • Flow Free 1 - 그리드 (맵) 생성
    • [Unity] Flow Free 게임 카피캣 만들기
    ShovelingLife
    ShovelingLife
    Main skill stack => Unity C# / Unreal C++ Studying Front / BackEnd, Java Python

    티스토리툴바