본문 바로가기
스파르타 내배캠

스파르타 내배캠 Unity 3기 16일차

by LemongO 2024. 1. 15.

 

 

 

 

오늘은 팀 과제에 세이브 기능을 넣었다.

처음엔 개인 과제에 있는 그대로 넣을까 싶었지만, 마음에 안 드는 코드여서 새로 만들어 보았다.

하지만 Json을 썼다는 거에 이변은 없다.

 


Save & Load

 

Save 시 저장되어야 하는 데이터

  • 플레이어 정보 (기본 수치, 직업, 직업별 스킬)
  • 아이템 리스트
  • 장착 아이템 리스트

 

저장 기능은 유틸성 클래스이다. 전역으로 사용할 예정이니 static 클래스로 만들었다.

internal static class DataManager
{
    private static readonly string? DIRECTORY_NAME   = Path.GetDirectoryName(Directory.GetCurrentDirectory());           
}

 

저장할 폴더의 이름인 DIRECTORY_NAME 변수에 현재 콘솔앱이 실행되는 폴더의 부모 폴더를 할당했다.

 

개인과제에서 했던 폴더명은 직접 내가 저장하고자 했던 폴더를 하드코딩으로 할당했는데,

그렇게 하면 다른 팀원들이 세이브 기능 사용 시 오류가 나기 때문에 

Path.GetDirectoryName() 메서드로 현재 콘솔앱이 실행되는 폴더를

Directory.GetCurrentDirectory() 메서드를 매개변수로 사용해 콘솔앱 폴더의 부모 폴더를 가져왔다.

굳이 부모 폴더를 가져온 이유는 단순히 그 폴더가 깨끗해서 보기가 편했기 때문이다.

 

Save

 

public static void SaveGame<T>(T data, string fileName)
{            
    string savePath = Path.Combine(DIRECTORY_NAME, fileName);
    string dataFile = JsonSerializer.Serialize(data, new JsonSerializerOptions() { WriteIndented = true });
    dataFile = Regex.Unescape(dataFile);

    File.WriteAllText(savePath, dataFile);
}

 

세이브는 어느 타입이든 받아와 저장할 수 있게끔 제너릭을 사용했다.

T 타입의 data를 받아와

DIRECTORY_NAME 폴더에 fileName 이름으로 저장을 한다.

Serialize와 Unescape 등의 기능은 이전 TIL 에서 작성한 내용이므로 넘어간다.

 

실제로 사용해보자.

case "X":
case "x":
    UI.ColoredWriteLine("※※※게임을 종료합니다※※※", ConsoleColor.Yellow);                        
    SaveGame(player, "PlayerData.json");                        
    return;

 

다소 코드가 생략되었지만 메인메뉴 씬에서 x를 입력해 게임종료시에 저장이 되도록 하였다.

저장될 타입으로 Player를, 파일명으로 "PlayerData.json" 을 매개변수로 넘겨줬다.

 

저장은 잘 된다.

 

기존 개인과제에서는 저장할 데이터를 총 세개로 파일화 시켰었는데, 그게 너무 보기싫었다.

그래서 PlayerData 파일에 모든것을 넣으려 하다보니 여러가지 코드 변경이 일어났다.

 

public List<int> Items              { get; private set; }
public List<int> EquippedItemsIndex { get; private set; }

 

먼저 보유 아이템, 장착 아이템 리스트들을 int 리스트화 시켜 프로퍼티로 만들었다.

int로 리스트화 시킨 이유는 아이템을 ID  저장할 것이기 때문이다.

 

리스트는 선언 과정에서 new List<int>() 를 사용하게 되면 Save 시, 저장이 안 된다는걸 알았다.

하지만 프로퍼티는 저장이 되길래 이 방법을 사용했다.

하지만 초기화는 해야하기에 이는 Player 생성자에서 하기로 했다.

 

또한 저장된 리스트들을 불러오기 시 가져와야하니 Player 생성자의 매개변수도 만들어 줬다.

this.Items = Items;
this.EquippedItemsIndex = EquippedItemsIndex;

if (this.Items == null)
    this.Items = new List<int>();
if (this.EquippedItemsIndex == null)
    this.EquippedItemsIndex = new List<int>();

 

위 처럼 캐릭터를 처음 생성할 땐, 생성자가 null 인 상태이다. 그렇기에 new List<int>() 로 초기화를 한다.

불러온 캐릭터가 있다면 null 이 아니므로 무시한다.

 

저장이 잘 된다.

 

일단 저장은 잘 되니 이제 불러오도록 하자

public static T? LoadGame<T>(string fileName)
{
    string savePath = Path.Combine(DIRECTORY_NAME, fileName);

    if (!File.Exists(savePath))
        return default;

    string data = File.ReadAllText(savePath);
    T? type = JsonSerializer.Deserialize<T>(data, new JsonSerializerOptions() { IncludeFields = true });

    return type;
}

 

불러올 때 역시 제너릭을 사용해 T 타입을 반환 하도록 한다.

 

매개변수로 fileName을 받아와 저장 폴더에 해당 파일이 있는지 File.Exists() 메서드로 확인 후,

없으면 default 값으로 null을 있으면 T 타입을 역직렬화를 통해 불러온 후 반환하도록 했다.

 

그럼 불러오기 해보도록 하자.

 

불러오기는 게임이 시작되는 EnterGame 메서드가 실행되자마자 player를 할당할 때 호출된다.

// 인게임에서 사용될 Player
private Player player = null;

#region 게임시작
public void EnterGame()
{
    player = LoadGame<Player>("PlayerData.json");

    if (player == null)
        CharacterCreation();
    else
        shop.Restore(player);
}

 

게임이 시작될 때, 전역변수인 player에 LoadGame<Player>("PlayerData.json") 으로 PlayerData.json 파일을 찾아
Player 타입으로 저장된 플레이어를 반환, 할당해줬다.

 

불러오기 후 저장된 플레이어가 없다면 캐릭터 생성을

저장된 플레이어가 있다면 상점 정보를 갱신해준다.

 

그럼 플레이어가 불러와졌으니 다음으로 아이템 정보를 갱신해주자.

 

그 전에 짚고 넘어가야 할 부분이 있다.

 

플레이어의 보유 아이템 리스트는 상점과 연동된다.

 

라는 점이다.

이 말은 플레이어가 따로 아이템 리스트를 가지고 있는것이 아니라,

상점에서 구매를 한 아이템을 확인 후 콘솔창에 출력해주는 방식이다.

 

이는 아이템 클래스의 IsBuy 프로퍼티로 확인이 가능하다.

public bool     IsBuy           { get; protected set; } = false;


public void GetItem() => IsBuy = true;

 

이제 불러오기 후, 아이템 정보를 갱신하자.

public void Restore(Player player)
{
    foreach(Item shopItem in items)
    {
        foreach(int playerItem in player.Items)
            if (playerItem == shopItem.ID)
                items[shopItem.ID].GetItem();
        foreach (int equippedItem in player.EquippedItemsIndex)
            if (equippedItem == shopItem.ID)
                player.EquipOrUnequipItem(items[shopItem.ID]);            
    }
}
  • 상점의 아이템과 플레이어의 보유 및 장착 아이템들을 하나씩 비교한다.
  • 플레이어의 보유 아이템(index) 과 상점 아이템의 ID 가 일치하면 [구매됨] 상태로 변경한다. => GetItem();
  • 플레이어의 장착 아이템(index) 과 상점 아이템의 ID 가 일치하면 [장착] 한다. => player.EquipOrUnequipItem();

아이템 장착 부분의 경우 아이템의 상태를 장착으로 바꾸고, 플레이어의 장착중인 아이템 저장을 동시에 한다.

 

Restore 가 끝나면, 위에서 저장되었던 ID == 1 의 아이템이 정상 장착 되어 있다.

Restore 전

 

 

Restore 후