Agenda

- PlayerPrefs

- Json

- JsonUtiliey

- Security

PlayerPrefs

- Easy to implement

- Stored in a file on disl

- Easy for player to tamper with

- Good for data you can afford to lose

- User's preferences

- Temporaty fallback for saves

- Harder to serialize complex objects.

- Hard to handle multiple saves.

- Not made for saving game states.

PlayerPrefs의 특징

- 일부 데이터를 저장하는 빠르고 더러운 방법을 원했다고 가정했을 때 PlayerPrefs를 사용할 수 있습니다. 하지만 권장하지는 않습니다.

- PlayerPrefs는 게임 실행 사이에 데이터를 저장하는 가장 빠르고 쉬운 방법입니다.

파일은 텍스트 편집기로 쉽게 찾고, 읽고, 수정할 수 있습니다. 즉 메모장으로도 너무 쉽게 수정이 가능합니다. 따라서 보안에 좋지 않을 뿐만 아니라 내부 데이터가 손실되거나 손상되기 쉽습니다.

따라서 이러한 손실을 감당할 수 있는 데이터를 저장하는데 PlayerPrefs를 사용하는 것이 좋습니다.

예를 들어 그래픽 설정이나, 오디오 레벨과 같은 플레이어의 기본 설정과 같은 것이 있습니다.

또는 프로젝트가 아직 개발 중인 동안 임시 저장 솔루션으로 사용할 수 있습니다.

PlayerPrefs의 또다를 단점은 복잡한 객체를 직렬화 하기가 어렵다는 것입니다.

API는 부동소수점, 정수 및 문자열 저장만 지원합니다.

이론상으로 무엇이든 이런 형식으로 압축 할 수는 있지만 이러한 유형과 게임 내 데이터 유형 간에 변환하는 시스템을 유지 관리 해야합니다.

PlayerPrefs를 이용하여 다중 저장을 하는 것도 매우 어렵습니다.

그리고 궁극적으로 게임상태를 저장하기 위해 만들어진 것이 아닙니다.

Json

- Relatively human readable

- Widely used format

- Easily shared over a network

JSON의 장점

- 사람이 읽을 수 있습니다. 즉, 디버깅이 편리합니다.

- 널리 사용되는 형식이다. 플레이어 데이터를 서버에 저장하고 싶다고 가정해 볼 때, 널리 사용되는 형식을 사용하지 않으면 문제가 생길 수 있습니다.

그런 점에서 JSON은 상당히 컴팩트한 파일 형식으로 네트워크를 통해 전달하는 데에도 이상적입니다.

파일이 작을수록 전송 속도가 빨라지기 때문입니다.

그리고 다중 저장 파일이나 클라우드 저장을 지원하고 싶다면 PlayerPrefs를 이용하여 그렇게 하는 것이 훨씬 어렵습니다.

따라서 JSON을 사용하면 여러 저장 파일을 쉽게 저장하고 로드 할 수 있습니다.

JsonUtility

- Relatively easy to implement

- You have to store it yourself

- Manager multiple saves

- Cloud saves

- Constrained by the limitations of the internal serializer

- Unsupported fields wil be ignored

- Extra work steps may be required to support your use case

JsonUtility 특징

JsonUtility는 JSON 데이터를 직렬화 및 역직렬화하기 위한 Unity의 API입니다.

그런데 직렬화는 데이터를 파일로 저장할 수 있도록 변환하는 과정일 뿐입니다.

JsonUtility는 구현하기 쉽지만 PlayerPrefs와 달리 데이터를 직접 저장해야 합니다.

그러나 이것은 여러 저장을 쉽게 지원할 수 있기 때문에 이점이 될 수 있습니다.

또한 네트워크 등을 통해 어디서나 데이터를 가져올 수 있습니다.

Unity의 내부 직렬 변환기가 저장 및 로드할 수 있는 모든 데이터는 JsonUtility API를 통해 저장 및 로드할 수도 있습니다.

이것은 기본적으로 Inspector에 표시되는 필드만 JSON으로 변환된다는 것을 의미합니다.

JsonUtility는 내부 직렬 변환기와 동일한 제한 사항을 가집니다. 둘 다 내부적으로 동일한 시스템을 사용하기 때문입니다.

JsonUtility에 개체를 전달하면 지원되지 않는 필드는 무시됩니다.

즉, SaveData를 포함할 새 데이터 유형을 만들고 SaveData와 게임 내 데이터 간에 변환하는 코드를 작성해야 합니다.

Security

- Encryption is an easily overcome barrier

- Consider this an option to thwart the least dedicated players

- If you need security, store it on a server

- Anything that's stored locally is untrustworthy

- This affects your project architecture

데이터가 변조되지 않도록 보호하려면 어떻게 해야 할까요?

대부분의 사람들이 보안과 관련하여 가장 먼저 생각하는 것은 암호화 입니다.

그러나 암호화된 로컬데이터는 극복하기 어렵지 않습니다. 치팅을 이용하는 플레이어를 방해하는 옵션 정도라고 생각하면 됩니다.

따라서 일반적으로 로컬에 저장된 모든 데이터를 신뢰할 수 없다고 가정하는 것이 안전합니다.

데이터 보안은 프로젝트를 설계하는 방식에 영향을 미치므로 가능한 한 빨리 데이터 보안을 고려하는 것이 중요합니다.

예)

플레이어가 무료 온라인 게임에서 부정행위를 하면 게임 경제가 불안정해질 수 있습니다.

반면에 다른 사람들이 프리미엄 싱글플레이어 게임에서 치팅을 하면 피해가 거의 없습니다.

그렇다면 필요한 경우 SaveData를 어떻게 보호할 수 있을까요?

보안이 중요한 경우 가장 좋은 방법은 데이터를 로컬이 아닌 서버에 보관하는 것입니다.

게임 클라이언트가 하는 것을 믿을 수 없기 때문에 클라이언트가 실제 데이터가 아닌 서버에 명령만 보낼 수 있도록 프로젝트를 구성합니다.

그런 다음 서버는 명령이 유효한지 여부를 결정하고 결과만 다시 보낼 수 있습니다.

예를 들어 게임에서 전리품상자를 사용하는 경우 클라이언트에 전리품 상자를 저장하는 대신 서버에 저장하고

클라이언트가 전리품 상자를 여는 명령을 서버에 보내도록 합니다.

서버는 그것이 유효한지 확인하고 모든 작업을 수행하고 전리품 상자를 연 다음 클라이언트에 실제 데이터를 실제로 저장하지 않고 내부에 무엇이 있는지 클라이언트에게 알려줍니다.

Takeaways

- PlayerPrefs for data you can afford to lose

- JsonUtility for more serious data

- Security should be condiered from the start

결론적으로 PlayerPrefs는 사용하기 쉽지만 손실을 감수할 수 있는 데이터에 가장 적합하다는 것을 배웠습니다.

이것은 일반적으로 플레이어 설정 또는 기본설정 같은것입니다.

Json또는 유니티 JsonUtiliy는 게임상태와 같은 중요한 데이터를 저장하는 강력한 방법입니다.

마지막으로 보안은 처음부터 고려해야할 사항입니다.

암호화는 억지력일 뿐 실제 보안은 아닙니다.

따라서 실제 보안을 위해서는 앱 외부의 서버에 데이터를 저장하고 이러한 제약 조건을 중심으로 프로젝트를 설계해야합니다.

유니티 직렬화에 대해서 더 배우고 싶은 분은 아래 링크를 참고해 주세요

https://docs.unity3d.com/Manual/script-Serialization.html

'Unity' 카테고리의 다른 글

싱글톤 Singletone  (3) 2024.12.14
Serialization depth limit 7 exceeded Issue  (5) 2024.12.14
디펜스 게임 만들기 - 그리드 타일 시스템  (3) 2024.02.05

 

  • 싱글턴 패턴의 기본
  • 유니티에서 재사용 가능한 싱글턴 클래스 작성하기
  • 전역적으로 접근할 수 있는 GameManager 구현

요구사항

C# 기본지식

싱글턴패턴 이해하기

이름에서 알 수 있듯이 싱글턴 패턴의 주요 목표는ㄴ 유일성을 보장하는 것이다. 클래스가 싱글턴 패턴을 제대로 구현했다면 초기화된 후에는 런타임 동안 메모리에 는 오직 하나의 인스턴스만 존재해야 한다. 이 메커니즘은 일관되고 유일한 진입점에서 전역적으로 접근할 수 있는 시스템을 관리하는 클래스가 있을 때 도움이 된다.

 

디자인

싱글턴의 디자인은 매우 단순하다.

유일성을 보장해야하기 때문에 자기 자신과 같은 유형의 개체 인스턴스를 발견하면 즉시 없앤다. 예외는 없다.

싱글턴 패턴에서 가장 중요한 점은 오직 하나만 존재해야한다는 점으로 그렇지 않다면 목적달성에 실패한 것이다.

장단점

장점

  1. 전역접근가능 : 싱글턴 패턴을 사용하여 리소스나 서비스의 전역 접근점을 만들 수 있다.
  2. 동시성 제어 : 공유 자원에 동시 접근을 제한하고자 사용할 수 있다.

단점

  1. 유닛 테스트 : 과도하게 사용하면 유닛 단위의 테스트가 어려워진다. 싱글턴 오브젝트가 다른 싱글턴에 종속될 수도 있다. 하나가 누락되면 종속성이 끊어져 문제가 생긴다.
  2. 잘못된 습관 : 싱글턴은 사용하기 쉬워 잘못된 프로그래밍 습관이 생길 수 있다. 싱글턴으로 어디서나 모든 것에 쉽게 접근하게 만들 수 있기 때문에, 코드 작성 시 보다 정교하게 접근하여 테스트하는 것이 귀찮게 느껴질 수 있다.

게임 매니저 디자인

게임매니저는 게임 전체의 수명동안 살아있어야 한다. 메모리내에서 유일해야한다.

 

 

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

public class Singleton <T> : MonoBehaviour where T:Component
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<T>();
                if (instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    instance = obj.AddComponent<T>();
                }
            }
            return instance;
        }
    }

    public virtual void Awake()
    {
        if (instance == null)
        {
            instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

 

using UnityEngine;
using System;
using UnityEngine.SceneManagement;

public class GameManager : Singleton<GameManager>
{
    private DateTime sessionStartTime;
    private DateTime sessionEndTime;

    private void Start()
    {
        sessionStartTime = DateTime.Now;
        Debug.Log("Game session start @: " + sessionStartTime);
    }
    private void OnApplicationQuit()
    {
        sessionEndTime = DateTime.Now;
        TimeSpan timeDifference = sessionEndTime.Subtract(sessionStartTime);
        Debug.Log("Game session end @: " + sessionEndTime);
        Debug.Log("Game session duration: " + timeDifference);
    }
    
}

 

'Unity' 카테고리의 다른 글

Unity - 데이터 저장  (4) 2024.12.14
Serialization depth limit 7 exceeded Issue  (5) 2024.12.14
디펜스 게임 만들기 - 그리드 타일 시스템  (3) 2024.02.05

Serialization depth limit 7 exceeded Issue

 

 

1. 원인

부모객체에서 자식을 시리얼라이즈 할 때 발생.

2. 재현

직렬화 가능한

Unit이 있고

Unit을 상속받은

InnerUnit : Unit 이 있다.

Unit은 내부에 InnerUnit의 리스트를 가지고 있다.

InnerUnit리스트를 직렬화 한다.

3. 해결

새로운 클래스 생성

UnitData가 각각 Unit객체와 InnerUnit의 리스트를 들고있고

기존의 Unit의 내부에 대한 접근은 반드시 UnitData를 통하여 접근한다.

 

'Unity' 카테고리의 다른 글

Unity - 데이터 저장  (4) 2024.12.14
싱글톤 Singletone  (3) 2024.12.14
디펜스 게임 만들기 - 그리드 타일 시스템  (3) 2024.02.05

 

 

그러니까 순서는

 

0. 게임이 시작된다.

1. 안쪽에 초록색 타일을 생성하여 채운다.

2. 생성할 때 타일의 위치와 기초데이터들을 초기화 한다.

 

구현

public class GridSystem : MonoBehaviour
    {
        public Tile tile;

        public int xSize, ySize;
        public float xSpace, ySpace;
        [SerializedDictionary("TilePos", "Tile")]
        public Dictionary<string, Tile> tileDic = new Dictionary<string, Tile>();


        private void Start()
        {
            for (int i = 0; i < xSize; i++)
            {
                for (int j = 0; j < ySize; j++)
                {
                    Tile newTile = Instantiate(tile, new Vector3(0.5f + i * xSpace, 0.5f, 0.5f + j * ySpace), Quaternion.identity);
                    TilePos tilePos = new TilePos(i, j);
                    string key = i + "," + j;
                    newTile.transform.parent = transform;
                    newTile.InitiateTile(tilePos, this);
                    tileDic.Add(key, newTile);
                }
            }
        }

 

결과

 

다음은 타일의 데이터를 초기화 한다.

 

 

 

public class Tile : MonoBehaviour
    {
        
        public Material material;
        public TilePos tilePos;
        public GridSystem gridSystem;


        public void InitiateTile(TilePos tilePos,GridSystem gridSystem)
        {
            material = GetComponent<MeshRenderer>().material;
            this.tilePos = tilePos;
            this.gridSystem = gridSystem;
            gameObject.name = tilePos.x + " , " + tilePos.y;
        }


        #region DebugLogic

        public void Debug_Select()
        {
            gridSystem.GetNearTile(tilePos);

            
        }

        public void TileColorChange(Color color)
        {
            material.DOColor(color, 0.5f).SetLoops(2, LoopType.Yoyo);

        }
        #endregion
    }

    [System.Serializable]
    public struct TilePos
    {
        public int x, y;
        public TilePos(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

 

내일은 타일을 클릭하면 주변 타일을 검색하는 기능을 만들겠다.

'Unity' 카테고리의 다른 글

Unity - 데이터 저장  (4) 2024.12.14
싱글톤 Singletone  (3) 2024.12.14
Serialization depth limit 7 exceeded Issue  (5) 2024.12.14

+ Recent posts