건축 데이터 저장 / 복원 시스템 ( PlacementManager (7) )

목차

1. 요구 사항

2. 흐름도

        2.1. 저장

       2.2. 복원

3. 구현

        3.1. 저장 데이터 추출

       3.2. 복원용 배치 함수

       3.3. 전체 데이터 초기화 함수

4. 개발 의도

1. 시스템 요구 사항

Grid 기반 건축 시스템에서는 플레이어가 구조물을 배치하고 제거하는 과정을 통해 맵의 상태를 지속적으로 변경하게 된다.

이때 게임을 종료하거나 씬을 이동한 이후에도 이전에 구성한 건축 상태를 그대로 유지하기 위해서는, 현재 건축 데이터를 저장하고 이후 다시 복원하는 기능이 반드시 필요하다.

건축 시스템의 상태는 단순히 GameObject의 존재 여부로만 표현되지 않는다.

실제 씬에는 구조물이 GameObject 형태로 존재하지만, 건축 시스템의 논리적 상태는 Grid 데이터 시스템을 통해 관리된다.

구조물의 위치, 점유 셀, 회전 값, 구조물 종류와 같은 정보는 GridData 내부에 저장되어 있으며, 이 데이터가 실제 GameObject 상태의 기준이 된다.

따라서 저장 시스템은 단순히 GameObject의 Transform을 기록하는 방식이 아니라, Grid 데이터 자체를 저장하고 이를 기반으로 구조물을 다시 생성하는 방식으로 구성되어야 한다.

만약 GameObject 상태만 저장하게 되면 이후 공간 검사, 충돌 판정, 배치 가능 여부 판단과 같은 시스템이 정상적으로 동작하지 않을 수 있다.

또한 건축 시스템은 셀 기반 배치와 경계 기반 배치를 모두 지원하기 때문에, 저장 데이터 역시 이 두 가지 구조를 모두 포함할 수 있어야 한다.

각 구조물은 단순히 위치만이 아니라 크기, 회전, 배치 타입 등의 정보를 함께 가지고 있어야 복원 시 동일한 상태를 재현할 수 있다.

복원 과정에서는 저장된 Grid 데이터를 기반으로 실제 GameObject를 다시 생성해야 하며, 이때 기존 배치 시스템과 동일한 로직을 재사용하여 시스템의 일관성을 유지해야 한다.

저장 시스템은 독립적인 로직이 아니라, 기존 Placement 시스템과 연결된 형태로 설계되어야 한다.

결과적으로 건축 데이터 저장 / 복원 시스템은 단순한 직렬화 기능이 아니라, Grid 데이터와 씬 오브젝트 상태를 연결하는 핵심 데이터 계층으로 동작해야 한다.

2. 흐름도

2.1. 저장

현재 Grid 상태

   ↓

PlacementManager.GetDataToSave()

   ↓

GridData.GetSaveData()

   ↓

GridSaveData 생성

   ↓

파일 / PlayerPrefs 저장

저장 단계에서는 현재 GridData 내부에 있는 정보를 추출하여 별도의 저장용 데이터 구조로 변환한다.

이 데이터는 이후 직렬화되어 파일이나 PlayerPrefs 등에 저장된다.

2.2. 복원

저장 데이터 로드

   ↓

GridSaveData

   ↓

PlacementManager.PlaceStructureAt()

   ↓

Grid 좌표 → 월드 좌표 변환

   ↓

StructurePlacer를 통한 GameObject 생성

   ↓

GridData 동기화

복원 단계에서는 저장된 Grid 데이터를 다시 읽어들인 뒤, 이를 기반으로 구조물을 다시 배치한다.

이때 단순히 GameObject를 생성하는 것이 아니라, PlacementManager의 기존 배치 로직을 재사용하여 Grid 데이터와 씬 상태를 동시에 복원한다.

3. 구현

3.1. 저장 데이터 추출
public GridSaveData GetDataToSave()
{
    return gridData.GetSaveData();
}

이 함수는 현재 건축 상태를 저장 가능한 데이터 형태로 변환하는 진입점 역할을 한다.

PlacementManager는 GridData를 직접 외부에 노출하지 않고, 이 함수를 통해 필요한 데이터만 추출하도록 설계되어 있다.

여기서 중요한 점은 저장 대상이 GameObject가 아니라 GridData라는 것이다.

gridData는 현재 건축 상태의 논리적 표현이며, 어떤 구조물이 어디에 있고 어떤 형태로 배치되어 있는지를 모두 포함하고 있다.

이 함수는 내부적으로 GridData의 GetSaveData()를 호출하여 GridSaveData를 생성한다.

GridSaveData는 직렬화 가능한 형태로 구성된 데이터 객체이며, 실제 저장 시스템에서 사용되는 최종 데이터 구조이다.

이 구조를 사용한 이유는 Unity의 GameObject는 직렬화에 적합하지 않기 때문이다.

GameObject는 씬에 종속된 객체이며, 그대로 저장하는 것은 불가능하거나 매우 비효율적이다.

대신 위치, 회전, ID와 같은 최소 정보만 저장하고, 이후 복원 시 다시 생성하는 방식이 일반적인 접근이다.

즉, 이 함수는 현재 건축 상태를 재현할 수 있는 최소 데이터를 추출하는 역할을 한다.

3.2. 복원용 배치 함수(저장 데이터 기반)
public void PlaceStructureAt(SelectionResult selectionResult, ItemData itemData)
{
    buildingState = new PlacingObjectsState(gridManager, gridData, itemData);

    List<Vector3> selectedPosition = selectionResult.selectedGridPositions
        .Select(x => gridManager.GetWorldPosition(x)).ToList();

    SelectionResult modifiedResuls = new()
    {
        selectedPositions = selectedPosition,
        selectedGridPositions = selectionResult.selectedGridPositions,
        selectedPositionsObjectRotation = selectionResult.selectedPositionsObjectRotation,
        selectedPositionGridCheckRotation = selectionResult.selectedPositionGridCheckRotation
    };

    PlaceStructureAt(modifiedResuls, buildingState.CurrentPlacementData, itemData, false);

    CancelState();
}

PlaceStructureAt 함수는 저장된 데이터를 기반으로 구조물을 복원하는 핵심 함수이다.

기존의 PlaceStructureAt과 이름은 같지만, 이 함수는 저장 시스템을 위해 특별히 구성된 오버로드 버전이다.

가장 먼저 수행되는 작업은 BuildingState를 생성하는 것이다.

buildingState = new PlacingObjectsState(gridManager, gridData, itemData);

이 코드는 복원 과정에서도 일반 배치 시스템과 동일한 흐름을 유지하기 위한 준비 단계이다.

건축 시스템은 항상 특정 상태를 기준으로 동작하기 때문에, 복원 과정에서도 배치 상태를 생성해야 이후 로직이 정상적으로 동작할 수 있다.

그 다음으로 수행되는 핵심 작업은 Grid 좌표를 월드 좌표로 변환하는 것이다.

List<Vector3> selectedPosition = selectionResult.selectedGridPositions
    .Select(x => gridManager.GetWorldPosition(x)).ToList();

저장 데이터는 Grid 좌표를 기준으로 저장되어 있기 때문에, 실제 구조물을 생성하려면 이를 월드 좌표로 변환해야 한다.

gridManager.GetWorldPosition()은 Grid 좌표를 실제 씬 위치로 변환하는 함수이며, 이 과정을 통해 저장 데이터가 실제 공간으로 매핑된다.

여기서 LINQ의 Select를 사용한 점도 의미가 있다

Select는 컬렉션의 각 요소를 변환하는 함수로, 코드 가독성을 높이는 장점이 있다.

다만 GC 할당이 발생할 수 있기 때문에 성능이 중요한 상황에서는 주의가 필요하다.

하지만 저장/복원은 빈번하게 호출되는 로직이 아니기 때문에 가독성을 우선한 선택으로 볼 수 있다.

다음으로 SelectionResult를 다시 생성한다.

SelectionResult modifiedResuls = new()
{
    selectedPositions = selectedPosition,
    selectedGridPositions = selectionResult.selectedGridPositions,
    selectedPositionsObjectRotation = selectionResult.selectedPositionsObjectRotation,
    selectedPositionGridCheckRotation = selectionResult.selectedPositionGridCheckRotation
};

이 과정이 필요한 이유는 기존 SelectionResult 구조가 “월드 좌표 기반 배치”를 전제로 하기 때문이다.

저장 데이터는 Grid 좌표만 가지고 있기 때문에, 월드 좌표를 포함한 새로운 SelectionResult를 만들어야 기존 배치 로직을 그대로 사용할 수 있다.

이 부분은 구조적으로 매우 중요한데, 복원 시스템이 별도의 배치 로직을 만들지 않고 기존 배치 시스템을 그대로 재사용한다는 점을 보여준다.

그 다음 실제 배치 함수가 호출된다.

PlaceStructureAt(modifiedResuls, buildingState.CurrentPlacementData, itemData, false);

여기서 마지막 인자인 false는 애니메이션 비활성화를 의미한다.

일반 배치에서는 구조물이 생성될 때 애니메이션이나 효과가 있을 수 있지만, 복원 과정에서는 이러한 연출이 필요하지 않기 때문에 이를 비활성화한다.

마지막으로 CancelState 함수를 호출하여, 상태를 정리한다.

이 함수는 복원 과정이 끝난 뒤 건축 상태를 초기화하는 역할을 한다.

복원은 내부적으로 배치 시스템을 사용하지만, 실제 플레이어가 건축 모드에 들어간 것은 아니기 때문에 상태를 유지하면 UI나 입력이 잘못 동작할 수 있다.

따라서 복원 후에는 상태를 정리하여 시스템을 정상 상태로 되돌린다.

이 시스템에서 가장 중요한 설계 포인트는 GameObject를 저장하지 않는다는 것이다.

대신 구조물의 ID, Grid 위치, 회전 값과 같은 최소 정보만 저장하고, 이를 기반으로 다시 생성한다.

이 방식의 장점은 명확하다.

첫째, 데이터가 매우 가볍다. GameObject를 그대로 저장하는 방식에 비해 저장 크기가 작고 직렬화가 쉽다.

둘째, 시스템 일관성이 유지된다. GridData를 기준으로 복원하기 때문에 이후 공간 검사나 충돌 판정이 정상적으로 동작한다.

셋째, 플랫폼 독립성이 높다. JSON, 바이너리 등 다양한 형태로 저장할 수 있다.

반면 단점도 존재한다.

복원 시 반드시 모든 구조물을 다시 생성해야 하기 때문에 초기 로딩 비용이 발생한다.

또한 생성 순서나 의존 관계가 있는 경우 추가 처리 로직이 필요할 수 있다.

하지만 건축 시스템에서는 데이터 일관성이 가장 중요하기 때문에, 이 구조가 가장 안정적인 선택이다.

3.3. 전체 데이터 초기화 함수
public void ClearAllPlacedObjects()
{
    structurePlacer.ClearAllPlacedObjects();
    gridData.Clear();
}

이 함수는 현재 씬에 존재하는 모든 건축 구조물과 Grid 데이터 상태를 동시에 초기화하는 역할을 한다.

단순히 GameObject를 삭제하는 것이 아니라, 건축 시스템의 논리 상태와 실제 씬 상태를 모두 초기 상태로 되돌리는 기능이다.

먼저 structurePlacer.ClearAllPlacedObjects()가 호출된다.

이 함수는 StructurePlacer 내부에서 관리하고 있는 모든 GameObject를 제거하는 역할을 한다.

StructurePlacer는 생성된 구조물의 인덱스를 기반으로 객체를 관리하고 있기 때문에, 이 함수는 내부 리스트를 순회하면서 모든 오브젝트를 Destroy하거나 비활성화하는 방식으로 동작한다.

이후 gridData.Clear()가 호출된다.

이 함수는 Grid 기반으로 저장된 모든 배치 정보를 초기화한다.

즉, 어떤 셀이 점유되어 있는지, 어떤 구조물이 어디에 배치되어 있는지에 대한 모든 논리 데이터를 제거한다.

여기서 중요한 점은 이 두 작업이 반드시 함께 수행되어야 한다는 것이다.

만약 GameObject만 제거되고 Grid 데이터가 남아 있다면, 시스템은 해당 위치를 여전히 점유된 것으로 인식하게 된다.

반대로 Grid 데이터만 초기화되고 GameObject가 남아 있다면, 씬과 데이터 간의 불일치가 발생하게 된다.

이 함수는 저장 데이터를 불러오기 전에 기존 상태를 초기화하거나, 새로운 게임을 시작할 때 주로 사용된다.

결과적으로 이 함수는 건축 시스템의 상태를 완전히 초기화하는 리셋 계층으로 동작하며, 저장 / 복원 시스템과 밀접하게 연결되는 핵심 유틸리티 함수이다.

4. 개발 의도

건축 데이터 저장 / 복원 시스템의 핵심 설계 의도는 건축 상태를 씬 상태가 아니라 데이터 상태로 관리하는 것이다.

건축 시스템에서 중요한 것은 GameObject 자체가 아니라, 구조물이 어디에 어떻게 배치되어 있는지에 대한 정보이다.

따라서 저장 시스템은 GameObject를 그대로 저장하는 방식이 아니라, Grid 데이터를 중심으로 상태를 기록하고 이를 기반으로 다시 생성하는 구조로 설계하였다.

또한 복원 과정에서 기존 배치 시스템을 그대로 재사용하도록 구성하여 시스템 일관성을 유지하였다.

이 구조 덕분에 복원 로직이 별도로 존재하지 않고, 배치 시스템을 통해 동일한 방식으로 구조물이 생성되기 때문에 유지보수성과 확장성이 크게 향상된다.

결과적으로 이 시스템은 단순한 저장 기능이 아니라, 건축 시스템의 상태를 데이터 기반으로 관리하고, 이를 실제 씬과 동기화하는 핵심 인프라로 설계되었다.