본문 바로가기
Unity/Undead Survivor

로컬 저장소(PlayerPrefs) / 업적 시스템(Achievement System)

by hwan91 2025. 3. 19.

PlayerPrefs

로컬 저장소 기능으로, SetInt(), SetFloat(), SetString()을 사용해서 숫자, 실수, 문자열 데이터를 저장 가능하다.

게임이 꺼져도 데이터가 유지된다. (PlayerPrefs.Save()를 명시적으로 호출하지 않아도 자동 저장됨)

 

사용 방법

PlayerPrefs.SetInt("HighScore", 100);
PlayerPrefs.SetString("PlayerName", "Alex");
PlayerPrefs.Save();  // 저장 (자동으로 저장되므로 생략 가능)

- "HighScore" 키에 100 저장

- "PlayerName" 키에 "Alex" 저장

 

PlayerPrefs에서 데이터 불러오기

int score = PlayerPrefs.GetInt("HighScore");
string name = PlayerPrefs.GetString("PlayerName");

- "HighScore"에서 값 불러오기 (100 반환됨)
- "PlayerName"에서 값 불러오기 ("Alex" 반환됨)


Edit > Clear All PlayerPrefs 기능

모든 PlayerPrefs 데이터를 초기화하는 기능으로, 유니티 상단 메뉴에서 [Edit > Clear All PlayerPrefs] 클릭하면 저장된 데이터가 모두 삭제된다. 이후 PlayerPrefs.GetInt() 같은 함수를 호출하면 기본값(0 또는 빈 문자열)이 반환된다.

 

이 기능이 필요한 이유

1) 테스트 시 기존 데이터 초기화

- 개발 중 업적이나 설정 데이터가 꼬였을 때 기존 데이터를 삭제해서 테스트를 처음부터 다시 할 수 있음

2) 업적 시스템 초기화 시

 

- 특정 업적을 해금한 상태에서 다시 처음부터 잠금 상태로 테스트하고 싶을 때 실행하면 값이 0이 되어 다시 잠김

3) 저장된 데이터가 꼬였을 때 리셋
개발 중 PlayerPrefs를 잘못 저장하면 삭제 기능이 없어서 직접 수정하기 어려움.
Clear All PlayerPrefs를 실행하면 한 번에 싹 초기화 가능!

 

코드로 직접 초기화하는 방법

PlayerPrefs.DeleteAll();  // 모든 PlayerPrefs 데이터 삭제
PlayerPrefs.Save();       // 변경 내용 저장 (자동으로 저장되므로 생략 가능)

 

 

 

AchiveManager.cs

업적 시스템을 관리하는 스크립트로, 게임에서 특정 조건을 만족하면 새로운 캐릭터를 잠금 해제하고, 플레이어에게 알림(UI)을 보여주는 역할을 한다.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AchiveManager : MonoBehaviour
{
    public GameObject[] lockCharacter; // 해금 전 캐릭터들을 저장하는 배열
    public GameObject[] unlockCharacter; // 해금 완료된 캐릭터들을 저장하는 배열
    public GameObject uiNotice; // 업적 달성 시 표시할 알림 UI

    enum Achive { UnlockPotato, UnlockBean } // 열거형으로 업적을 정의
    Achive[] achives; // 업적 데이터들을 저장할 배열
    WaitForSecondsRealtime wait; // 코루틴에서 일정 시간 기다릴 때 사용하는 변수

    void Awake()
    {
        // .GetValues(typeof()) = 주어진 열거형의 데이터를 모두 가져오는 함수
        achives = (Achive[])Enum.GetValues(typeof(Achive)); // Achive 열거형에 있는 값들을 배열로 변환해서 achives에 저장
        wait = new WaitForSecondsRealtime(5); // 5초 동안 기다리는 객체를 생성

        // PlayerPrefs = 디바이스에 간단한 게임 데이터 저장 기능을 제공하는 유니티 클래스
        if (!PlayerPrefs.HasKey("MyData")) { // PlayerPrefs에 MyData라는 키가 없으면
            Init();
        }
    }

    // 처음 실행될 때 업적 데이터 초기화
    void Init()
    {
        PlayerPrefs.SetInt("MyData", 1); // MyData라는 키를 1로 설정하여 게임이 실행된 적 있다고 기록

        // achives 배열을 반복하면서 모든 업적의 값을 0으로 초기화(잠금 상태)
        foreach (Achive achive in achives) {
            /* achive.ToString() = 업적의 이름을 문자열로 변환
             * Achive.UnlockPotato.ToString() -> "UnlockPotato" */ 
            PlayerPrefs.SetInt(achive.ToString(), 0); // 업적 이름을 키로 사용하여 0 저장(해당 업적 잠금 상태)
        }
    }

    void Start()
    {
        UnlockCharacter();
    }

    // 업적 확인 후 잠금/해금 상태 반영
    void UnlockCharacter()
    {
        // 배열을 순회하여 index에 해당하는 업적 이름 가져오기
        for (int index = 0; index < lockCharacter.Length; index++) { // 잠금된 캐릭터 개수만큼 반복
            string achiveName = achives[index].ToString(); // achives 배열에서 업적 이름을 가져옴
            bool isUnlock = PlayerPrefs.GetInt(achiveName) == 1; // 업적 해금 여부 확인(1이면 해금 완료)
            lockCharacter[index].SetActive(!isUnlock); // 잠겨있는 캐릭터 버튼 활성화/비활성화
            unlockCharacter[index].SetActive(isUnlock); // 해금된 캐릭터 버튼 활성화/비활성화
        }
    }

    /* LateUpdate()를 사용한 이유
     * LateUpdate()는 모든 Update() 코드가 실행된 후 마지막에 실행되는 함수
     * CheckAchive()는 업적 조건을 확인하는 함수로 만약 Update()에서 실행하면 GameManager.instance.kill
     * 같은 변수가 업데이트되더라도 같은 프레임 내에서 조건이 완전히 반영되지 않을 가능성이 있음
     * LateUpdate()를 사용하면 모든 Update() 로직이 끝나 변경된 값이 반영된 후 최종적으로 체크 가능 */
    void LateUpdate()
    {
        // 모든 업적 확인
        foreach (Achive achive in achives) {
            CheckAchive(achive);
        }
    }

    // 업적 달성 체크
    void CheckAchive(Achive achive)
    {
        bool isAchive = false;

        // 업적 별 다른 달성 조건문 작성
        switch (achive) {
            case Achive.UnlockPotato: // 감자 농부 해금 조건
                isAchive = GameManager.instance.kill >= 100; // 100킬 이상
                break;
            case Achive.UnlockBean: // 콩 농부 해금 조건
                isAchive = GameManager.instance.gameTime == GameManager.instance.maxGameTime; // 최초로 끝까지 생존
                break;
        }

        // 해당 업적을 처음 달성했다는 조건문 작성
        if (isAchive && PlayerPrefs.GetInt(achive.ToString()) == 0) { // 아직 해금되지 않았다면
            PlayerPrefs.SetInt(achive.ToString(), 1); // 해당 업적 해금 저장

            for (int index = 0; index < uiNotice.transform.childCount; index++) { // 업적 알림 UI의 모든 자식 오브젝트를 체크
                bool isActive = index == (int)achive; // index가 현재 해금된 업적과 같을 때만 true
                uiNotice.transform.GetChild(index).gameObject.SetActive(isActive); // index번째 자식 오브젝트를 가져와서 활성화/비활성화
            }

            StartCoroutine(NoticeRoutine());
        }
    }

    // 알림 창을 활성화했다가 일정 시간 이후 비활성화하는 코루틴 생성
    IEnumerator NoticeRoutine()
    {
        uiNotice.SetActive(true); // 알림 활성화
        AudioManager.instance.PlaySfx(AudioManager.Sfx.LevelUp);

        yield return wait; // 5초 대기

        uiNotice.SetActive(false); // 알림 비활성화
    }
}