본문 바로가기
프로젝트/Lucky Defense (InGame Clone)

유닛 배치 필드 구현

by argentdarae 2025. 3. 14.
반응형

개요

유저가 직접 조작하는 유닛 배치 필드 구현 회고, 그리고 소개하는 글이다

 

 

내용

유닛 배치 필드 소개

유닛 배치 필드

 

플레이어가 소환한 유닛이 배치되고, 유닛 배치 변경 및 플레이어 입력을 받아 특정 동작을 수행할 수 있는 영역이다

 

이 글을 통해 소개하고 싶은 부분은 UnitPlacementNode와 UnitGroup을 이용하여 어떻게 내부 상태를 관리하는지이다 

 

UnitPlacementNode

각 Side마다 존재하는 18개의 노드

 

UnitPlacementNode는 유닛이 배치되는 단일 노드로, 각 사이드에 18개씩 존재하여 유닛 배치를 관리하는 역할을 한다

 

유닛의 배치 상태를 관리하고, 유닛 추가 및 삭제, 그리고 위치 조정을 처리한다. 또한 내부의 UnitGroup 클래스를 통해 유닛의 등급, 타입, 개수 등을 관리하며, 배치 가능 여부를 판단할 수 있도록 했다

 

UnitPlacementNode - UnitGroup 클래스를 통해 구현된 구조의 핵심은 노드와 관련된 동작을 추상화하고 유닛과 관련된 동작 및 데이터는 UnitGroup에 의존하게 설계하여 책임을 명확하게 분리한 것이다

public sealed class UnitPlacementNode : MonoBehaviourBase
{
    public UnitGroup UnitGroup = new UnitGroup();

    public bool CanAcceptUnit(SUnitSpawnRequestData requestData)
    {
        if (UnitGroup.IsFull())
            return false;

        if (UnitGroup.IsEmpty())
            return true;

        return UnitGroup.UnitType == requestData.UnitType &&
               UnitGroup.UnitGrade == requestData.UnitGrade;
    }

    public void AddUnit(UnitRoot unit)
    {
        UnitGroup.AddUnit(unit);
        UnitGroup.UpdateUnitInfo();
        RearrangeUnitPositions();
    }

    public void SubUnit()
    {
        UnitGroup.SubUnit();
        UnitGroup.UpdateUnitInfo();
        RearrangeUnitPositions();
    }

    private void RearrangeUnitPositions()
    {
        UnitGroup.SetPositions(GetUnitPositions());
    }

    public async UniTask SwapWith(UnitPlacementNode targetNode)
    {
        // 유닛 그룹 교환
        (UnitGroup, targetNode.UnitGroup) = (targetNode.UnitGroup, UnitGroup);

        // 이동할 목표 위치 설정
        Transform[] myTargetPositions = GetUnitPositions();
        Transform[] targetNodePositions = targetNode.GetUnitPositions();

        // 유닛 이동 (모두 이동 완료될 때까지 대기)
        await UniTask.WhenAll(
            UnitGroup.MoveToTargetNode(myTargetPositions),
            targetNode.UnitGroup.MoveToTargetNode(targetNodePositions)
        );

        UnitGroup.UpdateUnitInfo();
        targetNode.UnitGroup.UpdateUnitInfo();
        RearrangeUnitPositions();
        targetNode.RearrangeUnitPositions();
    }

    // ...
}

 

UnitGroup

UnitGroup은 하나의 배치 노드에 속한 유닛들의 집합과 정보를 관리하는 클래스다

각 UnitPlacementNode 내부에서 유닛 배치 상태를 유지하며 그룹에 대한 유닛 추가, 삭제, 이동과 같은 동작을 수행한다

public class UnitGroup
{
    // ...

    public void AddUnit(UnitRoot unit)
    {
        AssertHelper.NotEqualsValue(typeof(UnitGroup), UnitCount, MAX_UNIT_COUNT);

        _placedUnits.Add(unit);
        UpdateUnitInfo();
    }

	public void SubUnit()
    {
        if (IsEmpty()) return;

        int lastIndex = _placedUnits.Count - 1;
        UnitRoot unit = _placedUnits[lastIndex];

        AssertHelper.NotNull(typeof(UnitGroup), unit);
        unit.ReleaseObject();

        _placedUnits.RemoveAt(lastIndex); // 마지막 유닛 제거

        if (!IsEmpty()) 
        {
            UpdateUnitInfo(); // 남아 있는 유닛 기준으로 UnitType, UnitGrade 갱신
            return;
        }

        Clear();
    }

    public void UpdateUnitInfo()
    {
        if (IsEmpty())
        {
            Clear();
            return;
        }

        UnitGrade = _placedUnits[0].grade;
        UnitType = _placedUnits[0].type;
    }

    private void Clear()
    {
        _placedUnits.Clear(); // 리스트 비우기
        UnitGrade = EUnitGrade.None;
        UnitType = EUnitType.None;
    }

    public bool IsFull() => UnitCount >= MAX_UNIT_COUNT || UnitGrade == EUnitGrade.Mythic;

    public bool IsEmpty() => _placedUnits.Count == 0;

    public void SetPositions(params Transform[] targetPositions)
    {
        for (int i = 0; i < UnitCount; ++i)
        {
            _placedUnits[i].transform.position = targetPositions[i].position;
        }
    }

    public async UniTask MoveToTargetNode(params Transform[] targetPositions)
    {
        for (int i = 0; i < MAX_UNIT_COUNT; ++i)
        {
            _moveTasks[i] = default;
        }

        for (int i = 0; i < UnitCount; ++i)
        {
            _moveTasks[i] = _placedUnits[i].MoveController.MoveToTarget(targetPositions[i]);
        }

        await UniTask.WhenAll(_moveTasks);
    }

    public float GetAttackRange()
    {
        UnitMetaData data = RootManager.Ins.DataManager.UnitResources.GetResource(UnitGrade, UnitType);
        AssertHelper.NotNull(typeof(UnitGroup), data);

        return data.AttackRange;
    }
}

 

 

마무리

이번 유닛 배치 시스템 구현에서는 UnitPlacementNodeUnitGroup을 분리하여 책임을 명확하게 구분하는 구조를 설계했다

  • UnitPlacementNode는 배치 위치 및 노드 간 유닛 이동을 담당하고,
  • UnitGroup은 배치된 유닛의 속성을 관리하고 상태를 갱신하는 역할을 수행한다.

만약 배치 필드에서 추가적으로 유저가 스킬을 사용한다라는 기능을 구현해야 한다면 확장만 하면 된다. 즉 개방-폐쇄 원칙(OCP) 또한 잘 지켜지고 있다