반응형

 

 

 

 

SafeArea

- 해상도가 세로로 길어지면서 생겨난 개념이다.

- 초창기에는 iPhone의 노치라는 변태 해상도(?)를 출발로 현재는 안드로이드도 펀치홀 디스플레이가 적용되서 이런 비정상적인 화면에 맞춰 UI를 정상적으로 보이게 배치해줘야한다.

- 안드로이드같은 경우 유니티 내장 기능에서 펀치홀 라인을 레터박스로 날리는 옵션지 주어져있다. (하지만 이 포스팅에서 이 옵션을 사용하지 않는다.)

 

 

 

Screen.safeArea

- 단어 뜻 그대로 화면의 안전 영역의 픽셀 단위를 반환한다.

- 기기 해상도 값과 SafeArea 해상도 값을 계산해서 UI의 anchor의 위치값을 계산해서 UI Rect의 새로운 값을 만들어낸다.

 

 

 

Hierachy 세팅

- Canvas 자식으로 빈 UI 게임오브젝트를 넣어주고 자식으로 넣은 게임오브젝트의 RectTransform AnchorPreset을 화면 전체로 세팅해준다.

- 다른 UI 요소는 위에 자식으로 넣은 게임오브젝트의 자식으로 넣어주면 된다.

- 다음 코드로 스크립트를 생성해 Safe Area를 적용할 게임오브젝트에 붙여준다.

 

 

 

코드

using UnityEngine;

public class SafeArea : MonoBehaviour
{
    private void Awake()
    {
        ApplySafeArea();
    }

    private void ApplySafeArea()
    {
        Rect safeAreaRect = Screen.safeArea;
        Vector2 anchorMin = safeAreaRect.position;
        Vector2 anchorMax = safeAreaRect.position + safeAreaRect.size;

        //Calculate anchorMin
        anchorMin.x /= Screen.width;
        anchorMin.y /= Screen.height;

        //Calculate anchorMax
        anchorMax.x /= Screen.width;
        anchorMax.y /= Screen.height;

        var rectTr = GetComponent<RectTransform>();
        //Apply anchor
        rectTr.anchorMin = anchorMin;
        rectTr.anchorMax = anchorMax;

        rectTr.offsetMin = Vector2.zero;
        rectTr.offsetMax = Vector2.zero;
    }
}
  • 먼저 anchor의 Min, Max 값을 구한다음, offset을 각각 0으로 지정해주면 화면(Rect)이 SafeArea에 맞춰진다.
  • Screen.safeArea.position의 값을 포함시키는 이유는 iPhone의 경우 Safe Area가 화면 밑부분을 더 띄워주므로 다른 위치값이 나온다.
  • Screen.safeArea 실제 기기에서만 SafeArea에 맞는 값을 반환하기 때문에 에디터에서는 SafeArea가 적용 안되서 보이는게 정상이다.

 

 

 

결과 확인 (Safe Area 스크립트 적용 차이, 유니티 내장 Safe Area 적용)

 

 

 

 

 

반응형
반응형

 

 

 

 

유니티 이벤트 함수

- 유니티는 미리 정의된 순서대로 실행되는 이벤트 함수들이 존재한다.

- 종류와 실행 순서(Life cycle)는 다음 페이지 참조

 

이벤트 함수의 실행 순서 - Unity 매뉴얼

Unity 스크립트를 실행하면 사전에 지정한 순서대로 여러 개의 이벤트 함수가 실행됩니다. 이 페이지에서는 이러한 이벤트 함수를 소개하고 실행 시퀀스에 어떻게 포함되는지 설명합니다.

docs.unity3d.com

 

 

 

 

Awake(), Start(), OnEnable()

- Awake

  • Scene이 로드될 때 스크립트가 포함된 게임오브젝트가 초기화되거나 비활성화된 게임오브젝트가 활성화 될 때, Instantiate로 생성된 게임 오브젝트가 초기화 된 후에 호출된다.
  • 스크립트가 비활성화 되어있을때도 호출이 되며, 게임 오브젝트가 초기화된 후, 단 한번 호출된다.
  • 모든 게임오브젝트가 초기화된 후 호출되기 때문에 Find와 같은 함수를 사용하여 안전하게 쿼리할 수 있다.
  • 멤버를 초기화할 때 사용된다.
  • 호출하는 순서가 정해져있지 않기 때문에 스크립트들 사이에서 Awake전이나 후에 호출되는것에 의존해서는 안된다.

- OnEnable

  • 게임오브젝트 또는 스크립트가 활성화될 때마다 호출된다. (1회성이 아님)
  • 이벤트 연결시 사용된다.

- Start

  • Update가 호출되기 전에 호출되는 함수이다.
  • Awake와 마찬가지로 단 한번 호출된다.
  • Awake와 달리 스크립트가 비활성화 되어있을때 호출이 되지 않아서 비활성화 된 스크립트에서는 Awake와 동일한 프레임에 호출되지 않을수도 있다.
  • 한 객체가 다른 객체에 의존하는 경우에 유용하게 사용할 수 있는 함수이다. (의존하는 객체 초기화는 Start에서 의존되는 객체 초기화는 Awake에서하게되면 안정성이 높아진다.)
  • 해당 함수는 코루틴으로 정의가 가능해서 yield문으로 흐름제어가 가능하다.

 

 

 

Update(), FixedUpdate(), LateUpdate()

- Update

  • 스크립트가 활성화 되어있을 때, 매 프레임마다 호출된다.
  • 게임의 핵심 로직을 작성한다.

- FixedUpdate

  • 물리 시스템 주파수에 따라 일정한 주기로 호출된다. (기본설정 기준 0.02초마다 호출됨.)
  • 프레임 속도에따라 Update보다 더 많이 호출될 수도 있고 적게 호출될 수도 있다.
  • 프레임 속도와 독립적으로 다른 타이머에서 호출되기 때문에 Time.deltaTime을 곱할 필요가 없다.

- LateUpdate

  • Update가 호출된 후 프레임마다 한 번씩 호출된다.
  • 일반적으로 Update에서 플레이어를 움직이는 계산을 하고, LateUpdate에서 카메라의 계산을 수행하기 위해 사용한다.

 

 

 

 

반응형
반응형

 

 

 

 

제네릭 싱글톤(Generic singleton)

- 싱글톤 : 객체의 인스턴스가 오직 하나인 디자인 패턴

- Generic class : 데이터 형식을 일반화한 클래스를 말한다.

 

 

코드

using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    [SerializeField]
    private bool dontDestroy = true;
    private static T instance = null;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = InitManager<T>();
            }
            return instance;
        }
    }

    protected static U InitManager<U>() where U : MonoBehaviour
    {
        GameObject go = null;
        U obj = FindObjectOfType<U>();
        if (obj == null)
        {
            go = new GameObject(typeof(U).Name);
            go.AddComponent<U>();
        }
        else
        {
            go = obj.gameObject;
        }

        DontDestroyOnLoad(go);
        return go.GetComponent<U>();
    }

    private void Awake()
    {
        if (instance == null)
        {
            if (dontDestroy)
            {
                Instance.Init();
            }
            else
            {
                instance = GetComponent<T>();
                Init();
            }
        }
        else
        {
            Destroy(gameObject);
        }
    }

    protected abstract void Init();
}
  • DontDestroyOnLoad를 사용하지 않으려면 인스펙터 창에서 해당 bool값을 false로 변경해준다.
  • Init 함수를 추상 함수로 지정해서 싱글톤으로 지정할 클래스의 초기화를 Init에서 하도록 유도한다.

 

 

 

 

반응형
반응형

 

 

 

 

JsonUtility

- 유니티에서 제공하는 JSON 클래스이다.

- 다른 JSON 라이브러리에 비해 기능이 적지만 성능이 좋다.

- 직렬화된 클래스, 구조체만 지원되고 배열, 리스트, 딕셔너리 등의 자료구조는 클래스로 래핑해야 JSON 변환이 가능하다.

 

 

 

직렬화(Serializable)

- C#은 복합 데이터 형식을 쉽게 스트림에 읽기/쓰기를 할 수 있게 하는 직렬화라는 메커니즘을 제공한다.

- 직렬화는 객체의 상태(필드에 저장된 값들)를 메모리에 저장 장치에 저장이 가능한 0과 1의 순서로 바꾸는것을 말한다.

- 직렬화는 원하는 데이터 위에 [Serializable] 어트리뷰트를 작성해주면 된다.

 

 

 

using System;
using UnityEngine;

public struct SomeStruct
{
    [SerializeField]
    private int m_iNum;
    [SerializeField]
    private bool m_bCheck;
    
    public SomeStruct(int i, bool b)
    {
        m_iNum = i;
        m_bCheck = b;
    }
}

//ToJson
SomeStruct ss = new SomeStruct(10, false);
string json = JsonUtility.ToJson(ss, true);

//FromJson
SomeStruct ss2 = JsonUtility.FromJson<SomeStruct>(json);
  • JSON으로 변환 할 클래스나 구조체를 반드시 직렬화 어트리뷰트를 작성해줄 필요는 없지만, 해당 클래스나 구조체가 다른 클래스의 멤버가 되면 반드시 직렬화 어트리뷰트를 작성해줘야한다.
  • 멤버 변수의 접근 지시자를 private로 할 경우 [SerializeField] 어트리뷰트를 붙여줘야 해당 데이터가 JSON으로 변환된다.
  • static, readonly, const 키워드가 붙은 멤버 데이터는 JSON으로 변환되지 않는다.
  • JSON으로 변환하지 못하게 할 멤버는 [NonSerialized] 어트리뷰트를 붙여준다.
  • FromJsonOverwrite(json, object)는 추후 작성할 예정(기능 파악 중..)

 

※※ 직렬화 클래스(혹은 구조체)에 멤버변수가 추가된 경우, 해당 멤버변수에 대한 에러가 발생하지 않고 기본값으로 초기화된다.

 

 

 

래핑 클래스

- 배열이나 리스트 등의 자료구조는 API에 직접 전달하는 기능은 지원되지 않기 때문에, 클래스나 구조체로 래핑 해서 변환한다.

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

public static class JsonWrapper
{
    #region ARRAY WRAPPER
    [Serializable]
    private class ArrayWrapper<T>
    {
        [SerializeField]
        private T[] m_items;
        public T[] Items { get { return m_items; } }
        public ArrayWrapper(T[] arr)
        {
            m_items = arr;
        }
    }

    public static string ToJson<T>(T[] array, bool prettyPrint = false)
    {
        ArrayWrapper<T> wrapper = new ArrayWrapper<T>(array);
        //bool : 출력 제어 (로그 출력 시, 보기 좋게 출력하는 기능 지원. default : false)
        return JsonUtility.ToJson(wrapper, prettyPrint);
    }

    public static T[] ArrayFromJson<T>(string json)
    {
        ArrayWrapper<T> wrapper = JsonUtility.FromJson<ArrayWrapper<T>>(json);
        return wrapper.Items;
    }
    #endregion

    #region LIST WRAPPER
    [Serializable]
    private class ListWrapper<T>
    {
        [SerializeField]
        private List<T> m_list;
        
        public List<T> ToList()
        {
            return m_list;
        }

        public ListWrapper(List<T> list)
        {
            m_list = list;
        }
    }

    public static string ToJson<T>(List<T> list, bool prettyPrint = false)
    {
        ListWrapper<T> wrapper = new ListWrapper<T>(list);
        return JsonUtility.ToJson(wrapper, prettyPrint);
    }

    public static List<T> ListFromJson<T>(string json)
    {
        ListWrapper<T> wrapper = JsonUtility.FromJson<ListWrapper<T>>(json);
        return wrapper.ToList();
    }
    #endregion
}
  • Dictionary 역시 위와 같은식으로 래핑 클래스를 정의해야한다.

 

 

 

참고

 

JSON 직렬화 - Unity 매뉴얼

JSON 직렬화는 오브젝트와 JSON 포맷을 상호 변환하는 기능입니다. 이 기능은 웹 서비스와 상호작용할 때 유용하거나 단순히 데이터를 텍스트 기반 포맷으로 간편하게 패킹하고 언패킹하는 데 유

docs.unity3d.com

 

 

스크립트 직렬화 - Unity 매뉴얼

직렬화는 데이터 구조나 오브젝트 상태를 Unity 에디터가 저장하고 나중에 재구성할 수 있는 포맷으로 자동으로 변환하는 프로세스를 말합니다. Unity 에디터에서는 저장 및 로딩, 인스펙터 창, 인

docs.unity3d.com

 

 

 

 

 

반응형
반응형

 

 

 

※ isometric 타일맵을 기준으로 작성된 포스팅입니다.

 

타일맵 컴포넌트에서 타일을 그리면 그려진 타일의 배열의 크기를 가져올 수 있다. isometric 기준으로 타일맵의 배열은 다음과 같이 구성된다.

 

 

주의할 점이 있는데 타일맵 자체에서 배열을 제공하는것이 아니라 타일맵이 그려진 사이즈(Vector2) 값을 가지고있는것이기 때문에 타일맵의 사이즈 값과 타일맵의 셀 바운드값을 가져와서 직접 배열을 초기화해줘야한다.

이 때, 셀 바운드값은 최하단 타일을 기준으로 값이 정해지기 때문에 이 값이 무조건 0에서부터 시작하지 않는다.

 

 

  • 타일맵 초기화 코드
private void InitTileArray()
{
    Tilemap tilemap = GetComponent<Tilemap>();
    Vector2[,] tileArray = new Vector2[m_cTilemap.size.y, m_cTilemap.size.x];
    
    Vector3Int tilePos;
    for (int y = tilemap.cellBounds.yMin; y < tilemap.cellBounds.yMax; y++)
    {
        for (int x = tilemap.cellBounds.xMin; x < tilemap.cellBounds.xMax; x++)
        {
            tilePos = new Vector3Int(x, y, 0);
            if (tilemap.HasTile(tilePos))
            {
                int arrX = x - tilemap.cellBounds.xMin;
                int arrY = y - tilemap.cellBounds.yMin;
                Vector3 tileWorldPos = tilemap.CellToWorld(tilePos);
                tileArray[arrY, arrX] = tileWorldPos;
            }
        }
    }
}

  ※ tileWorldPos의 값은 타일의 중앙좌표가 아닌 하단 모서리의 좌표이기 때문에, 중앙좌표를 위한 추가값을 넣어줘야한다.

 

 

 

 

  • 추가 주의사항

- 특정 위치에 타일맵을 한번 그리게되면 그 위치의 타일맵을 지운다고해도 그 위치는 바운드값에 포함이 된다. 그렇기때문에 타일맵 컴포넌트에서 옵션(…)을 눌러서 'Compress Tilemap Bounds'를 눌러주거나 코드에 CompressBounds()를 호출해준다.

 

 

 

 

반응형

+ Recent posts