[Unity] Electricity (5)

suhan0304·2024년 5월 27일

유니티-Electricity

목록 보기
5/18
post-thumbnail

Start Node

스타팅 포인트에는 건물을 건설할 수 없도록 설정해주자. 스타트 포인트를 자식 오브젝트에 넣어서 스타트 포인트를 가지고 있는 노드는 알아서 isBuildable이 False가 되도록 하려고 했으나 그렇게 하면 맵이 커졌을때 모든 노드가 결국 자신의 자식 오브젝트를 한 번 씩 확인하고 시작한다는 점이 성능 측면에서 낭비가 심하다고 생각되서 다른 방법을 고안해봤다.

어차피 맵은 랜덤하게 생성되는 것이 아니라 미리 만들어놓은 맵을 로딩해오는 방식으로 진행할 예정이기 때문에 스타팅 포인트와 노드를 하나로 합쳐서 스타팅 노드를 아예 새로 만들어도 괜찮겠다고 생각했다.

아예 Start Node를 따로 만들어주고 Buildable을 False로 설정해주었다.


전기 로직

이제 전기가 흐르는 로직을 구현해보자. 일단 가장 먼저 전기가 흐르는 원리를 아래와 같이 그림판으로 그려보았다

이런 식으로 Starting Point에서 전기가 공급되고 인접한 블럭에 전기가 공급되는 방식으로 구현하려고 한다. 일단 블럭은 ON, OFF, INSULATOR 이렇게 세 가지 상태를 가진다.

  • ON : 현재 전기가 들어와 있는 상태
    - light 효과를 추가해 줄 듯? 아직은 미정
  • OFF : 전기가 들어오지 않는 상태 (기본)
  • INSULATOR : 부도체 (전기가 흐를 수 없음)
    - 전기가 흐를 수 없는 블럭도 배치해서 난이도를 조절할 예정

기존 블럭 디자인이 가시성이 좋지를 못해서 블럭 디자인을 간단히 수정해주었다. 전반적인 오브젝트의 다지인이나 색감은 전체 게임 로직을 모두 구현한 후에 수정해줄 예정이다.


Block 업데이트

블럭 코드를 한 번 수정해보자. 원래는 Block 내부에서 Enum 형으로 BlockState를 유지하려 했는데 실제로 End Point에서 블럭의 상태를 알아야하고 실제 전기 흐름 로직을 구현하는데 BlockState를 따로 선언해주는 것이 편리할 것 같아서 아래와 같이 작성해주었다.

BlockState.cs

public enum BlockState
{
    ON,
    OFF,
    INSULATOR
}

Block.cs

public BlockState currentState;


private void Start()
{
    currentState = BlockState.OFF;
    node = GetComponentInParent<Node>();
}

인접한 블럭에 전기가 흐르는지를 어떻게 알아차릴 수 있을까? 가장 쉽게 생각했던거는 Box Collider를 인접한 블럭들과 겹치도록 위치와 크기를 설정해줘서 Collider에 충돌하고 있는 오브젝트의 Block 컴포넌트를 가져와서 State에 접근하는 방식을 생각해보았다. 이 방법의 장단점은 아래와 같다.

  • 장점
    • 구현이 굉장히 쉽다
    • 동적인 상황에서 유용 (블럭이 움직일 때)
  • 단점
    • 물리 엔진을 사용하는 만큼 성능에 영향
    • 정적인 맵 구조에서는 비효율적

쉽게 말해서 블럭이 많아지면 많아질 수록 엔진 자체의 Collider 처리량이 많아지면서 성능이 비효율적으로 된다. 다른 방식으로는 맵 크기에 해당하는 3차원 배열을 사용하여 맵의 모든 블럭을 관리하는 방법을 생각해봤다. 블럭을 설치할때마다 적절한 배열 위치에 블럭을 추가해주는 방식이다. 이 방법의 장단점은 아래와 같다.

  • 장점
    • 인접한 블럭을 빠르게 조회 (배열의 옆자리만 확인하면 됨)
    • 물리 엔진을 따로 사용하지 않아서 성능이 우수
  • 단점
    • 맵 구조가 동적이거나 블럭의 위치가 변경되면 처리가 어려움
    • 구현이 복잡하다

이 때 추가적으로 생각한 것이 3차원 배열로 블럭의 위치를 결정하려면 노드를 누를 때 블럭을 생성하면서 적절한 위치의 배열 값을 수정해야한다는 점인데 그러면 노드도 수정해 주어야한다. 그리고 맵이 아무리 커져서 필요한 블럭의 개수가 아무리 많아진다고 해도 크게 성능에 영향을 줄 만큼 복잡한 로직이 아니기 때문에 첫 번째 방법을 사용하기로 했다.


Block - Collider 추가

다음과 같이 (상하전후좌우)에 위치한 블럭들을 확인하기 위해 BoxCollider를 생성해주었다.

이제 Block에서 BoxCollider에 들어와있는 Block Object를 가져오는 함수를 아래와 같이 작성했다.

Physics.OverlapBox : 충돌체 내부에 있는 Collider를 감지하는 함수, 자세한 설명은 유니티 공식 문서 참고

Block.cs


/// <summary>
/// Get Block in Colliders
/// </summary>
public List<Block> GetBlockInColliders()
{
    List<Block> blocksInColliders = new List<Block>();

    foreach (var collider in  colliders)
    {
        Collider[] hitColliders = Physics.OverlapBox(collider.bounds.center, collider.bounds.extents, collider.transform.rotation);
        foreach (var hitCollider in hitColliders)
        {
            Block hitBlock = hitCollider.GetComponent<Block>();
            if (hitBlock != null && hitBlock != this && !blocksInColliders.Contains(hitBlock)) 
            { 
                blocksInColliders.Add(hitBlock);
            }
        }
    }

    return blocksInColliders;
}

근데 이렇게 했더니 새로 생성하는 블럭은 Collider에 Overlap 되는 블럭 오브젝트를 잘 찾아왔으나 이미 건설되어있는 블럭 오브젝트는 업데이트가 되지 않았다. 따라서 인접하게 불러온 Block List의 원소들마다 다시 GetBlockInColliders를 실행시켜주도록 한다.

/// <summary>
/// Update Block State (After updating adjacent blocks, it retrieves their states and updates its own state.)
/// </summary>
public void UpdateBlockState()
{
    AdjacentBlocks = GetBlockInColliders();
    foreach (var block in AdjacentBlocks)
    {
        block.UpdateBlockState();
    }
}

이렇게 했더니 문제점이 옆에 블럭을 가서 인접한 블럭들을 또 업데이트하면서 나한테 다시 돌아와서 Update를 실행하게 되서 서로 UpdateBlockState를 주고 받으면서 무한반복하는 문제가 발생했다.

그냥 간단히 수정해서 고쳤다. 블럭을 생성하면 자신과 인접한 모든 블럭 오브젝트들의 AdjecentBlocks에 자신을 추가해주는 함수를 호출해주도록 했다. 즉, 블럭이 생성되면 이웃 블럭들한테 "자신을 이웃 목록에 추가해주세요."하는 함수를 호출하도록 했다.

/// <summary>
/// Update Block State (After updating adjacent blocks, it retrieves their states and updates its own state.)
/// </summary>
public void UpdateBlockState()
{
    AdjacentBlocks = GetBlockInColliders();
    foreach (var adjacentBlock in AdjacentBlocks)
    {
        adjacentBlock.AddBlockToAdjacentBlock(this);
    }
}

이제 인접한 블럭들 리스트 (Adjacent Blocks)가 잘 유지된다.


에러

이게 블럭에도 OnMouse로 노드 이벤트들을 호출했는데 Block의 박스 콜라이더가 추가되면서 클릭 범위가 이상해졌다.

다음과 같이 빈 오브젝트를 하나 더 만들어서 BlockMouseEvent에 BlockMouseEvent.cs를 따로 작성해줘서 해당 오브젝트가 지닌 콜라이더에서 마우스 이벤트가 처리되도록 구현해주었다.

BlockMouseEvent.cs

using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

public class BlockMouseEvent : MonoBehaviour
{
    [SerializeField]
    public Block block = null;
    public Node node = null;

    private void Start()
    {
        block = GetComponentInParent<Block>();
        node = block.node;
    }
    public void OnMouseEnter() // When the mouse leaves the object collider
    {
        if (node != null)
        {
            node.OnMouseEnter();
        }
    }

    public void OnMouseExit() // When the mouse leaves the object collider
    {
        if (node != null)
        {
            node.OnMouseExit();
        }
    }

    public void OnMouseDown() //When the mouse click the object collider
    {
        if (node != null)
        {
            // Build a Block
        }
        node.OnMouseDown();
    }
}

추후 계획

이제 인접한 블럭들까지 가져오는데 성공했으므로 다음번엔 전기 상태가 전파 되는 로직만 구현하면 된다. 전기가 흘러 end Point까지 도달하는 로직까지 구현하면 메인 게임에 필요한 로직 기반은 거의 대부분 구현했다고 할 수 있다. 일단 게임 로직을 모두 구현한 다음 기타 효과들을 추가해보자.

profile
Be Honest, Be Harder, Be Stronger

0개의 댓글