<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DevFrog</title>
    <link>https://devfrog.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 9 Jun 2026 21:56:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>뎁프</managingEditor>
    <image>
      <title>DevFrog</title>
      <url>https://tistory1.daumcdn.net/tistory/5865219/attach/c2a98bedadd84c6bb95bfcb12c442fbb</url>
      <link>https://devfrog.tistory.com</link>
    </image>
    <item>
      <title>Addressables in Unity</title>
      <link>https://devfrog.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에는 에셋을 다루는 다양한 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 예를 들자면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수를 Inspector에 노출시키고 Hierarchy에서 직접 에셋을 드래그&amp;amp;드롭으로 연결시켜놓는 방법.&lt;/li&gt;
&lt;li&gt;Resources 폴더를 에셋을 넣고 Resources.Load()를 사용해 불러오는 방법.&lt;/li&gt;
&lt;li&gt;에셋들을 묶음(Bundle) 단위로 관리, 배포하는 방법.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어드레서블은 다른 방식들 몇가지 단점들을 보완하고자 만들어진 방식이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;어드레서블이란?&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어드레서블은 유니티에서 사용되는 에셋 관리 시스템이다.&lt;/li&gt;
&lt;li&gt;원하는 타이밍에 에셋을 로드할 수 있기 때문에 메모리 관리에 도움이 된다.&lt;/li&gt;
&lt;li&gt;모든 에셋을 Resources.Load로 불러온다면 미리 메모리에 올려두어야 하고, 이는 로딩 타임에도 영향을 끼쳐 앱의 경쟁력이 떨어질 수 있으니, 프로젝트의 규모가 어느 정도 있다면 어드레서블 사용을 고려해 보자.&lt;/li&gt;
&lt;li&gt;어드레서블은 비동기로 실행되기 때문에 멈춰진 화면을 보고만 있는 일은 없을 것이다.&lt;/li&gt;
&lt;li&gt;백그라운드에서 로드가 끝났을 때 원하는 처리 하도록 만들자.&lt;/li&gt;
&lt;li&gt;어드레서블을 클라우드에 있는 서버에 올려서 사용할 수도 있다. 이런 경우 빌드를 다시 받지 않아도 새로운 에셋들을 배포할 수 있게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;로컬에서 Addressable 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Package Manager에서 Addressables를 인스톨하자.&lt;/li&gt;
&lt;li&gt;Window &amp;gt; Asset Management &amp;gt; Addressables &amp;gt; Groups를 선택해 Addressables Groups 팝업을 띄운다.&lt;/li&gt;
&lt;li&gt;그룹을 설정하고 원하는 에셋을 해당 그룹으로 그레그하면 등록시킬 수 있다. (에셋을 어드레서블로 등록시키는 방법은 다양하다)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스를 클릭하면 어드레서블 프리팹을 생성하는 간단한 스크립트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 경로를 string으로 전달하는 끔찍한 방식을 사용하고 있지만 다른 방식도 아래 설명될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1672394311941&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class TestAddressables : MonoBehaviour
    {
        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                var asyncOperationHandler = 
                    Addressables.LoadAssetAsync&amp;lt;GameObject&amp;gt;(&quot;Assets/Prefabs/Characters/Players/Player.prefab&quot;);
                asyncOperationHandler.Completed += HandleOperation;
            }
        }

        private void HandleOperation(AsyncOperationHandle&amp;lt;GameObject&amp;gt; operation)
        {
            if (operation.Status == AsyncOperationStatus.Succeeded)
            {
                Instantiate(operation.Result);
            }
            else
            {
                Debug.Log(&quot;Failed to load!&quot;);
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다식을 확용하여 좀 더 간결한 표현이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1672394681267&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TestAddressables : MonoBehaviour
    {
        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                Addressables.LoadAssetAsync&amp;lt;GameObject&amp;gt;(&quot;Assets/Prefabs/Characters/Players/Player.prefab&quot;).Completed +=
                    (operation) =&amp;gt;
                    {
                        if (operation.Status == AsyncOperationStatus.Succeeded)
                        {
                            Instantiate(operation.Result);
                        }
                        else
                        {
                            Debug.Log(&quot;Failed to load!&quot;);
                        }
                    };
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 끔찍한 string 대신에 다른 Addressable 활용법들을 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Asset Reference를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 변수를 선언하면 인스펙터에서 원하는 에셋을 드레그 해서 등록할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1672394949791&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[SerializeField] private AssetReference assetReference;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5bRXV/btrU0ueipgk/yeK7hy7dQ2txx1zJcPBmhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5bRXV/btrU0ueipgk/yeK7hy7dQ2txx1zJcPBmhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5bRXV/btrU0ueipgk/yeK7hy7dQ2txx1zJcPBmhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5bRXV%2FbtrU0ueipgk%2FyeK7hy7dQ2txx1zJcPBmhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;710&quot; height=&quot;107&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Asset Reference를 통해 프리팹을 생성해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1672395058712&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TestAddressables : MonoBehaviour
    {
        [SerializeField] private AssetReference assetReference;
        
        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                assetReference.LoadAssetAsync&amp;lt;GameObject&amp;gt;().Completed +=
                    (operation) =&amp;gt;
                    {
                        if (operation.Status == AsyncOperationStatus.Succeeded)
                        {
                            Instantiate(operation.Result);
                        }
                        else
                        {
                            Debug.Log(&quot;Failed to load!&quot;);
                        }
                    };
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string이 없어지니 한결 후련하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 간단하게 Addressable GameObject를 생성 방법도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1673401092777&quot; class=&quot;abnf&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assetReference.InstantiateAsync();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어드레서블을 로드하는 다른 방법도 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lable을 통해 어드레서블 에셋 로드하기.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNYlEX/btrVT7XIA7N/9M97nCJcIKi9tCJvmkRBK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNYlEX/btrVT7XIA7N/9M97nCJcIKi9tCJvmkRBK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNYlEX/btrVT7XIA7N/9M97nCJcIKi9tCJvmkRBK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNYlEX%2FbtrVT7XIA7N%2F9M97nCJcIKi9tCJvmkRBK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;184&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 Lable을 추가해서 다수의 에셋을 해당 Lable로 등록 시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1673400232412&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[SerializeField] private AssetLabelReference assetLabelReference;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스펙터에 노출해서 원하는 레이블을 선택할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1673400398641&quot; class=&quot;pgsql&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Addressables.LoadAssetAsync&amp;lt;GameObject&amp;gt;(assetLabelReference).Completed +=
                (operation) =&amp;gt;
                {
                    if (operation.Status == AsyncOperationStatus.Succeeded)
                    {
                        Instantiate(operation.Result);
                    }
                    else
                    {
                        Debug.Log(&quot;Failed to load!&quot;);
                    }
                };
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더 전체를 Addressable로 했을 때, 다수의 에셋들을 로딩하는 방법도 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더를 Addressable로 등록을 했더라도 폴더를 다이렉트로 접근하는 건 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위에 있는 에셋에 일일히 접근해야 하는데 이런 접근 방법은 우리가 원하는 방법이 아니기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lable을 사용하는게 바람직하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더를 Lable로 지정하면 하위에 에셋들이 모두 해당 Lable로 마크된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 언급한 AssetLableReference로 해당 폴더를 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1673401701359&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[SerializeField] private AssetLabelReference assetLabelReference;

private void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        Addressables.LoadAssetsAsync&amp;lt;GameObject&amp;gt;(assetLabelReference, (go) =&amp;gt; 
        { 
        	Debug.Log(go);
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 인자에 에셋을 불러올 때마다 실행하는 Action을 넣어줄 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 불러온 에셋을 사용하지 않는다면 Release 해주도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법으로 메모리 공간을 절약할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1673402202156&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[SerializeField] private AssetReferenceGameObject assetReferenceGameObject;

private GameObject spawnedGameObject;

private void Update()
{
    //불러오기
    if (Input.GetMouseButtonDown(0))
    {
        assetReferenceGameObject.InstantiateAsync().Completed +=
            (operation) =&amp;gt; spawnedGameObject = operation.Result;
    }

    //놓아주기
    if (Input.GetMouseButtonDown(1))
    {
        assetReferenceGameObject.ReleaseInstance(spawnedGameObject);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레퍼런스는 GameObject나 Sprite 등 강한 타입들도 지원을 하니 시간이 된다면 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[SerializeField] private AssetReferenceSprite assetReferenceSprite;
[SerializeField] private AssetReferenceGameObject assetReference;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드와 같이 원하는 타입을 만들어서 사용할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1673401569784&quot; class=&quot;angelscript&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[System.Serializable]
public class AssetReferenceAudioClip : AssetReferenceT&amp;lt;AudioClip&amp;gt;
{
	public AssetReferenceAudioClip(string guid) : base(guid) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity/Coding</category>
      <author>뎁프</author>
      <guid isPermaLink="true">https://devfrog.tistory.com/12</guid>
      <comments>https://devfrog.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 11 Jan 2023 11:03:26 +0900</pubDate>
    </item>
    <item>
      <title>Anti Cheat Toolkit</title>
      <link>https://devfrog.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Unity Asset Store에서 구매 가능한 유료 치트 방지 플러그인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 ObscuredTypes, ObscuredPrefs, ObscuredFile, ObscuredFilePrefs 4가지로 나눠져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나 씩 예제 코드와 함께 살펴보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ObscuredTypes&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Cheat Engine&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;ArtMoney&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;GameCIH&lt;/span&gt; 등과 같은 모든 메모리 검색기에서 변수를 숨김.&lt;/li&gt;
&lt;li&gt;모든 &lt;span style=&quot;color: #409d00;&quot;&gt;기본 타입&lt;/span&gt; 및 소수의 &lt;span style=&quot;color: #409d00;&quot;&gt;유니티 타입&lt;/span&gt;들을 커버한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;모든 플랫폼&lt;/span&gt;에서 작동. (PC, Mac, iOS, Android, WP8, WinStore)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;ObscuredCheatingDectector&lt;/span&gt;를 통한 &lt;span style=&quot;color: #409d00;&quot;&gt;임의 변조 감지&lt;/span&gt; 기능이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보호 기능이 없는 간단한 코드를 상상해 보자. (lives 변수 참고)&lt;/p&gt;
&lt;pre id=&quot;code_1672798767515&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;

public class ObscuredVars : MonoBehaviour
{
    private int lives = 5;

    private void OnGUI()
    {
        GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));

        if (lives &amp;gt; 0)
        {
            GUILayout.Label(&quot;Lives: &quot; + lives);

            if (GUILayout.Button(&quot;Kill player&quot;, GUILayout.ExpandWidth(false)))
            {
                lives--;
            }
        }
        else
        {
            GUILayout.Label(&quot;Game over!&quot;);

            if (GUILayout.Button(&quot;Start new game&quot;, GUILayout.ExpandWidth(false)))
            {
                lives = 5;
            }
        }
        
        GUILayout.EndArea();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 치터들은 변수를 찾을 때까지 검색을 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무런 보호 장치가 없다면, 이렇게 찾은 변수를 쉽게 조작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACTk을 사용해 변수를 보호하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 int형을 ObscuredInt형으로 교체하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672799069838&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ObscuredVars : MonoBehaviour
{
    private ObscuredInt lives = 5;

    private void OnGUI()
    {
        GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));

        if (lives &amp;gt; 0)
        {
            GUILayout.Label(&quot;Lives: &quot; + lives);

            if (GUILayout.Button(&quot;Kill player&quot;, GUILayout.ExpandWidth(false)))
            {
                lives--;
            }
        }
        else
        {
            GUILayout.Label(&quot;Game over!&quot;);

            if (GUILayout.Button(&quot;Start new game&quot;, GUILayout.ExpandWidth(false)))
            {
                lives = 5;
            }
        }
        
        GUILayout.EndArea();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 타입을 어느정도 보호했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;치터가 가짜 사본을 찾고 수정하는 것을 허용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObscuredCheatingDetector를 사용하여 부정 행위를 탐지해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cheater 감지에 대한 반응을 구현하고 Start함수에서 탐지기를 작동하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672799594891&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ObscuredVars : MonoBehaviour
{
    private ObscuredInt lives = 5;
    private bool cheaterDetected = false;

    private void Start()
    {
        ObscuredCheatingDetector.StartDetection(OnCheaterDetected);
    }

    private void OnCheaterDetected()
    {
        cheaterDetected = true;
    }

    private void OnGUI()
    {
        GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));

        if (lives &amp;gt; 0)
        {
            GUILayout.Label(&quot;Lives: &quot; + lives);

            if (GUILayout.Button(&quot;Kill player&quot;, GUILayout.ExpandWidth(false)))
            {
                lives--;
            }
        }
        else
        {
            GUILayout.Label(&quot;Game over!&quot;);

            if (GUILayout.Button(&quot;Start new game&quot;, GUILayout.ExpandWidth(false)))
            {
                lives = 5;
            }
        }
        
        GUILayout.EndArea();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 부정 행위 시도가 감지되며 변수는 안전합니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ObscuredPrefs&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장된 모든 데이터 &lt;span style=&quot;color: #409d00;&quot;&gt;암호화&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;변조 감지&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;저장된 &lt;span style=&quot;color: #409d00;&quot;&gt;데이터&lt;/span&gt;를 현재 디바이스에 &lt;span style=&quot;color: #409d00;&quot;&gt;잠금&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;PlayerPrefs&lt;/span&gt;에서 자동으로 &lt;span style=&quot;color: #409d00;&quot;&gt;마이그레이션&lt;/span&gt; 됨.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;추가 유형&lt;/span&gt;을 지원.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;모든 플랫폼&lt;/span&gt;에서 작동. (PC, Mac, iOS, Android, WP8, WinStore)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보호&amp;nbsp;기능이&amp;nbsp;없는&amp;nbsp;간단한&amp;nbsp;코드입니다.&amp;nbsp;(PlayerPrefs&amp;nbsp;사용법&amp;nbsp;참고)&lt;/p&gt;
&lt;pre id=&quot;code_1672796961106&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;

public class ObscuredPrefsExample : MonoBehaviour
{
    private int lives = 5;

    private void OnGUI()
    {
        GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
        OutputLivesWithKillButton();

        if (GUILayout.Button(&quot;Save lives&quot;, GUILayout.ExpandWidth(false)))
        {
            PlayerPrefs.SetInt(&quot;player lives&quot;, lives);
        }

        if (GUILayout.Button(&quot;Load lives&quot;, GUILayout.ExpandWidth(false)))
        {
            lives = PlayerPrefs.GetInt(&quot;player lives&quot;, lives);
        }
        
        GUILayout.EndArea();
    }

    private void OutputLivesWithKillButton(){}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 노출된 PlayerPrefs는 변조 되기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 ACTk를 사용한 ObscuredPrefs를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 간단하게 PlayerPrefs를 ObscuredPrefs로 교체하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672797334549&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;
using CodeStage.AntiCheat.Storage;

public class ObscuredPrefsExample : MonoBehaviour
{
    private int lives = 5;

    private void OnGUI()
    {
        GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
        OutputLivesWithKillButton();

        if (GUILayout.Button(&quot;Save lives&quot;, GUILayout.ExpandWidth(false)))
        {
            ObscuredPrefs.Set(&quot;player lives&quot;, lives);
        }

        if (GUILayout.Button(&quot;Load lives&quot;, GUILayout.ExpandWidth(false)))
        {
            lives = ObscuredPrefs.Get(&quot;player lives&quot;, lives);
        }
        
        GUILayout.EndArea();
    }

    private void OutputLivesWithKillButton(){}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 부정 행위 감지 콜백을 추가하고 적절한 반응을 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672797880816&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ObscuredPrefsExample : MonoBehaviour
{
    private int lives = 5;
    private bool cheated = false;

    private void Start()
    {
        ObscuredPrefs.NotGenuineDataDetected += OnCheatingDetected;
    }

    private void OnCheatingDetected()
    {
        cheated = true;
    }

    private void OnGUI()
    {
        GUILayout.BeginArea(new Rect(5, 5, Screen.width - 10, Screen.width - 10));
        OutputLivesWithKillButton();

        if (cheated)
        {
            GUILayout.Label(&quot;Cheater!&quot;);
        }

        if (GUILayout.Button(&quot;Save lives&quot;, GUILayout.ExpandWidth(false)))
        {
            ObscuredPrefs.Set(&quot;player lives&quot;, lives);
        }

        if (GUILayout.Button(&quot;Load lives&quot;, GUILayout.ExpandWidth(false)))
        {
            lives = ObscuredPrefs.Get(&quot;player lives&quot;, lives);
        }
        
        GUILayout.EndArea();
    }

    private void OutputLivesWithKillButton(){}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ObscuredFile&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중요한 데이터를 보호하기 위한 파일 암호화 옵션이 있다.&lt;/li&gt;
&lt;li&gt;파일 편집 시도를 포착하는 변조 감지 기능이 내장되어 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;실제&amp;nbsp;데이터&amp;nbsp;손상으로&amp;nbsp;인한&amp;nbsp;오탐&amp;nbsp;방지&lt;/li&gt;
&lt;li&gt;암호화 및 일반 모드 모두에서 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;추가 구성 없이 이벤트만 수신만으로 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다른 장치의 데이터를 감지하거나 완전히 방지하는 장치 잠금 옵션이 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;사용자&amp;nbsp;정의&amp;nbsp;ID&amp;nbsp;옵션.&amp;nbsp;(이메일,&amp;nbsp;사용자&amp;nbsp;이름&amp;nbsp;등)&lt;/li&gt;
&lt;li&gt;암호화 및 일반 모드 모두에서 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;ObscuredFile 요약&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선택적으로 데이터를 암호화.&lt;/li&gt;
&lt;li&gt;선택적으로 데이터를 장치 또는 사용자 지정 ID에 잠금.&lt;/li&gt;
&lt;li&gt;변조 시도를 감지.&lt;/li&gt;
&lt;li&gt;백그라운드 스레드에서 사용 가능.&lt;/li&gt;
&lt;li&gt;플랫폼 제한이 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 사용 예:&lt;/p&gt;
&lt;pre id=&quot;code_1672818446687&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ObscuredFileExample : MonoBehaviour
{
    private void Start()
    {
        var safeFile = new ObscuredFile();
        
        //write data:
        var writeResult = safeFile.WriteAllBytes(Encoding.UTF8.GetBytes(&quot;Some protected data&quot;));
        if(writeResult.Success)
            Debug.Log($&quot;Data saved to {safeFile.FilePath}&quot;);
        else
            Debug.Log($&quot;Someting went wrong while writing data: {writeResult.Error}&quot;);
        
        //read data:
        var readResult = safeFile.ReadAllBytes();
        if (readResult.Success)
        {
            // process readResult.Data;
        }
        else if (readResult.CheatingDetected)
        {
            // punish cheaters
            // differentiate using DataFromAnotherDevice and DataIsNotGenuine readResult properties
        }
        else
        {
            // something went wrong and Data wasn't read
            Debug.LogError($&quot;Something went wrong while reading data: {readResult.Error}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ObscuredFilePrefs&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ObscuredFile의 파워와 유연성.&lt;/li&gt;
&lt;li&gt;PlayerPrefs의 단순성과&amp;nbsp;사용&amp;nbsp;용이성.&lt;/li&gt;
&lt;li&gt;Prefs들을 List로 사용할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 사용 예:&lt;/p&gt;
&lt;pre id=&quot;code_1672819139011&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ObscuredFileExample : MonoBehaviour
{
    private void Start()
    {
        // first, let's init ObscuredFilePrefs and load existing prefs
        ObscuredFilePrefs.Init(true);
        
        // now, let's add cheating trigger listeners
        // I'll use same listener for both event in sake of demo simplicity
        ObscuredFilePrefs.NotGenuineDataDetected += CheaterDetected;
        ObscuredFilePrefs.DataFromAnotherDeviceDetected += CheaterDetected;
        
        // few silly prefs for demo purposes
        const string playerHealthKey = &quot;Player health&quot;;
        const string playerSessionStart = &quot;Player session start&quot;;
        
        // this is how you can set prefs using generics
        ObscuredFilePrefs.Set(playerHealthKey, 100);
        ObscuredFilePrefs.Set(playerSessionStart, DateTime.Now);
        
        // getting prefs is easy as well
        var loadedHealth = ObscuredFilePrefs.Get(playerHealthKey, 0);
        var loadedSessionStart = ObscuredFilePrefs.Get(playerSessionStart, DateTime.MinValue);
        
        Debug.Log($&quot;{playerHealthKey}: {loadedHealth}&quot;);
        Debug.Log($&quot;{playerSessionStart}: {loadedSessionStart}&quot;);
    }

    private void CheaterDetected()
    {
        Debug.Log(&quot;Cheater!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문 사용:&lt;/p&gt;
&lt;pre id=&quot;code_1672819276907&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// iterating prefs is really easy as well:
var allPrefs = ObscuredFilePrefs.GetKeys();
foreach (var pref in allPrefs)
{
    Debug.Log(pref);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SpeedHackDetector&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;스피드핵&lt;/span&gt;을 감지한다.&lt;/li&gt;
&lt;li&gt;오탐을 방지하기 위한 &lt;span style=&quot;color: #0593d3;&quot;&gt;쿨다운&lt;/span&gt; 기능이 있다.&lt;/li&gt;
&lt;li&gt;시간 변경을 &lt;span style=&quot;color: #409d00;&quot;&gt;준수&lt;/span&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;모든 플랫폼&lt;/span&gt;에서 작동. (PC, Mac, iOS, Android, WP8, WinStore)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeedHack이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 스피드 핵이 어떻게 작동하는지 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;씬의 큐브에서 이러한 회전 스크립트를 상상해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1672820469556&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SpeedHackDetecting : MonoBehaviour
{
    private void Update()
    {
        transform.Rotate(Vector3.up, 60f * Time.deltaTime);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 치터가 치트엔진을 통해 스피드를 쉽게 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 치트는 예방이 안되지만 감지 및 대응은 가능하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Hierarchy 메뉴에서 스피드핵 디텍터 오브젝트를 만들자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;993&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk0YhQ/btrVpDO4873/FlK9EVXrXHp42VnMkdkjLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk0YhQ/btrVpDO4873/FlK9EVXrXHp42VnMkdkjLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk0YhQ/btrVpDO4873/FlK9EVXrXHp42VnMkdkjLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk0YhQ%2FbtrVpDO4873%2FFlK9EVXrXHp42VnMkdkjLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1271&quot; height=&quot;993&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;993&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LOSai/btrVk4741gi/wTVgKBuNfRTcVeQsGtGNrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LOSai/btrVk4741gi/wTVgKBuNfRTcVeQsGtGNrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LOSai/btrVk4741gi/wTVgKBuNfRTcVeQsGtGNrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLOSai%2FbtrVk4741gi%2FwTVgKBuNfRTcVeQsGtGNrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;546&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제&amp;nbsp;인스펙터에서&amp;nbsp;감지기를&amp;nbsp;구성하고&amp;nbsp;이벤트&amp;nbsp;콜백을&amp;nbsp;할당하기만&amp;nbsp;하면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 부정 행위 감지 시 게임을 멈추고 팝업을 띄우는 식의 대응이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 이 작업을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672821989077&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SpeedHackDetecting : MonoBehaviour
{
    private bool cheater = false;

    private void Start()
    {
        SpeedHackDetector.StartDetection(OnSpeedHackDetected);
    }

    private void OnSpeedHackDetected()
    {
        cheater = true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 Start 함수에서 StartDetection을 해주는 것만으로 스피드핵을 감지할 수 있게 된다.&lt;/p&gt;</description>
      <category>Unity/Plugins</category>
      <author>뎁프</author>
      <guid isPermaLink="true">https://devfrog.tistory.com/11</guid>
      <comments>https://devfrog.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 4 Jan 2023 17:03:22 +0900</pubDate>
    </item>
    <item>
      <title>JSON in Unity</title>
      <link>https://devfrog.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;JSON이란?&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버와 클라이언트 사이에 데이터를 주고받을 때 사용하는 개방형 표준 포맷.&lt;/li&gt;
&lt;li&gt;텍스트를 사용하기 때문에 사람이 이해하기 쉽다는 장점을 가지고 있다.&lt;/li&gt;
&lt;li&gt;JSON은 유니티에서도 다양하게 사용 되는데, 게임에 필요한 데이터를 주고받거나, 게임 진행 상황 또는 설정을 저장할 때 등 광범위하게 사용된다.&lt;/li&gt;
&lt;li&gt;JSON에서 {}중괄호는 객체를 의미하고 []대괄호는 배열을 나타낸다.&lt;/li&gt;
&lt;li&gt;XML과 사용처가 비슷하지만 XML은 JSON에 비해 가독성이 떨어지고 데이터를 넣거나 파싱 하는 과정이 까다롭다.&lt;/li&gt;
&lt;li&gt;JSON은 주석을 제공하지 않는다. 때문에 가독성을 위해 Key의 네이밍에 신경 쓸 필요가 있다.&lt;/li&gt;
&lt;li&gt;작은 문법 오류에도 문제가 생길 수 있어서 문법에 오입력이 없도록 신경 쓰자. (JSON 검사기로 테스트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 코드 예시:&lt;/p&gt;
&lt;pre id=&quot;code_1672362920769&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;someID&quot;,
  &quot;level&quot;: 10,
  &quot;exp&quot;: 33.3,
  &quot;hp&quot;: 400,
  &quot;items&quot;: [&quot;Sword&quot;, &quot;Armor&quot;, &quot;Hp Potion&quot;, &quot;Hp Potion&quot;, &quot;Hp Potion&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON에서 지원하는 데이터 타입들:&lt;/p&gt;
&lt;pre id=&quot;code_1672362944196&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;i&quot;:10,
    &quot;f&quot;:99.9,
    &quot;b&quot;:true,
    &quot;str&quot;:&quot;JSON Test&quot;
    &quot;iArray&quot;:[1,1,2,3,5,8,12,34],
    &quot;iList&quot;:[0,2,4,6,8],
    &quot;fDictionary&quot;:
    {
        &quot;PIE&quot;:3.14159274,
        &quot;Epsilon&quot;:1.401298E-45,
        &quot;Sqrt(2)&quot;:1.41421354
    },
    &quot;iVector&quot;:
    {
        &quot;x&quot;:3,
        &quot;y&quot;:2
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Newtonsoft JSON Library&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 라이브러리인 Newtonsoft JSON Library를 사용하기 위해서 using Newtonsoft.Json을 선언해 준다.&lt;/li&gt;
&lt;li&gt;테스트 용도로 사용할 클래스를 정의하자.&lt;/li&gt;
&lt;li&gt;JSON이 담을 수 있는 대부분의 타입을 멤버 변수로 추가하였다.&lt;/li&gt;
&lt;li&gt;Int, Float, Bool, String과 같이 기본적인 데이터 타입부터 배열, List, Dictionary와 같은 컨테이너 타입,&lt;/li&gt;
&lt;li&gt;그리고 사용자가 직접 정의한 클래스 형식도 한 클래스에 묶어서 JSON 데이터 형식으로 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672363146898&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class JsonTestClass
{
    public int i;
    public float f;
    public bool b;
    public string str;
    public int[] iArray;
    public List&amp;lt;int&amp;gt; iList = new List&amp;lt;int&amp;gt;();
    public Dictionary&amp;lt;string, float&amp;gt; fDictionary = new Dictionary&amp;lt;string, float&amp;gt;();
    public IntVector2 iVector;

    public class IntVector2
    {
        public int x;
        public int y;

        public IntVector2(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 값을 자동으로 초기화시켜 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1672363178328&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public JsonTestClass()
{
    i = 10;
    f = 99.9f;
    b = true;
    str = &quot;JSON Test string!&quot;;
    iArray = new int[] { 1, 1, 2, 3, 5, 6, 12, 21, 34, 55 };

    for (int idx = 0; idx &amp;lt; 5; idx++)
    {
        iList.Add(2 * idx);
    }

    fDictionary.Add(&quot;PIE&quot;, Mathf.PI);
    fDictionary.Add(&quot;Epsilon&quot;, Mathf.Epsilon);
    fDictionary.Add(&quot;Squrt(2)&quot;, Mathf.Sqrt(2));

    iVector = new IntVector2(3, 2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 값을 출력하는 Print 함수도 정의해 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1672363207780&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void Print()
{
    Debug.Log($&quot;i = {i}&quot;);
    Debug.Log($&quot;f = {f}&quot;);
    Debug.Log($&quot;b = {b}&quot;);
    Debug.Log($&quot;str = {str}&quot;);

    for (int idx = 0; idx &amp;lt; iArray.Length; idx++)
    {
        Debug.Log(string.Format(&quot;iArray[{0}] = {1}&quot;, idx, iArray[idx].ToString()));
    }
    for (int idx = 0; idx &amp;lt; iList.Count; idx++)
    {
        Debug.Log(string.Format(&quot;iList[{0}] = {1}&quot;, idx, iList[idx].ToString()));
    }
    foreach (var data in fDictionary)
    {
        Debug.Log(string.Format(&quot;iDictionary[{0}] = {1}&quot;, data.Key, data.Value));
    }

    Debug.Log(&quot;iVector = &quot; + iVector.x + &quot;, &quot; + iVector.y);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 Start에서 JSON 데이터를 테스트해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 오브젝트를 문자열인 JSON 데이터로 만드는 과정은 매우 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonConvert.SerializeObject()를 통해서 오브젝트를 매게 변수에 넣어주면 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 '직렬화'라고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1672363303731&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JsonTestClass jsonTest1 = new JsonTestClass();
string jsonData = JsonConvert.SerializeObject(jsonTest1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 반대로 문자열이 된 JSON 데이터를 오브젝트로 변환해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터를 다시 오브젝트로 바꿀 때는, 어떤 오브젝트로 변환하는지 명시적으로 함수에 알려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 JSON 데이터가 가진 구조가 함수에 알려준 클래스의 구조와 다르다면 변환 도중에 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 변환 후 아까 만든 Print() 함수를 통해 데이터를 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1672363389781&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JsonTestClass jsonTest2 = JsonConvert.DeserializeObject&amp;lt;JsonTestClass&amp;gt;(jsonData);
jsonTest2.Print();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;주의 사항&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MonoBehaviour를 상속받는 오브젝트를 Serialize 할 경우에 Self Referencing Loop, 즉 자기 참고 루프에 빠질 수 있게 때문에 주의가 필요하다.&lt;/li&gt;
&lt;li&gt;MonoBehaviour를 상속 받는 오브젝트를 Serialize 하는 대신, 필요한 property를 클래스로 묶어서 해당 클래스만 Serialize 하거나 이후에 설명할 유니티가 기본 제공하는 JsonUtility를 통해 Serialize 하도록 하자.&lt;/li&gt;
&lt;li&gt;Vector3 역시 자기 참조 루프에 빠지게 되니 따로 Serialize용 Vector 클래스를 만들어서 사용하도록 하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1672363585474&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SerializableVector3
{
    public float x;
    public float y;
    public float z;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JSON Utility&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에서 기본 제공되는 Json Utility에 대해서 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonUtility.ToJson() 함수를 통해 간단하게 오브젝트를 JSON 데이터로 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 JSON 데이터에서 오브젝트로 만들 때는 JsonUtility.FromJson&amp;lt;objectType&amp;gt;(jsonData); 함수를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1672363675108&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var jTest1 = new JsonTestClass();
var jsonData = JsonUtility.ToJson(jTest1);
Debug.Log(jsonData);

var jTest2 = JsonUtility.FromJson&amp;lt;JsonTestClass&amp;gt;(jsonData);
jTest2.Print();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;MonoBehaviour를 상속받는 클래스의 Serialization&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MonoBehaviour를 상속 받는 클래스를 Serialize 할 때는 GetComponent 등으로 직접 가져온 클래스의 오브젝트로 Serialize 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1672363842601&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GameObject obj = new GameObject();
obj.AddComponent&amp;lt;TestMono&amp;gt;();
string jsonData = JsonUtility.ToJson(obj.GetComponent&amp;lt;TestMono&amp;gt;());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MonoBehaviour를 상속 받는 오브젝트의 JSON 데이터를 다시 오브젝트로 만들려면, 같은 형태의 오브젝트를 생성한 뒤 Deserialize해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonUtility.FromJsonOverwrite() 함수는 JSON 데이터를 다시 오브젝트로 변환할 때, 새로운 오브젝트를 만들지 않고 기존에 있는 오브젝트 클래스의 변수 값을 덮어씌우는 처리를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 이용하면 게임 내의 오브젝트를 JSON 데이터로 저장해서 게임 진행 상황을 저장하고 불러올 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1672364001305&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GameObject obj2 = new GameObject();
JsonUtility.FromJsonOverwrite(jsonData, obj2.AddComponent&amp;lt;TestMono&amp;gt;());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;주의 사항&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만 JsonUtility의 단점은 기본적인 데이터 타입과 배열, 리스트에 대한 Serialize만 지원한다.&lt;/li&gt;
&lt;li&gt;그래서 위에 Print() 함수로 출력된 텍스트를 보면 딕셔너리나 직접 정의한 클래스에 대한 데이터는 빠진 것을 알 수 있다.&lt;/li&gt;
&lt;li&gt;직접 생성한 클래스의 경우에는 [System.Serializable] 어트리뷰트를 붙여줘야만 JSON 데이터로 변환된다.&lt;/li&gt;
&lt;li&gt;딕셔너리를 사용하려면 외부 라이브러리를 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터 파일로 저장하기&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 JsonSaveLoader 테스트 클래스를 생성해 보자.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Start 함수에서 파일을 저장할 경로와 파일 이름으로 FileStream을 만들어준다. (보통 JSON 파일은 .json 확장자를 가진다)&lt;/li&gt;
&lt;li&gt;문자열인 JSON 데이터를 Encoding.UTF8의 GetBytes 함수로 byte 배열로 만들어준다.&lt;/li&gt;
&lt;li&gt;이렇게 만든 데이터를 FileStream에 써준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1672364341825&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System.IO;
using System.Text;
using UnityEngine;
using Newtonsoft.Json;

public class JsonSaveLoader : MonoBehaviour
{
    private void Start()
    {
        FileStream stream = new FileStream(Application.dataPath + &quot;/test.json&quot;, FileMode.OpenOrCreate);
        JsonTestClass jTest1 = new JsonTestClass();
        string jsonData = JsonConvert.SerializeObject(jTest1);
        byte[] data = Encoding.UTF8.GetBytes(jsonData);
        stream.Write(data, 0, data.Length);
        stream.Close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 저장된 JSON 파일을 읽어 들이는 방법도 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 FileStream을 생성하되, FileMode를 Open으로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음에 이 문자열을 Deserialize해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1672364413610&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FileStream stream = new FileStream(Application.dataPath + &quot;/test.json&quot;, FileMode.Open);
byte[] data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
stream.Close();
string jsonData = Encoding.UTF8.GetString(data);
JsonTestClass jTest2 = JsonConvert.DeserializeObject&amp;lt;JsonTestClass&amp;gt;(jsonData);
jTest2.Print();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity/Coding</category>
      <category>JSON</category>
      <category>Unity</category>
      <author>뎁프</author>
      <guid isPermaLink="true">https://devfrog.tistory.com/10</guid>
      <comments>https://devfrog.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 30 Dec 2022 10:42:01 +0900</pubDate>
    </item>
    <item>
      <title>PlayerPrefs</title>
      <link>https://devfrog.tistory.com/9</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 간단한 저장 시스템.&lt;/li&gt;
&lt;li&gt;PlayerPrefs는 별도의 암호화 없이 로컬 레지스트리에 저장된다.&lt;/li&gt;
&lt;li&gt;유저의 암호와 같이 중요한 데이터에는 사용하지 말 것. (중요한 데이터를 서버에 저장하자)&lt;/li&gt;
&lt;li&gt;환경설정(음량 조절, 기타 옵션) 같은 것들을 저장하기 용이하다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;정수형&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;실수형&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;문자열&lt;/span&gt; 형식만 지원한다.&lt;/li&gt;
&lt;li&gt;이보다 큰 데이터의 경우 이 3가지 데이터로 나눠서 저장해야 하는 불편함이 있다.&lt;/li&gt;
&lt;li&gt;딕셔너리나 헤쉬맵과 같이 key와 value의 형태로 데이터를 저장한다.&lt;/li&gt;
&lt;li&gt;key의 값은 string이며 value를 찾기 위한 식별자로 사용된다.&lt;/li&gt;
&lt;li&gt;중복된 key와 다른 value를 입력했다면 덮어쓰기로 인해 가장 마지막 value가 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PlayerPrefs 함수&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;입력&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PlayerPrefs.SetInt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PlayerPrefs.GetInt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PlayerPrefs.SetFloat&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PlayerPrefs.GetFloat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PlayerPrefs.SetString&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PlayerPrefs.GetString&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;입력 함수들:&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SetInt(string key, int value) : int 값을 해당 key에 저장한다.&lt;/li&gt;
&lt;li&gt;SetFloat(string key, float value) : float 값을 해당 key에 저장한다.&lt;/li&gt;
&lt;li&gt;SetString(string key, string value) : string 값을 해당 key에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;출력 함수들:&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GetInt(string key) : key에 저장된 int 값을 불러온다.&lt;/li&gt;
&lt;li&gt;GetFloat(string key) : key에 저장된 float 값을 불러온다.&lt;/li&gt;
&lt;li&gt;GetString(string key) : key에 저장된 string 값을 불러온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;기타 함수들:&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Save() : 수정된 모든 preferences를 파일에 저장한다.&lt;/li&gt;
&lt;li&gt;HasKey(string key) : key가 있는지 확인한다.&lt;/li&gt;
&lt;li&gt;DeleteKey(string key) : key에 저장된 value를 삭제한다.&lt;/li&gt;
&lt;li&gt;DeleteAll() : 모든 데이터를 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;pre id=&quot;code_1672196522737&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void SaveData()
{
  PlayerPrefs.SetString(&quot;Input&quot;, inputField.text);
}

public void LoadData()
{
  inputField.text = PlayerPrefs.GetString(&quot;Input&quot;);
}

public void DeleteData()
{
  PlayerPrefs.DeleteKey(&quot;Input&quot;);
  PlayerPrefs.DeleteAll();
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PlayerPrefs 저장 위치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Playerprefs는 데이터를 Window에서 Registery에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Window &amp;gt; 실행 &amp;gt; regedit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;HKEY_CURRENT_USER/Software/Unity/UnityEditor/CompanyName/ProjectName&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;안드로이드는 안드로이드 api에서 제공하는 SharedPreferences에 저장한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;암호화 없이 로컬에 저장된다는 점이 쓰기 어렵게 만든다.&lt;/li&gt;
&lt;li&gt;데이터 타입의 제한도 큰 감점 요소.&lt;/li&gt;
&lt;li&gt;그냥 Json파일로 저장하는게 나을지도?&lt;/li&gt;
&lt;li&gt;Option이나 Menu와 관련된 데이터를 저장하는 용도로 괜찮아 보인다.&lt;/li&gt;
&lt;li&gt;빠르게 프로토타이핑 할 때는 사용해도 좋겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PlayerPrefs - Unity Documents: &lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://docs.unity3d.com/kr/530/ScriptReference/PlayerPrefs.html&quot;&gt;docs.unity3d.com/kr/530/ScriptReference/PlayerPrefs.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672198021007&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;UnityEngine.PlayerPrefs - Unity 스크립팅 API&quot; data-og-description=&quot;Stores and accesses player preferences between game sessions.&quot; data-og-host=&quot;docs.unity3d.com&quot; data-og-source-url=&quot;https://docs.unity3d.com/kr/530/ScriptReference/PlayerPrefs.html&quot; data-og-url=&quot;https://docs.unity3d.com/kr/530/ScriptReference/PlayerPrefs.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.unity3d.com/kr/530/ScriptReference/PlayerPrefs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.unity3d.com/kr/530/ScriptReference/PlayerPrefs.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;UnityEngine.PlayerPrefs - Unity 스크립팅 API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Stores and accesses player preferences between game sessions.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.unity3d.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity/Coding</category>
      <category>PlayerPrefs</category>
      <category>Unity</category>
      <author>뎁프</author>
      <guid isPermaLink="true">https://devfrog.tistory.com/9</guid>
      <comments>https://devfrog.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 28 Dec 2022 12:29:57 +0900</pubDate>
    </item>
  </channel>
</rss>