제한된 이미지 사이즈 안에서 들어가야 할 글자가 초과될 경우 사용하면 될듯하다.

좌우 방향 설정과 속도조절이 가능하다.

 

 

깃허브 링크

https://github.com/hahahohohun/PublicCode/blob/main/README.md#-cmovetextcs

 

GitHub - hahahohohun/PublicCode

Contribute to hahahohohun/PublicCode development by creating an account on GitHub.

github.com

+) 특정 상태에 따라 흐를 것인가 멈출 것인가를 기능을 넣으면 좋을 듯

반응형

선택한 오브젝트를 제외한 오브젝트의 SetActive를 false처리한다. 단축키는 Ctrl + Shift + Q

 

같은 부모 오브젝트 아래에서 작동

 

최상단 오브젝트에서 작동

 

-----------------------------------------------------------------------------------

2021.12.13

단축키를 사용하기전에 애초에 꺼져있는 오브젝트는 다시 켜지지 않도록 처리

1.Ctrl + Shift + Q

2.해당 오브젝트의 포지션값 변경

3.Ctrl + Shift + Q (꺼져있던 오브젝트 다시 활성화)

-----------------------------------------------------------------------------------

 

 

https://github.com/hahahohohun/PublicCode/blob/main/README.md#eccustomtoolscs

 

반응형

오브젝트에 인덱스를 추가하여 이름을 변경이 필요할때!

ctrl + D 를 통해 생성한 오브젝트는 (1),(2),(3)~~~ 이름 마지막에 이렇게 숫자가 붙는다. 하나 하나 바꾸기 번거롭기때문에 이것을 한꺼번에 변경이 가능한 간단한 툴을 만들었다.

 

https://github.com/hahahohohun/PublicCode/blob/main/README.md#ecnamingcs

 

반응형

유니티 커스텀 에디터로 맵툴 만들기 두 번째 포스팅

오늘 공부할 에디터 뷰


            GUILayout.BeginVertical("box");

            GUILayout.Label("Map Name");
            _strMapName = EditorGUILayout.TextField(_strMapName/*, GUILayout.ExpandWidth(true)*/);
            GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;
            if (GUILayout.Button("Create New Map", GUILayout.ExpandWidth(true)))
            {
                Debug.LogError("create " + _strMapName);
                CreateMap();
                _bEditOn = true;
            }

            GUILayout.Space(12);

1. GUILayout.BeginVertical("box"); 박스로 묶는 범위(진한 회색으로)를 지정하게 된다.

2. Create New Map 버튼 : EditorGUILayout.TextField()에 string값이 존재 시 활성화.

3. CreateMap()함수 : 새로운 맵을 생성함

 

 


            GUI.enabled = true;
            GUILayout.Label("Load MapData");
            _txtAssetMapContent = (TextAsset)EditorGUILayout.ObjectField(_txtAssetMapContent, typeof(TextAsset), GUILayout.ExpandWidth(true));
            GUI.enabled = _txtAssetMapContent != null ? true : false;
            if (GUILayout.Button("Edit Map", GUILayout.ExpandWidth(true)))
            {
                LoadMap(_txtAssetMapContent.text);
                _bEditOn = true;
            }
            GUILayout.EndVertical();

1. EditorGUILayout.ObjectField() : 끌어다 붙여도 되고, 동그라미 눌러서 프로젝트 리소시스에 텍스트 파일을 선택해도 됨

2. Edit Map 버튼 : _txtAssetMapContent에 값이 들어와야 활성화.

3. LoadMap() 함수 : 선택한 텍스트 파일을 가지고 하이어라키에 맵 파일을 로드시킨다.

반응형

커스텀 에디터를 이용한 맵툴 제작

 

작업 결과물

 

GUILayout.Label(string text );

 GUILayout.Label("Map Name");

=> 윈도 류창에 텍스트 라벨에 원하는 string을 표시할 수 있다.

 

EditorGUILayout.TextField( )

_strMapName = EditorGUILayout.TextField(_strMapName);
 GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;

=> 텍스트 입력필드, GUI.enabled을 통해 입력값이 있어야 

 

GUI.enabled

GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;

=> 해당 코드 아래에 버튼이나 입력 칸을 활성화 처리 여부를 정한다.

 

GUILayout.Button( )

if (GUILayout.Button("Create New Map", GUILayout.ExpandWidth(true)))
{
    CreateMap();
    _bEditOn = true;
}

=> 버튼 , 클릭하면 if안에 함수들이 실행된다.

 

GUILayout.Space()

GUILayout.Space(12);

=> 여백을 준다.

 

EditorGUILayout.ObjectField()

_txtAssetMapContent = (TextAsset)EditorGUILayout.ObjectField(_txtAssetMapContent, typeof(TextAsset), GUILayout.ExpandWidth(true));

=> 설정한 타입으로 입력받을수있는 링크 필드가 생긴다. 나는 TextAsset으로 텍스트 관련 오브젝트만 입력받을 수 있도록 했지만 해당 타입을 그냥 object라고 하면 어떠한 오브젝트도 받을 수 있는 상태가 된다.

반응형

#1에 이어 #2이다. 이번에는 Client코드를 살펴보자.

 

먼저 소켓에 접속하는 함수

 

ConnectToServer()

    public void ConnectToServer()
    {
        // 이미 연결되었다면 함수 무시
        if (_bSoketReady) return;

        // 기본 호스트/ 포트번호
        string ip = ins_inputIPNumber.text == "" ? "127.0.0.1" : ins_inputIPNumber.text;
        int port = ins_inputPort.text == "" ? 9999 : int.Parse(ins_inputPort.text);

        // 소켓 생성
        try
        {
            _tcpSoket = new TcpClient(ip, port);
            _NetworkStream = _tcpSoket.GetStream();
            _writer = new StreamWriter(_NetworkStream);
            _reader = new StreamReader(_NetworkStream);
            _bSoketReady = true;
        }
        catch (Exception e)
        {
            Chat.instance.ShowMessage($"소켓에러 : {e.Message}");
        }
    }

 

 

OnIncomingData

    void OnIncomingData(string data)
    {
        if (data == "%NAME")
        {
            _strClientName = ins_inputNickName.text == "" ? "Guest" + UnityEngine.Random.Range(1000, 10000) : ins_inputNickName.text;
            Send($"&NAME|{_strClientName}");
            return;
        }

        Chat.instance.ShowMessage(data);
    }

if(data = "%NAME") : 으로 들어오는 %,&인 특수문자로 체크하는데 영상 댓글에 보니 들어오기 전 이름, 나갈 이름을 구분하는 용도라 한다. 여기서는 % 들어오기 전에 이름인거같다.

사용자가 입력한 string이 없으면 Guest + 숫자로 임의로 만들어준다.

send : &NAME으로 서버에 보낸다.

 

연결되는 부분이 Server클래스에 OnIncomingData()에서

//Server.cs.

void OnIncomingData(ServerClient c, string data)
    {
        if (data.Contains("&NAME"))
        {
            c.clientName = data.Split('|')[1];
            Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
            return;
        }

        Broadcast($"{c.clientName} : {data}", _listClients);
    }

연결된 클라이언트의 이름을 접속한 클라이언트들에게 보내는 내용이다.

 

OnSendButton

    public void OnSendButton(InputField SendInput)
    {
#if (UNITY_EDITOR || UNITY_STANDALONE)
        if (!Input.GetButtonDown("Submit")) return;
        SendInput.ActivateInputField();
#endif
        if (SendInput.text.Trim() == "") return;

        string message = SendInput.text;
        SendInput.text = "";
        Send(message);
    }

메시지 보내기 버튼으로 입력된 내용을 send 해주는 과정이다.

 

 

OnApplicationQuit

    void OnApplicationQuit()
    {
        CloseSocket();
    }
    
    void CloseSocket()
    {
        if (!_bSoketReady) return;

        _writer.Close();
        _reader.Close();
        _tcpSoket.Close();
        _bSoketReady = false;
    }

앱 종료하면 연결된 소켓들의 정보를 끊어준다.

 

tcp에 대한 간단한 개념잡기에는 괜찮은거 같다.

 

참고:

https://www.youtube.com/watch?v=y3FU6d_BpjI

반응형

유튜브에 c# tcp소켓통신 채팅 관련 영상이 있었다 그 영상의 코드를 공부해봤다.

 

먼저 ServerClinet 

public class ServerClient
{
    public TcpClient tcp;
    public string clientName;

    public ServerClient(TcpClient clientSocket)
    {
        clientName = "Guest";
        tcp = clientSocket;
    }
}

TcpClient : TCP 네트워크 서비스에 대한 클라이언트 연결을 제공한다.

 

ServerCreate

    public void ServerCreate()
    {
        _listClients = new List<ServerClient>();
        _listDisconnect = new List<ServerClient>();

        try
        {
            int port = ins_PortInput.text == "" ? 9999 : int.Parse(ins_PortInput.text);
            _server = new TcpListener(IPAddress.Any, port);
            _server.Start(); //바인드 처리.

            StartListening();
            _bserverStarted = true;
            Chat.instance.ShowMessage($"서버가 {port}에서 시작되었습니다.");
        }
        catch (Exception e)
        {
            Chat.instance.ShowMessage($"Socket error: {e.Message}");
        }
    }

port로 포트번호를 만들어준다. (현재 사용하지 않는 포트번호로 지정)

IPAddress.Any : 모든 클라이언트에서 오는 요청을 받겠다는 의미

TcpListener.Start : 들어오는 연결 요청의 수신을 시작. (바인드)

 

StartListening

    private void StartListening()
    {
        _server.BeginAcceptTcpClient(AcceptTcpClient, _server);
    }

BeginAcceptTcpClient(AsyncCallback? callback, object? state) : 들어오는 연결 시도를 받아들이는 비동기 작업을 시작한다.

AsyncCallback은 작업이 완료됐을때 호출한다.

state는 연결을 받아들이는 작업자에 대한 정보가 들어 있는 정의 개체이다. 작업이 완료되면 callback대리자에게 전달한다.

 

AcceptTcpClient

    private void AcceptTcpClient(IAsyncResult ar)
    {
        TcpListener listener = (TcpListener)ar.AsyncState;
        _listClients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
        StartListening();

        // 메시지를 연결된 모두에게 보냄
        Broadcast("%NAME", new List<ServerClient>() { _listClients[_listClients.Count - 1] });
    }

매개변수

IAsyncResult : 비동기 작업의 상태를 나타낸다.

IAsyncResult 

TcpListener.EndAcceptTcpClient() : 들어오는 연결 시도를 비동기적으로 받아들이고 원격 호스트 통신을 처리할 새로운 TcpClient을 만든다. (리턴 TcpListener)

 

AcceptTcpClient이 실행 된후에 다시 StartListening()를 실행시켜 새로운 Client를 받을 수 있도록 처리한다.

 

Update()

	priavate void Update()
    {
        if (!_bserverStarted)
        { 
            return;
        }

        foreach (ServerClient c in _listClients)
        {
            // 클라이언트가 여전히 연결되있나?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                _listDisconnect.Add(c);
                continue;
            }
            // 클라이언트로부터 체크 메시지를 받는다
            else
            {
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = new StreamReader(s, true).ReadLine();
                    if (data != null)
                        OnIncomingData(c, data);
                }
            }
        }

        for (int i = 0; i < _listDisconnect.Count - 1; i++)
        {
            Broadcast($"{_listDisconnect[i].clientName} 연결이 끊어졌습니다", _listClients);

            _listClients.Remove(_listDisconnect[i]);
            _listDisconnect.RemoveAt(i);
        }
    }

Update에서 클라이언트들의 접속여부와 채팅 내용을 체크하도록 한다.

 

IsConnected

   bool IsConnected(TcpClient c)
    {
        try
        {
            if (c != null && c.Client != null && c.Client.Connected)
            {
                if (c.Client.Poll(0, SelectMode.SelectRead))
                    return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);

                return true;
            }
            else
                return false;
        }
        catch
        {
            return false;
        }
    }

 

if(c.Client.Poll(0, SelectMode.SelectRead))

TcpClient.Poll(int microseconds, SelectMode mode); : 메소드는 간단히 말하자면 하려는 행동이 완료할 수 있는 상태면 true를 리턴한다. 

매개변수

microseconds : 응답을 기다리는 시간

SelectMode : 오류상태모드, 읽기 상태 모드, 쓰기 상태 모드를 선택한다.

=> 데이터를 읽을 수 있다면 true를 반환한다.

 

!(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);

socket.Receive(Byte [], Int32, Int32, SocketFlags, SocketError)

매개변수

byte[] 수신된 데이터에 대한 스토리지 위치인 Byte형식의 배열

SoketFlags.Peek : 소켓 전송 및 수신 동작을 지정(Peek:들어오는 메시지를 미리 본다)

=> 1바이트를 보내고 실제 수신된 바이트를 확인하여 연결여부를 확인한다.

 

다시 위쪽에 else부분을 살펴보면,

            else
            {
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = new StreamReader(s, true).ReadLine();
                    if (data != null)
                        OnIncomingData(c, data);
                }
            }

TcpClient.GetStream() : 데이터를 보내고 받는 데 사용되는 NetworkStream을 반환한다.

NetworkStream.DataAvailable() : 데이터를 읽을 수 있는지 여부를 나타내는 값을 가져온다.( 읽을 수 있으면 true, 그렇지 않으면 false)

 

이제 NetworkStream.DataAvailable() 까지 성공했다면 밑에 함수를 통해

    void OnIncomingData(ServerClient c, string data)
    {
        if (data.Contains("&NAME"))
        {
            c.clientName = data.Split('|')[1];
            Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
            return;
        }

        Broadcast($"{c.clientName} : {data}", _listClients);
    }

 

Broadcast()

    void Broadcast(string data, List<ServerClient> cl)
    {
        foreach (var c in cl)
        {
            try
            {
                StreamWriter writer = new StreamWriter(c.tcp.GetStream());
                writer.WriteLine(data);
                writer.Flush();
            }
            catch (Exception e)
            {
                Chat.instance.ShowMessage($"쓰기 에러 : {e.Message}를 클라이언트에게 {c.clientName}");
            }
        }
    }

리스트로 받은 모두 ServerClient에 메세지를 전달하는 과정이다.

StreamWriter.Flush : 현재writer의 모든 버퍼를 지우면 버퍼링된 모든 데이터가 내부 스트림에 쓰여진다.

 

참고

https://www.youtube.com/watch?v=y3FU6d_BpjI 

https://docs.microsoft.com/ko-kr/dotnet/api/system.net.sockets

반응형

번들관련 공부를하다가 동적으로 생성되는 파일이나 저장되는 데이터들이 어디로 저장되는지 알아보았다.

 

1. PlayerPrefs 저장위치

단말기에 저장되는 PlayerPrefs 저장경로

 

[레지스트리 편집기] -> [HKEY_CURRENT_USER] -> [SOFTWARE] -> [Unity] -> [UnityEditor] -> [DefaultCompany] ->["ProductName"] 

(DefaultCompany 와 ProductName은 ProjectSettings에서 볼수 있음)

Int형
string형

 


2. 앱 저장 경로 Application클래스

빌드된 패키지에는 에디터에서 폴더에 접근할수 없다. 

 

1) Application.dataPath

[C:\Users\사용자이름\AppData\LocalLow\회사이름]

읽기 전용


2) Application.persistentDataPath

[해당 프로젝트폴더 경로\Assets]

 

3) Application.streamingAssetsPath
[해당 프로젝트 폴더]

읽기 전용이다.

 

 

테스트 결과.

1) 에디터

2) 안드로이드

 


유니티에서 지정한 폴더들의 특징 정리

 

wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder

반응형

 

오랜만에 글을 작성한다.

 

사내 스터디 프로젝트에는 어드레서블을 사용한다. 그렇기 때문에 어드레서블에 학습이 필요했다.! 알아야 쓰지

 

리소스 폴더 사용

장점은 사용하기에 편리하다가 있다. 하지만, 리소스폴더에 메모리는 최대한 줄이도록 한다. 시작 시 최소한의 에셋만 남겨두어야겠지만? 리소스 폴더를 경계해야 한다.

단점으로는

1. apk사이즈가 커진다.

2. 앱 시작 시간이 길어진다.

3. 앱이나 폴더 변경 시 재 빌드를 해야 한다. (apk빌드에 묶이기 때문에 에셋 변경 시 무조건 재 빌드해야 한다.)

4. 에셋 이름 변경이 힘들다. (에셋을 로드할 때 경로를 바꿔줘야 하기 때문)

 

이런 문제점들을 보완할 수 있는 에셋 번들이 있다.

에셋 번들

장점은 에셋을 묶음 단위로 관리할 수 있다. 빌드 사이즈 절감과, 앱 시작 시간을 단축시킨다. 있지만

단점으로는

1. 번들의 종속성 문제.

같은 이미지를 Asset A와 Asset B에 묶여있다면 같은 이미지 1개로 처리될 것 같지만 2개의 이미지로 인식된다.

 

이러한 에셋 번들(빌드와 번들의 분리)의 장점과 리소스 폴더의 장점(비교적 편리함)을 가진 어드레 서블 에셋 시스템입니다. 


어드레 서블 에셋

어드레서블 에셋이란 어드레스가 할당된 에셋이다. 어드레스를 이용하여 에셋 위치에 상관없이 참조가 가능하다.

어드레서블 에셋 시스템

어드레스를 이용하여 에셋의 관리, 로딩, 빌드가 통합된 시스템이다.

 

어드레 서블 에셋 시스템. 특징만 봐도 편리해 보인다. 장점을 자세하게 보면

1. 에셋 빌드와 배포의 단순화 : 직접 레퍼런스, 리소스 폴더, 에셋 번들 분리가 단순화되고 편리해진다.

2. 효율적인 에셋 관리 : 종속성 파악 및 메모리 로드/언로드 현황을 볼 수 있다. (중복된 에셋으로 메모리를 낭비가 되는 것을 막는다.)


 

이제 어드레 서블 기능에 대해서 알아보자!

 

1. 해당 프리 팹에 어드레스 부여 하기.

어드레 서블 체크박스에 체크하고 어드레스를 부여하면된다. 그리고 잘 되어있는지 어드레서블 현황 대시보드를 통해 확인한다.

2. 에셋 로드하기.

1) Lables 으로 로드하기.

		Addressables.LoadAssetsAsync(type.ToString(), (Object obj) =>
		{
			if (!m_dicObject.ContainsKey(type))
				m_dicObject.Add(type, new Dictionary<string, Object>());

			m_dicObject[type].Add(obj.name, obj);

		});

위 코드에 type이 어드레서블에 Lables이다. 

 

2) 레퍼런스로 로드하기.

이런 식으로 인스펙터에 레퍼런스를 지정하여 로드하는 방식이다.

 

3) 어드레 서블 이름으로 로드하기.

    public void LoadAsset(string strAddressableName)
    {
       Addressables.InstantiateAsync(strAddressableName);
    }

2),3) 번은 현재 프로젝트에서는 사용하지 않는 방법이지만 어떤 식으로 로드하는지 찾아봤다. 

반응형

유니티가 제공하는 인앱결제 IAP에 대한 학습.

간단하게 인앱컨트롤러를 만들어서 게임시작과 동시에 인앱초기화작업이 되도록 한다.

 

1. var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

AddProduct ~~~...

builder를 통해 스토어에 등록된 상품의 아이디와 같은 데이터를 입력한다.

 

2. UnityPurchasing.Initialize(this, builder);

등록된 builder와 Store클래스를 초기화 한다.

 

초기화

1. IStoreListener.OnInitialized(IStoreController controller, IExtensionProvider extensions)

초기화가 성공하면 IStoreController 과 IExtensionProvider 을받습니다.

 

2. IStoreController 

Unity IAP를 제어하는데 사용하며 데이터및 구매 영수증을 포함한 제품을 저장하고 있다.

3. IExtensionProvider

상점 별 확장 기능에 대한 엑세스를 제공 (extensionProvider.GetExtension<IAppleExtensions>()) 등 특정 스토어 관련한 

 

초기화 실패

1. OnInitializeFailed

초기화 실패 시InitializationFailureReason를 통해 원인을 볼 수 있음 

 

구매실패

1. Product

구매 시도한 상품의 대한 정보.

2. PurchaseFailureReason

구매 실패한 원인

 

구현결과

인앱 초기화의 성공하면 Purchase함수를 통해 각각 상품 구매 테스트 진행

 

사실 실제 상용게임에서는 여러가지 이유로 구매실패가 많이 발생한다. 게임 구매 시도중에 단말기가 종료되거나 통신이 끊기는 경우가 있기에 거기에 대응이 되는 코드와 조건이 많이 필요하다. 

반응형

타이머를 간단하게 만들어봤습니다. 

 

타이머 시작 부분

    public void StartTimer(int nRemain, Text txtTimer = null, UnityAction EndCallBack = null)
    {
        ClearTimer();
        _CorTimer = StartCoroutine(CorStartTime(nRemain, txtTimer, EndCallBack));
    }

 

nRemain : 파라미터로 타이머를 동작시킬 시간

txtTimer : 남은 시간을 표기할 Text UI

EndCallBack : 타이머가 완료 후 실행시킬 함수.

 

코 루틴으로 작동되기 때문에 Clear 하는 부분을 꼭 넣어주도록 합니다. 

    private void ClearTimer()
    {
        if (_CorTimer != null)
        {
            StopCoroutine(_CorTimer);
            _CorTimer = null;
        }
        _bPause = false;
    }

타이머 동작 부분

    private IEnumerator CorStartTime(int nRemain = 5, Text txtTimer = null, UnityAction EndCallBack = null)
    {
        while (nRemain >= 0)
        {
            txtTimer.text = nRemain + " 초 남음";

            while (_bPause)
            {
                yield return null;
            }

            yield return new WaitForSeconds(1f);
            nRemain--;
        }
        EndCallBack?.Invoke();
    }

 

이 타이머스크립트에는 코 루틴으로 작동되며, WaitForSeconds(1f)로 1초마다 남은 시간을 감소하도록 했습니다. 저는 EndCallBack으로 델리게이트로 넘겨받도록 했는데 이유는 다른 곳에서도 쓰일 수 있도록 했습니다. 

 

 

그리고 매프 레임 텍스트만 바꾸지만 매 프레임 단위로 실행시키는 함수가 필요하면 EndCallBack처럼 받고 매프 레임 실행시켜주면 되겠죠. 


일시정지

타이머를 잠깐 멈추는 일시정지를 하는 부분입니다. 버튼 부분에 달아주고 OnPointerDown과 OnPointerUp을 통해 제어하도록 해줬습니다.

public class EventButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    private TimerUI _TimerUI = null;
    public void SetData(TimerUI TimerUI)
    {
        _TimerUI = TimerUI;
    }
    //클릭 누름.
    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.LogError("퍼즈");
        _TimerUI.ins_Timer.SetPause(true);
    }
    //클릭 뗌.
    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.LogError("퍼즈 취소");
        _TimerUI.ins_Timer.SetPause(false);
    }

}

 ins_Timer는 타이머 동작부분이 구현되어있는 스크립트입니다. SetPause를 통해 코루틴을 잠시 멈추도록합니다.

 


구현결과

반응형

스크롤 셀을 만들 때 셀에 들어가는 데이터가 같은 글자 수면 참 좋겠지만 그렇지 않은 경우가 많다. 예를 들어 퀘스트 스크롤을 제작한다고 하자.

퀘스트의 설명이 어떤 퀘스트는 아주 길고 또 어떤 퀘스트의 내용은 짧고 또는 예상할수 없는 길이의 데이터가 입력될 수 있다.

그렇다고 셀들의 크기를 설명이 큰 내용의 맞추어 크게 만들 수는 없다. 또 유지보수에도 좋지않다.(이미 지정한 크기 이상의 데이터는 추가될 수도 없기 때문이다.)

이쁘지않다.

그렇다면 입력되는 데이터 텍스트의 수의 따라 파란배경 이미지의 사이즈 변경이 필요하다.

방법은 Content Size Fitter와 Layout Group컴포넌트를 이용해하는 것이다. 

사실 위 두개 컴포넌트가 텍스트 사이즈의 맞게 바로바로 변경되면 좋겠지만 그렇지 못하는 경우가 있다. LayoutRebuilder.ForceRebuildLayoutImmediate요 함수를 통해 즉시 정렬하도록 해줬다.

(요 함수는 오브젝트가 켜져있어야 정상 작동한다)

반응형

유니티 그래픽스 최적화 스타트업책을 참고해서 작성하였습니다.

 

텍스처를 압축해야 하는 이유?

무한한 메모리를 가지고 있는 기기는 없기 때문이다. 그렇기에 텍스처 압축 기술을 통해 텍스처 역시 디스크나 메모리의 크기를 절약해야 한다. 

 

메모리 이슈

텍스처는 수많은 비트 데이터들도 이루어진 이미지 데이터이다. 그렇기에 GPU가 이 데이터를 이용하려면 대역폭이 필요하고 성능이슈가 발생하게 된다. 우리가 원하는 것은 적은 메모리로 합리적인 품질을 내야한다. 그렇기에 텍스처 데이터 사이즈를 관리해야한다.

하지만 텍스처 사이즈는 대역폭만의 문제만이 아니라 메모리 문제도 있다. 복잡한 씬을 렌더링하거나 데이터 사이즈가 큰 텍스처들을 한꺼번에 많이 올려두는 경우들이 있다. 이런 문제는 PC보다는 적은 메모리에 모바일 디바이스에서 더 큰 문제로 다가 올 수 있다.  

패키지용량 이슈(모바일)

모바일 앱마켓에서 게임을 받을 때 WIFI에서만 다운로드가 가능하거나 권장하는 문구를 본적이 있다. 이렇게 큰 용량의 앱들을 받게 될때 거부감이 들때가 있다. 특히 WIFI가 없는 곳에서는 더더욱! 그렇기 때문에 초기에 모두 받지 않고 설치 이후 패치등으로 데이터드를 다운로드 받게 하는데 이 역시도 너무 큰 용량이라면 거부감이 들 수있다.   

압축방식

이미지 압축은 크게 손실압축과 비손실 압축으로 두 종류가 존재한다. 비손실 압축은 원본 이미지를 그대로 유지하면서 데이터만 압축하는 방식이며, 비손실 압축은 원본 이미지의 퀄리티를 어느정도는 훼손하는 대신에 데이터 압축 비율을 높여서 데이터 크기 절약에 집중하는 방식이다. JPG와PNG는 GPU가 바로 읽어 들일 수 없다. 그렇기 때문에 텍스처를 위한 별도의 압축 방식을 이용해야한다. 유니티는 어떤 이미지를 Import하면 원본 이미지 포맷이 PNG인지 JPEG인지 등은 상관없이 특정 포맷의 이미지로 변환하여 사용한다. 텍스처 압축은 플랫폼별 특성이 다르므로 플랫폼마다 적절한 압축 포맷을 설정해주는게 좋다. 

압축종류의 대한 자세한 설명

https://ozlael.tistory.com/84

반응형

1. 그림자

빛이 지나가는 경로에 불투명한 물체가 존재하여 빛이 통과하지 못해 생기는 어두운 부분이다. 게임에서 그림자는 모든 물체를 입체감을 더해주는 요소이다. 그림자가 있으면 사물의 공간산 위치를 인지하기 쉽게 만들어주기 때문이다. 유니티는 그림자 또한 쉽게 표현하도록 기능이 구현되어있다. 

2. 그림자의 원리

유니티는 쉐도우맵기법을 사용하고 있다.  쉐도우 맵은 깊이 텍스처 값을 이용한 기법이다. 이 기법은 모든 굴곡에서 대응하고 셀프-쉐도우가 처리되는 등 높은 퀄리티를 보여준다.

쉐도우 맵의 원리? (포워드 렌더링)

먼저 뎁스 텍스처를 생성한다. 카메라를 통해서 현재의 씬이 렌더링 되는 정보를 렌더링 하는 과정이다. 렌더링 될 픽셀의 컬러 대신 카메라로부터 픽셀의 위치까지의 거리를 렌더링 한다. 카메라의 가까이 있는 픽셀일 수로 록 검은색, 멀 수록 흰색이다(그림자에 영향을 받는 모든 오브젝트들을 렌더링). 그 후 그림자 처리를 위한 별도의 버퍼가 필요하다. 이 버퍼에는 광원에서 바라보는 오브젝트의 픽셀의 깊이를 저장한다. 이 버퍼가 그림자를 위한 정보를 담아서 쉐도우 맵이다. 

그림자의 깊이를 저장하는 버퍼를 만든 후 픽셀 쉐이더에서 깊이를 비교하는 과정을 진행한다. 또한 넓은 영역을 커버하기 위해 여러 구역으로 나누기도 하고 계단 현상을 없애기 위해 여러 번 샘플링하여 필터링 처리를 하는 부가 기능들을 추가한다. 그러다 보니 그림자가 렌더링 비용 중 많은 부분을 차지하게 된다.

뎁스 텍스처와 쉐도우 맵이 완성되면 뎁스 텍스처에 있는 정보와 쉐도우 맵에 있는 정보를 비교하여 그림자 영역을 계산한다. 뎁스 텍처에 있는 픽셀들을 순회하면서 해당 픽셀을 광원으로부터의 거리로 변환하여 비교한다.

2. 어떤 그림자를 이용해야 할까

물론 실시간으로 그림자를 활성화시키고 유니티의 그림자 구현능력을 믿어보면 간단하고 편하다. 하지만 그림자를 렌더링 하는 것은 높은 성능을 요구하기 때문에 이렇게 그냥 맡기는 것은 무책임하다. 그렇기에 만들려는 게임이 무엇인지, 어떤 디바이스를 타깃으로 만들 건지 생각하고 그에 맞는 세팅이 필요하다. 

아래는 유니티가 제공하는 컴포넌트에서 쉽게 설정할 수 있는 셋팅방법이다. 

Mesh Renderer는 메쉬 필터의 지오메트리를 사용하여 오브젝트의 Transform컴포넌트에서 정의된 위치에서 렌더링 한다. 프로퍼티를 간단하게 살펴보면 

Cast Shadows : 그림자 라이트가 비춰질 때 메쉬가 그림자를 만든다. 

Receive Shadows : 오브젝트에 다른 오브젝트의 그림자를 받는 설정이다. 실시간으로 그림자가 적용된다. 

1. 그림자를 필요로 하지 않는 오브젝트인 경우는 CastShadow를 꺼놓아야 드로우콜을 절약할 수 있다.

2. ShadowsOnly로 선택하면 최종 화면에는 렌더링 되지 않지만 쉐도우 맵에는 반영되어 그림자를 만드는 오브젝트가 된다. 즉 화면에는 보이지 않는 특성이 있어 그림자 처리만을 위한 3D 모델에 활용할 수 있다.

씬뷰에는 메시만 보이고 게임 뷰에서는 그림자만 보인다.

3. Light -> Shadow Type

Soft Shadows 가 좋은 만큼 성능도 많이 차지한다. 픽셀 쉐이더의 부담이 된다. 모바일이라면 되도록 HardShadows를 사용한다.

HardShadows

 

SoftShadows

4. Light -> Ressolution

쉐도우맵의 해상도를 나타낸다.

Low Resolution
Very High Resolution

5. Shadow Distance

Edit -> Project Setting -> Quality -> Shadow Distance

Shadow Distance는 카메라로부터 그림자가 그려지는 거리를 의미한다.

이 값이 낮을수록 멀리 있는 그림자는 그리지 않는다. 그러는 대신에 쉐도우 맵에서 오브젝트의 픽셀이 차지하는 비중이 커져 시각적으로 해상도가 올라가는 효과를 낸다.

멀리 있는 슬라임은 그림자를 그리지 않는다.

멀리있는 슬라임의 그림자가까지 그리지만 비교적으로 낮은 품질의 그림자로 보인다.

7. Shadow Cascades 

케스트 케이드 쉐도우 맵은 쉐도우 맵을 범위별로 여러 개를 만들어서 사용하는 기법이다. 원근법이 적용된 뷰 프러스텀 공간에 오브젝트를 렌더링하는 유니티에서는 가까운 오브젝트일수록 차지하는 픽셀의 수가 적다. 

이러한 한계를 해결하기 위해 프러스텀 영역을 몇 단계로 나누어서 서로 다른 쉐도우 맵을 사용 한다.

Two Cascades는 2등분, Four Cascades는 4등분

Two Cascades

많이 쪼갤수록 그만큼 드로우 콜 수도 늘어난다. 

반응형

7. 배칭

드로우 콜에 대해서 알았다면, 이제 이것을 줄이는 방법을 알아야 한다. 가장 효율적인 방법으로 배칭이 있다. 효율적인 방법이라고 한 것은 여러의 배치를 하나로 묶어서 하나의 배치로 만드는 것이 바로 배칭인 것이다.  한 가지 예를 들어보면 오브젝트가 3개 있다면 원래는 3개의 드로우 콜 즉, 3개의 배치가 필요하지만 조건에 따라서 1개까지 줄이는 것이다. 메시가 서로 다른 오브젝트이지만 같은 머테리얼을 사용하면 하나의 배치로 만들 수 있다. 

배칭을 위해서는 다른메시를 이용하더라도 머티리얼을 공유해서 사용해야 한다. 파츠가 여러 개인 경우 메시는 각각 다르지만 하나의 머티리얼을 통해 색상을 입히는 것이다. 이렇게 하기 위해서는 여러 장의 텍스처를 하나를 묶어 텍스처 아틀라스 기법으로 하나의 텍스처로 여러 메시들이 사용할 수 있게 하는 것이다.

같은 머테리얼이라는 의미는 동일한 인스턴스를 말하며, 같은 텍스처를 사용하는 머테리얼이지만 그것이 2개라면 같은 인스턴스가 아닌 것이다.

다른 머테리얼

이러한 이유로 코드상에서 머티리얼을 접근할때 유의할 점이있다.  머테리얼 색상을 바꾸는 코드는 GetComponent<Renderer>().material.color = Color.red;을 사용하면 색상을 바꿀 수 있다. 하지만 이 코드를 사용할 때마다 계속해서 새로운 머테리얼을 복사 생성하게 된다. 그러면 다른 머테리얼의 인스턴스를 생성하는 것이다. 하지만 같은 인스턴스를 사용하는 방법도 있다. 머테리얼의 속성을 바꾸는 Renderer.sharedMaterial을 사용하면 된다. 물론 상황에 따라 사용하겠지만 한 가지 예시로,

아래 그림처럼 스켈레톤 몬스터는 모두 같은 머티리얼을 사용하고 있다. 그러면 현재는 한 개의 인스턴스가 있는 것이다. 그런데 플레이어가 스켈레톤을 공격하면 빨간색으로 피격 효과를  받게 되는데 같은 인스턴스를 공유하고 있기 때문에 피격을 당하지 않은 몬스터까지도 피격 효과(빨갛게) 변할 것이다. 

이것은 분명 개발 기획의도는 아닐 것이다. 그렇다면 어쩔 수 없이 몬스터마다 각각 다른 인스턴스를 생성해서 적용해야 하는 걸까?

2019/10/03 - [유니티/레퍼런스] - 유니티)MaterialPropertyBlock

 

유니티)MaterialPropertyBlock

sharedMaterial 머테리얼의 속성을 변경할때 SharedMaterial으로 색상을 바꾼다던지 쉐이더코드의 프로퍼티값들을 변경한다. 그런데 이 런타임중에 변경된 머테리얼의 값들은 종료해도 바뀐 값 그대로 유지된다...

funfunhanblog.tistory.com

MaterialPropertyBlock를 이용하면 객체의 변화를 주는 동안만 배칭이 되지 않고 작업이 끝나면 다시 배칭을 하는 방법이다.

 

8.  스태틱배칭

스태틱 배칭은 말 그대로 정적인 오브젝트 즉 움직이지 않는 오브젝트를 위한 기법이다. 주로 배경 오브젝트들이 해당되는데 이 기법을 이용하려면  Static 플래그를 켜야 한다. 이 플래그를 켜야 유니티가 이 오브젝트는 움직이지 않는 스태틱 오브젝트라는 것을 알 수 있기 때문이다. 

그런 후 다른 작업 없이 게임을 시작하면 자동으로 유니티는 스태틱 플래그가 켜진 오브젝트를 배칭 처리 작업을 진행한다. 오브젝트 버텍스가 많을수록 렌더링 연산 비용이 크다. 당연히 버텍스 수가 많으면 런타임 과정 중에 부담이 크다. 하지만 스태틱 배칭 된 오브젝트는 런타임에 연산이 이루어지지 않는 게 장점이 되는 것이다.

드로우콜수를 줄이기 위해 오브젝트들을 합쳐 내부적으로 하나의 메시로 만들어준다. 만일 3개의 오브젝트가 1개의 메시만 사용하더라도 3개의 메시를 합친 만큼 추가 메모리가 필요하게 되는 것이다. (내 생각에는 SpriteAtlas처럼 생각하면 될듯하다)

물론 이 작업은 유니티가 이러한 정보들을 저장해 야하기 때문에 메모리가 조금 더 필요하게 된다. 렌더링(드로우 콜)이냐 메모리냐 이 논제는 최적화에서 자주 결정해야 할 사항이다.  스태틱 배칭을 간단하게 정의하자면

대상 :움직이지 않는 오브젝트

장점 : 드로우콜 감소

단점 : 메모리 증가

9.  다이내믹 배칭

동적으로 움직이는 오브젝트들끼리 배칭 처리를 하는 기능이다. 동일한 머티리얼을 사용하며 자동으로 배칭이 이루어진다. 배칭 처리는 런타임상에 이루어지며 Static플래그가 체크되어 있지 않는 오브젝트들의 버텍스들을 모아서 합쳐주는 과정을 거친다. 이러한 버텍스들을 모아서 다이내믹 배칭에 쓰이는 버텍스 버퍼와 인덱스 버퍼에 담는다. GPU는 이를 가져가서 렌더링 하는 것이다. 매프 레임 데이터 구축과 갱신이 발생하기 때문에 오버헤드가 발생하게 된다. 다이내믹은 자동으로 이루어지지만 여러 가지 조건들이 있다.

1. 버텍스 900개 이하로 된 메시로만 적용된다.

2. 트랜스폼을 미러링 한 오브젝트들은 배칭 되지 않는다.(예를 들어 Scale값이 1인 오브젝트를 -1로 적용했을 때)

3. 다른 머티리얼 인스턴스를 사용하면 같이 배칭 되지 않는다.

4. 라이트맵이 있는 오브젝트는 추가 렌더러 파라미터를 가지는데, 일반적으로 동적으로 라이트 매핑된 오브젝트가 완전히 동일한 라이트맵의 지점에 있어야 배칭 된다.

5. 멀티 패스 셰이더를 쓰면 배칭이 되지 않는다.

스태틱 배칭은 버텍스 쉐이더에서 월드 스페이스로의 변환이 이루어진다. 즉 GPU에서 연산이 된다. 하지만 다이내믹 배칭은 실시간으로 오브젝트의 위치가 변환되기 때문에 CPU에서 연산이 이루어진다. 

10.  2D 스프라이트 배칭

2D도 배칭 작업이 가능하다. 텍스처 아틀라스 기법처럼 여러 이미지들을 한 이미지에 모아서 사용하는 방법과 SpriteAtlas를 사용하여 배칭 처리한다.

2019/01/09 - [유니티/최적화] - 유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

 

유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

스프라이트 아틀라스 유니티에서 아틀라스는 텍스처를 한곳에 모은 한장의 큰 텍스처라고 할수있다. 텍스처 하나씩 따로따로 사용하고 관리하는것은 효율적이지 못하다. 아틀라스를 사용하면 드로우콜을 줄일 수..

funfunhanblog.tistory.com

11.  GPU 인스턴 싱

적은 수의 드로우 콜을 사용하여 동일한 메시의 여러 복제본을 한 번에 그리거나 렌더링 한다. 특히 씬에서 반복적으로 나타나는 건물, 나무, 풀 등의 오브젝트를 보여줄 때 좋다. 또 같은 메시를 사용하더라도 간단한 변화 컬러, 스케일 등의 변화를 줄 수 있다. 별도의 메시를 메시를 생성하지 않기에(트랜스폼 정보를 별도의 버퍼에 저장) 다이내믹 배칭과 스태틱 배칭보다 오버헤드가 적다.

https://docs.unity3d.com/kr/2017.4/Manual/GPUInstancing.html

반응형

 

1. 드로우콜  이란

간단하게 CPU가 GPU에게 렌더링 작업을 수행하도록 명령을 하는것이다.

게임은 실시간 렌더링 어플리케이션이다. 

실시간으로 렌더링을 수행하기에, 한 프레임의 렌더링은 오브젝트를 하나하나 그릴때마다 여러 정보들을 CPU에서 GPU로 전달하여 그리도록 명령한다.

CPU가 중앙처리 장치인 만큼 GPU에게 명령하기 떄문에 CPU의 의해 핸들링 되는 구조이다.

여기서 알아야 할 것은 CPU와 GPU는 각각 자신들이 메모리 공간을 가지고 있다. CPU의 메모리는 RAM이 주로 사용된다. 즉 이 RAM에 각종 데이터들이 담기고 CPU가 이를 이용하여 연산을 수행하게 된다. 그래픽 처리도 GPU와 VRAM을 나누어 관리한다.

보통 모바일기기에서는 CPU와 GPU메모리를 물리적으로 나누지 않는다.(하나의 물리적인 메모리를 논리적으로 나누어 사용)

드로우 콜은 렌더의 상태를 알려주는 명령과 CPU가 GPU에게 그리라는 명령(DP Call)까지이며, 이런 과정은 한번에 보내는 것이 아니라 순차적으로 보내게된다. 이런 과정이 반복되며 화면에 오브젝트를 렌더링하게된다.

2. 데이터와 명령의 흐름

데이터의 흐름 : Storage(HDD, SDD ,SD) -> CPU메모리 -> GPU메모리 (메시정보, 텍스처, 등등)

드로우콜을 이해 하려면 데이터와 명령의 흐름을 알아야한다. 먼저 메시,텍스처 등 렌더링에 필요한 데이터들은 저장소 흔히 알고있는 HDD,SDD,SD에 먼저 위치한다. 그러고 CPU는 이 데이터들을 파싱하여 CPU메모리에 데이터를 옮긴다. 그 그런 후 마지막으로 GPU는 CPU의 메모리의 데이터들을 GPU메모리에 복사를 하게된다.

예를들어 GPU가 메시를 렌더링할 때 지오메트리 데이터를  GPU메모리에서 가져와 렌더링하게 된다. (GPU메모리에 렌더링에 필요한 데이터가 있다고 보면 됨)

메모리를 가져오고 전달하는 과정을 매프레임 때마다 하는것은 성능저하가 올 수 있기 때문에 특정 시점(로딩 시점, 씬전환시점)에 메모리 데이터를 올려 사용하도록 하는게 좋다. 계속해서 사용하는 데이터라면 메모리에 계속 올려두고 사용하고 그럴필요가 없는 메모리라면 데이터를 해제해 메모리 부담이 없도록 한다. 메모리에 영향이 없다면, 특정시점에 한번에 해제하는 것도 방법이다. 

3. 커맨드 버퍼 (Command Buffer)

렌더링은 CPU가 바로 GPU에 명령을 보낸다고 생각하지만 중간에 GPU가 할일을 보관하는 중재자가 있다. CPU와 GPU는 병렬작업을 통해 수행하는데 이러한 과정을 중재하는 커맨드버퍼가 있다. CPU가 GPU에게 명령을 하는 순간 GPU가 다른 일을 하고 있을 수 있기 때문에 그 명령을 쌓아 놓고 순차적으로 GPU가 처리 할 수 있도록 한다.  

4. CPU의 성능에 의존되는 드로우콜

렌더링이기 때문에 드로우콜은 CPU보다는 GPU의 영향이 크다고 생각하지만, GPU가 그리기 위한 신호들을 모두 CPU가 GPU에 맞게 변형하여 보내게된다. 이런 과정들은 곧 CPU바운더리의 오버헤드이며, 텍스쳐의 크기를 줄이거나, 폴리곤 수를 줄인다고 해서 드로우콜의 성능이 좋아지는 것은 아니다. (횟수를 줄여야함) 

5. 배치와 SetPass Call

드로우콜의 발생조건을 이해하기 전에 먼저 배치(Batch)와 SetPass를 알아야한다. 배치는 각각의 드로우콜 사이에서 그래픽스 드라이버 유효성 체크를 진행할때, GPU에 의해 접그되어지는  리소스들을 변경하는 일련의 작업들을 총칭한다. (드로우콜과 혼용하여 사용하지만 드로우콜을 포함하는 상위 개념인것이다.) SetPass는 쉐이더로 인한 렌더링 패스 횟수를 의미하고 쉐이더의 변경 시 SetPass카운트는 증가하게된다. 드로우콜이 일어날때 상태 변경의 발생 여부로 이해하면 된다. (메시 변경은 포함하지 않음) 

유니티 Stats를 보면 간단하게 몇번의 배치가 됐는지 볼수 있다.

6. 드로우콜의 발생 조건

오브젝트 하나를 그릴때 메시1개, 머테리얼도 1개로 이루어져있다면 배치는 1이다.(드로우콜 한번), 하지만 하나의 오브젝트가 여러 파츠로 나눠져있고, 메시가 여러개이면 하나의 머테리얼을 공유했다고 해도 파츠 수 만큼 드로우콜이 발생한다.  이러한 오브젝트가 여러개있다면 파츠수 x 오브젝트수  = 드로우콜 수 가되는것이다. 때로는 당연히 성능에 많은 영향을 미칠수 있다. 메시가 여러개인 경우말고도 머테리얼이 여러개 인 경우, 툰쉐이딩 처럼 외각선을 그리기위해 멀티패스로 이루어져있는 쉐이더를 사용하면 두번의 드로우콜이 발생하게 된다.  

 

2019/01/09 - [유니티/최적화] - 유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

 

 

유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

스프라이트 아틀라스 유니티에서 아틀라스는 텍스처를 한곳에 모은 한장의 큰 텍스처라고 할수있다. 텍스처 하나씩 따로따로 사용하고 관리하는것은 효율적이지 못하다. 아틀라스를 사용하면 드로우콜을 줄일 수..

funfunhanblog.tistory.com

 

 

 

 

반응형

sharedMaterial 

머테리얼의 속성을 변경할때 SharedMaterial으로 색상을 바꾼다던지 쉐이더코드의 프로퍼티값들을 변경한다. 그런데 이 런타임중에 변경된 머테리얼의 값들은 종료해도 바뀐 값 그대로 유지된다. 이것을 봤을때 Material에셋과 대응한다고 볼 수 있다.

같은 머테리얼을 가지고 있는 오브젝트

sharedMaterial을 사용하면 같은 머테리얼을 사용하는 모든 오브젝트가 변경된다. 예를들어 플레이어가 공격한 몬스터에게 림라이트효과를 주고 싶어 피격받은 몬스터의 Rim의 굵기를 변경했는데, 주위 모든 몬스터들의 Rim의 굵기가 변경되는것이다. => sharedMaterial을 참조하여 렌더링하기 때문 (배치 렌더링)

하지만 피격한 몬스터의 Rim만 변경을 원하기에 Renderer.material로 특정 오브젝만 변경할 수 있다. 

복사본을 생성(Instance)하는 Renderer.material

값을 변경을 하지 않아도 Renderer.material을 참조하는 순간!, 사본이 생성된다. 당연히 사본을 생성했기에 배치 랜더링이 되지않는다. 

해결법은 Material Property Block

 

 

학습참고

http://thomasmountainborn.com/2016/05/25/materialpropertyblocks/

https://docs.unity3d.com/kr/530/ScriptReference/MaterialPropertyBlock.html

https://cacodemon.tistory.com/entry/material-%EA%B3%BC-sharedMaterial-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Material-Property-Block

반응형

1. namespace 추가

 

2. EditorWindow상속

monobehavior는 없애주고, start(),update()도 같이 지워준다

 

3. 에디터 이름및 경로 설정

[MenuItem("TestEditor/EditorWindow")]

유니티 에디터의 등록될 이름과 경로를 적어준 뒤 static함수를 하나 만들어주면 아래와 같이 실제 유니티에서 생성된 것을 볼 수 있다.

유니티 에디터

 

4. 에디터 창 띄우기

현재는 클릭해도 아무것도 변화가 없다. 당연!

EditorWindow클래스에있는 GetWindow라는 함수를 사용해 창을 띄울 수 있다.

에디터 창 이름 변경 

아래 My Editor로 변경된 걸 볼 수 있다.

 

5. 글자 띄우기

OnGUI()함수안에 GUILayout.Label("안녕하세요 반갑습니다."); 넣어준다.

 

문자 입력받기

6. 버튼 만들기

GUILayout.Button("버튼")

 

7. 적용

프로젝트 원소 대전에서 플레이 중의 에디터로 속성을 바꿀 수 있게 했다. 속성마다 테스트할 경우가 많기 때문에 만들어봤다.

물일 때는 불속 성이 도망가지만 땅 속성이면 다시 쫒아온다.

물>불>땅>물

반응형

1. 트레이닝 설정값 세팅

config폴더

trainer_config.yaml파일을 복제하고 지금 작업하고 있는 유니티 프로젝트에 넣는다.(#1에서 복사 떴었음)

trainer_config에는 트레이닝에 사용할 여러 가지 설정값들이 존재한다.

2. 트레이닝 설정값 세팅

터미널 창을 열 고을 유니티폴더로 이동 후

mlagents-learn trainer_config.yaml --run-id=firstRun --train --slow 입력 명령어를 입력한다.

유니티 에디터를 실행하라는 명령이 나온다.

 

slow로 명령해 느리지만 자동으로 뭔가 하고 있다. (프로그램이 아무거나 누르는 중이다)

터미널에서 훈련상황 로그를 보여준다.

 

3. 훈련내용 확인

프로젝트 폴더에 가보면 model폴더가 생성된 것을 볼 수 있다. 

 

4. 훈련상황 세팅

파일을 열어주고

 

default선언 부분은 새로 선언한 부분에 없는 속성들은 default에서 속성 값대로 진행된다.

유니티에서 생성한 이름과 같게 Learing_Brain으로 선언한다.

gamma : 낮을수록 먼 미래의 보상을 낮게 평가하기 때문에 즉각적인 보상이 높게 평가됨 / 높으면 미래의 보상을 좀 더 신경 씀

lamda : 낮으면 지금까지의 보상을 기준으로 미래의 보상을 평가함(지금까지의 방향을 유지함) / 높으면 다양성이 커짐 대신 안정성은 떨어짐

buffer_size : 모델이 갱신될 스탭 사이즈

max_steps : 트레이닝을 완전히 종료함

 

훈련을 동시에 실행하기 위해 복사해준다.

Academy TimeScale 변경

TimeScale : 100 -> 15 작을수록 오차가 줄어듦

 

step 5000이 되면 훈련을 종료한다.

 

 

반응형

'유니티 > 머신러닝' 카테고리의 다른 글

ML-Agents) 브레인 생성  (0) 2019.09.17
ML-Agents) 유니티 프로젝트 생성  (0) 2019.09.17
ML-Agents)개발환경 셋팅 (Window)  (0) 2019.09.17

1. Player 브레인 생성

- Player Brain 파라미터 살펴보기

BrainParameters 

Space Size :  관측한 횟수 (Agent 스크립트에서 6번 저장을 하도록 했으니 6으로 설정)

Stacked Vectors : 다양한 관측이 가능 ( 성능이 떨어짐)

Vector Action

Space Type : Continuous로 변경

Space Size : 입력 통로는 2개로 설정했음 (가로, 세로)

- Axis Continuous Player Action설정

사용자가 입력한 방향을 입력받기 위해

- Brain 등록

만든 Brain등록

아카데미가 브레인을 통제하게 됨

 

Player에게도 브레인 등록

테스트 사용자가 키보드로 움직임

 

2 Learning브레인 생성

ML-Agents -> LearningBrain생성

LearningBrain 파라미터 설정

SpaceSize : 6 설정

SpaceType : Continuous

SpaceType : 2 설정

 

Player는 브레인을 playerBrain에서 LearingBrain으로 변경해준다.

이제는 사용자가 아닌 인고 지능 학습활동으로 움직일 것이기 때문에 

 

Academy설정

생성한 Brains 두 개를 등록해준다.

Control : 외부 파이썬 프로그램(텐서 플로우)이 통제할 수 있도록 하는 것 체크해준다.

 

 

반응형

'유니티 > 머신러닝' 카테고리의 다른 글

ML-Agents) 강화학습 #4  (0) 2019.09.17
ML-Agents) 유니티 프로젝트 생성  (0) 2019.09.17
ML-Agents)개발환경 셋팅 (Window)  (0) 2019.09.17

1. Unity프로젝트 생성

 

Unity SDK는 템플릿으로 사용할 수 있는 유니티 프로젝트 폴더이다.

여기에는 유니티 ML-Agent에서 쓰이는 유니티 프로젝트 구성이 모두 들어가 있다. 

템플릿 프로젝트를 훼손하지 않도록 복사해서 사용하는 것이 좋다.

 

그런 다음 유니티로 이 프로젝트를 열어주도록 한다. (2017.04버전 이상이어야 사용 가능)

그러면 ML-Agents라는 폴더가 보인다.

여기 안에는 구동할 수 있는 소스와 사용 예제들이 있다.

2. 아카데미 생성

아카데미는 유니티 외부에 있는 파이썬 프로그램과 유니티 내부에 있는 브레인들을 이어주는 역할을 한다.

 

- 스크립트 생성 / 오브젝트 생성

Player_Academy라는 이름의 스크립트와 Academy라는 오브젝트를 생성해준다.

- 네임스페이스 선언

- Academy 상속

이 Academy는 아카데미에서 동작하기 위한 코드들이 작성돼있다. (Academy는 추상 클래스)

- Academy기능 확인

Max Step : 현재 에피소드를 종료하고 다음 에피소드로 강제로 리셋하는 간격

(0으로 되어있으면 아카데미가 강제로 넘기지 않고 파이썬 프로그램에서 설정한 간격을 따른다.)

Training Configuration : 강화 학습을 시작할 때 사용하는 설정(해상도, 프레임 제한)

Inference Configuration : 이미 강화 학습된 결과를 실시간으로 실행할 때 사용하는 옵션

 

3. 트레이닝 환경 구성

아래 그림과 같이 간단한 훈련을 시키기 위해 세팅을 해준다.

Plane : 보여주기 위한 초록색 땅

Player : 훈련을 시킬 에이전트

Target : Player가 먹을 아이템 (Tag를 goal로 바꿔준다)

DeadZone : 플레이어가 닿으면 죽는 곳

(DeadZone의 Tag는 콜라이더 충돌 시 체크하기 위해 변경해준다)

 

3. Agent스크립트 작성

새로운 스크립트를 만든 후 아카데미와 마찬가지로 네임스페이스 추가와 Agent를 상속받도록 한다.

에이전트 기본 세팅

작성한 Agent스크립트를 Player에 붙이고 Pivot과 Target을 넣어준다.

 

 

학습참고

https://www.youtube.com/watch?v=o8cEK6X8pkM

반응형

'유니티 > 머신러닝' 카테고리의 다른 글

ML-Agents) 강화학습 #4  (0) 2019.09.17
ML-Agents) 브레인 생성  (0) 2019.09.17
ML-Agents)개발환경 셋팅 (Window)  (0) 2019.09.17

 머신러닝

유니티 머신러닝

https://unity3d.com/kr/machine-learning

 

머신러닝은 자율 에이전트에게서 지능형 행동을 끌어낼 수 있는 방식이다.  이 기술로 이용하여 기존의 반복된 작업을 사람이 아닌 컴퓨터가 가능하도록 할 수 있다.

 

유니티 머신러닝의 대한 자세한 설명은 

https://blogs.unity3d.com/kr/2017/09/19/introducing-unity-machine-learning-agents/

 

유니티 머신러닝 에이전트 소개 – Unity Blog

기존에 작성한 두 개의 블로그 게시물에서 게임이 강화 학습 알고리즘 개발을 진전시키는데 수행할 수 있는 역할이 있다고 언급했었습니다. 유니티는 세계에서 가장 널리 사용되는 3D 엔진 개발업체로 머신러닝 및 게임 분야 사이에서 미래를 그려나가고 있습니다. 머신러닝 연구자가 가장 ...

blogs.unity3d.com

 

개발 환경 세팅

 

1. 깃 설치

 

2.  ML-Agents 다운로드 

 

깃에서 주소복사를 해준다

 

3. PowerShell을 이용해서 패키지 Clone

 

복사한 주소를 아래 명령어처럼 입력한다.

설치 중

 

완료되면 설치된 장소로 ml-agents라는 폴더가 생성된다.

4. Ml-agent동작 환경 설정 (Anaconda & Python 설치)

 

- Anaconda 설치 

 

Ml-agent는 Pyhton3.6 버전대에서만 사용이 가능하기에 아나콘다 최신 버전이 아닌 이전 버전을 이용한다.

https://repo.continuum.io/archive/index.html

 

Skip 해준다.

 

- Python개발 환경 설정

 

Anaconda Prompt실행

 

아래 그림처럼 명령어 입력  => 파이썬 3.6 버전을 사용하는 새로운 MI-Agent이름으로 새로운 개발환경을 생성함

 

추가 다운로드할 패키지 명단을 보여줌 (y) 눌러줌

 

ml-agent 개발환경 로드해준다.

 

- ML-Agent 유니티 패키지 개발환경 구성

pip라는 파이썬에 내장되어있는 패키지 매니저를 통해서 유니티 ml-agent가 필요로 하는 외부 의존 라이브러리들을 설치

밑에 그림 명령어를 통해 설치

설치 과정에서 버전에 대한 오류가 났었는데 아래 사이트를 참고하여 해결하였다.

https://github.com/Unity-Technologies/ml-agents/issues/1939

설치확인

mlagents-learn --help  명령어를 통해 설치가 잘되었는지 확인한다.

 

5. 유니티는 Linux Target Support  설치

유니티 허브나 유니티 홈페이지에서 다운로드해준다.

 

 

 

여기까지 개발환경설정이 완료다.

 

 

 

 

 

 

학습 참고 사이트

https://www.youtube.com/watch?v=mJh31T3aGkI

반응형

'유니티 > 머신러닝' 카테고리의 다른 글

ML-Agents) 강화학습 #4  (0) 2019.09.17
ML-Agents) 브레인 생성  (0) 2019.09.17
ML-Agents) 유니티 프로젝트 생성  (0) 2019.09.17

Game logic


Update

호출되는 시점 :  스크립트가 활성화 된 경우 매프레임마다 호출

 

Fire Animation Eevents

호출되는 시점 :  마지막 Update시간과 최신 Update업데이트 시간사이에 모든 클립에서 애니메이션이벤트를 호출

 

OnAnimatorMove 

호출되는 시점 :  Update 프레임마다 Animator컴포넌트에 대해 한 번 호출

 

OnAnimatorIK

호출되는 시점 :  Update 프레임마다 Animator컴포넌트에 대해 한 번 호출

 

LateUpdate

호출되는 시점 : Update가 끝난 후 프레임당 한 번 호출

특징

1. Update에서 수행된 모든 계산은 LateUpdate가 시작 할 때 완료 됨

2. 일반적으로 3인칭 카메라에 사용된다.

(캐릭터를 움직이고 Update방향을 카메라 움직임과 로테이션 계산을 수행하는 경우 LateUpdate에서 카메라로 그 위치와 방향에 맞게 움직이도록)

 


 

Scene rendering


OnWillRenderObject

호출되는 시점 : 카메라에 오브젝트가 표시되면 각 카메라에 한 번 호출

특징

1. 스크립트가 활성화 되어있어야 호출됨

2. 모든 컬링된 객체들을 렌더링하기 바로 직전의 과정인 컬링 처리중에 호출

 

OnPreCull

호출되는 시점 : 카메라에서 장면을 컬링하기 전에 호출(프로스터 컬링)

특징

1. 카메라에 붙어있는 스크립트에만 호출

2. 카메라의 속성을 바꾸고싶을때 사용하면 좋음

 

OnBecameVisible / OnBecameInvisible

호출되는 시점 : 카메라에 표시되거나/표시되지 않을 때 호출

 

OnPreRender

호출되는 시점 : 카메라가 씬 렌더링을 렌딩하기 전에 호출

특징

1. yield 사용 가능

2. 스크립트는 카메라에 붙어있어야함 

 

OnRenderObject(queueIndex:int)

호출되는 시점 : 오브젝트가 렌더링처리 된 후 호출

특징

1. queueIndex은 해당 오브젝트를 렌더링할 때 사용하는 렌더 큐의 값

 

OnPostRender

호출되는 시점 : 카메라가 씬 렌더링을 마친 후 호출

특징

1. 카메라에 붙어있는 스크립트에만 호출

2. 해당 카메라가 모든 오브젝트를 렌더링한 후 호출

 

OnRenderImage

호출되는 시점 : 씬 렌더링이 완료된 후 호출

 


 

Gizmo rendering


OnDrawGizmos

특징

1. 시각화 목적으로 씬 뷰에 기즈모를 그릴 때 사용


 

End of frame


yield WaitForEndOfFrame

호출되는 시점 : 하나의 프레임이 완전히 종료될 때 호출 

특징

1.  Update,LateUpdate이벤트가 모두 실행되고 화면에 렌더링이 끝난 이후 호출


 

Decommissioning


OnDisable

호출되는 시점 : 스크립트가 비 활성화 되거나 오브젝트가 비 활성 상태가 될 때 호출

특징

1. 오브젝트가 제거되는 경우에도 호출

2. 삭제를 위한 작업에 사용

3. 에디터모드일때 스크립트가 다시 로드되는 경우에도 호출

4. Start와 달리 코루틴 사용할 수 없음

OnDestroy

호출되는 시점 : 오브젝트 존재의 마지막 프레임 업데이트를 마친 후 에 호출

특징

1. 씬 게임이 종료될 때 도 발생

2. 오브젝트가 상태가 활성화 상태였던 경우에만 호출

 

반응형

프로젝트를 하면서  유니티 어떤 함수가 먼저 호출 될까? 라는 생각을 했지만  순서의 영향이 주는 프로젝트가 아니어서 넘어갔었는데 이참에 정리해보았다.

 

함수호출 순서로(위에서 부터 먼저 호출)

 

Initializion (초기화부분)

 


Awake

호출되는 시점 :  스크립트 객체가 로딩될 때, 단 한번 호출됨

특징

1.  모든 오브젝트가 초기화 된 후 호출된다.

2. Start함수 전에 호출된다.

3. coroutine동작 불가능

 

OnEnable

호출되는 시점 :  스크립트가 켜질 때

특징

1.  플레이모드 중에 스크립트를 편집할 경우, 편집이 끝난 후 스크립트가 다시 로딩되면 호출됨(Awake와 Start는 호출되지 않음)

2.   Awake함수 호출 후  호출됨 

 

Start

호출되는 시점 :  Update함수가 처음 호출 될 때 Update함수 직전에, 단 한번 호출됨

특징

1.  Awake와 다르게  start함수는 스크립트가 켜져있을 때 호출된다. (Awake는 스크립트가 꺼진 상태에도 호출됨)

2. Awake함수는 항상 Start함수 호출되기 전에 호출 된다.


 

 

Physics (물리)


FixedUpdate

호출되는 시점 :  스크립트가 켜져 있을 때 매프레임마다 호출

특징

1.  설정한 값에  따라 일정한 간격으로 호출된다.

2. 모든 물리계산 업데이트는 FixedUpdate후 즉시 발생된다.

3. 일정한 시간으로 호출되기에 Time.deltaTime을 곱할 필요 없다.

4.  Rigidbdoy를 다룰 때  일정한 힘을 가할 때, Update함수 대신에 사용 

(불규칙적인 Update에서는 물리엔진충돌 검사 등이 잘 안될 수 있다.)  

 

yield WaitForFixedUpdate (코루틴)

호출되는 시점 :  모든 FixedUpdate가 모든 스크립트에 호출된 후 

특징

1.  당연히 yield코루틴 명령문에서만 사용 가능

 

OnTriggerXXX, OnCollisionXXX

특징은 여기서 정리했다.

https://funfunhanblog.tistory.com/13

 

유니티) 충돌체크 OnTriggerEnter , OnCollisionEnter (Collider, Collision) #2Roll a Ball 로 연습하기

OnTrigger , OnCollision 비교하기 Collider 유니티에서 충돌체크를 하기 위한 필요한 컴포넌트이다. 3D오브젝트를 생성하면 Collider가 자동적으로 추가 되어있다. 먼저 Trigger에 대해 살펴보자 1) void OnTrigg..

funfunhanblog.tistory.com

 


 

 

Input events


OnMouseXXX

특징 : 마우스입력으로 호출되는 이벤트함수

 


Unity) 유니티 이벤트실행순서 / 플로우차트(Script Lifecycle Flowchart) #2

https://funfunhanblog.tistory.com/253

 

 

 

 

 

유니티 - 매뉴얼: 이벤트 함수의 실행 순서

자동 메모리 관리를 이해하기 이벤트 함수의 실행 순서 Unity 스크립팅시, 미리 정의된 순서대로 실행되는 많은 이벤트 함수가 있습니다. 아래에서는, 실행 순서를 설명합니다. 에디터 __ Reset : __ Reset은 개체가 처음 연결되었을 때 스크립트의 프로퍼티로 초기화하는 데 호출됩니다. Reset 명령을 실행했을 때도 마찬가지입니다. 첫 번째 씬 로드 이 함수는 씬이 시작되면 호출됩니다(씬의 각 오브젝트에 대해 한 번). __ Awake : __이

docs.unity3d.com

반응형

UI오브젝트의 위치를 지정하는 것은 월드맵의 존재하는 오브젝트들의 위치를 변경하는 것과 다르다.

 

월드맵의 존재하는 오브젝트 Transform의 Position의 값으로 위치를  지정해준다.

 

 

하지만 UI는 아래와 같이 Rect Transform으로 위치를 지정해준다  

 

 

UI는 단말기의 보여지는 사각형의 스크린 뷰로 Canvas밑에서 관리된다. 그렇기에 다른 방법으로 위치를 지정해줘야 한다.

 

RectTransform으로 접근한 뒤 anchorePosition으로 접근해서 값을 지정해줄 수 있다.

 

 

 

 

추가적으로 ) RectTransform

Anchors사각형 왼쪽 하단 모서리와 오른쪽 상단 모서리의 앵커

Min :  사각형 크기의 일부로 정의되는 사각형 왼쪽 하단 모서리의 앵커 포인트. 0,0은 상위 사각형 왼쪽 하단 모서리의 앵커에 해당하고 1,1은 상위 사각형 오른쪽 상단 모서리의 앵커에 해당

 

Max :  사각형 크기의 일부로 정의되는 사각형 오른쪽 상단 모서리의 앵커 포인트. 0,0은 상위 사각형 왼쪽 하단 모서리의 앵커에 해당하고 1,1은 상위 사각형 오른쪽 상단 모서리의 앵커에 해당

 

Left, Top, Right, Bottom : 앵커를 기준으로 한 사각형 에지의 상대적인 포지션으로, 앵커에 의해 정의되는 사각형 안의 패딩

https://docs.unity3d.com/kr/current/Manual/class-RectTransform.html

반응형

인벤토리 아이템 개수가 보이는 슬롯의 개수를 초과했을 때 스크롤 뷰로 처리하면 된다.

적용방법

생성 or 기존 이미지의 컴포넌트 추가

추가할 때는 UI -> Scroll View을 만들어준다.

나는 기존의 만든 이미지의 컴포넌트를 추가했다.

 

컴포넌트 살펴보기

Movement Type : 밑에 설명

Viewport : 표시하고자 하는 콘텐츠가 보일 배경? 공간

Content : 표시하고자 하는 슬롯

Scrollbar Horizontal/Vertical :  수평/수직 스크롤바

 

나 같은 경우는 수평 스크롤바는 필요 없으니 뺐다.

쉽게 슬롯들은 Content자식으로 넣어두면된다.

(Grid LayOut을 이용해 슬롯을 정렬)  

 

Movement Type

Unrestricted : 콘텐츠들이 드래그한곳으로 움직이며, 되돌아오지 않음

Elastic : 스크롤 영역 밖으로 나가도  다시 돌아옴

Clamped : 아예 못 나감

 

스크롤바와 Grid LayOut은 다른글에 포스팅하도록 해야겠다.

반응형

 

 

쉐이더코드

 

C# 유니티 스크립트 


2D이미지의 붙어있는 머테리얼

(obj는 Image)

쉐이더코드 변수의 값 받아오기

GetFloat("변수이름");

 

 

쉐이더코드 변수의 값 변경하기 

SetFloat("변수 이름", 변경할 값);


 

3D모델

1. material

2. sharedMaterial

두가지 접근방법이 있다.

 

material은 해당 오브젝트의 머테리얼만

sharedMaterial은 해당 머테리얼을 포함한 모든 오브젝트의 값을 변경하게된다.

material은 사용할때 각각으로 인스턴싱하기 때문에 배칭이 안된다. 

 

반응형

셰이더코드없이 노드구조로 셰이더효과를 낼 수 있는 어셋

 

어셋을 활용하기 위해서는 쉐이더의 기본용어는 알아두는게 좋다

UV좌표 : 텍스쳐 이미지를 3차원 공간의 폴리곤에 입히기위해 변환 기준이 되는 2차원 좌표계

UV 매핑 : D:2차원 그림을 3차원 모델로 만드는 3차원 모델링 프로세스

(자세한설명  https://kblog.popekim.com/2011/12/03-part-1.html)

 

그래픽스 파이프라인

3차원 컴퓨터 그래픽스에서 그래픽스 파이프라인(graphics pipeline) 또는 렌더링 파이프라인(rendering pipeline)은 3차원 이미지를 2차원 래스터 이미지로 표현을 하기위한 단계적인 방법

 

Shadero Sprite - 2D Shader Editor 사용방법

어셋다운로드

에셋을 다운로드하고 열면 이런 에디터화면이 나온다. 여기서 작업을하게된다.

Build Shader 생성

마우스 오른쪽 클릭 -> Build Shader 선택

 

Shadero Sprite Shader 스크립트 생성

유니티 프로젝트창에서 마우스 오른쪽 클릭

쉐이더 코드를 적용할 머테리얼도 같이 생성

 

쉐이더 코드 넣기

아까 생성한 쉐이더코드를 드래그앤드롭으로 놓는다.

머테리얼도 넣어둔다

 

적용할 이미지(텍스처) 추가

New Texture를 누르고 Select를 통해 원하는(쉐이더를 적용할) Sprite이미지를 선택한다.

 

Build Shader로 선을 이어준다

그러면 우측상단에 결과적용 이미지가 출력되는것을 볼 수 있다.

 

 

기본적인 효과설명

4Gradients

위치마다 다른 그라데이션효과

Twist UV 효과 UV->FX(UV)

Burn 효과  RGBA -> FX -> Burn FX

PlasmaFX

Gold효과

적용방법

쉐이더코드를 머테리얼에 적용후 그 머터리얼을 sprite에 적용

 

번외

쉐이더 그래프

유니티자체에도 쉐이더를 노드구조로 그리는 기능이 있음

프로젝트 생성할때 설정해주고 만들어야 쉐이더 그래프를 사용이 가능하다.

2018버전 Preview
2019버전 이상

튜토리얼 https://www.youtube.com/watch?v=VvK7sLbbLYE

 

반응형

랜더텍스처

 

Render Texture는 런타임에서 생성 및 갱신되는 특수한 Texture입니다. 사용하려면 먼저 새 렌더 텍스처를 만들고 Cameras 중 하나를 지정하여 거기에 렌더링합니다. 이제 정상적인 텍스처처럼 Material의 렌더 텍스처를 사용할 수 있습니다. Unity 표준 에셋의 Water 프리팹은 실시간 반사와 굴절을 생성하기 위해 렌더 텍스처를 실제 월드상에 사용하는 예입니다.

<https://docs.unity3d.com/kr/530/Manual/class-RenderTexture.html>

 

 

카메라가 비추는 화면을 다른 특정 이미지에서 보여줄 때 사용했다. 

 

1. 랜더텍스처 생성

랜더 텍스처를 생성한다.

2. 카메라 추가

일단 특정 오브젝트를 비추는 카메라가 있어야 한다. 그렇기에 카메라를 추가하고 

이곳에 아까 생성한 랜더 텍스처를 추가한다.

3. Raw 이미지 추가

Raw 이미지를 추가한다. 이 이미지 크기로 카메가 비추는 화면이 보이게 된다. 적절히 크기를 설정해주고

이쪽에도 랜더 텍스처를 넣어준다.

4. 추가 작업

 

보이긴 하는데 뒷배경도 나와 보기가 별로다. CullingMask를 통해 보일 오브젝트만 레이어로 분류하면 된다.

나 같은 경우는 ClearFlag를 Solid로 바꿔서 캐릭터만 보이도록 했다.

 

 

 

 

 

반응형

코드 분석 중에 모르는 것이 나와서 정리해두기 위해 포스팅했다.

Destroy

오브젝트(Component,Asset)를 삭제시켜주는 함수다. 가비지가 많이 생성시켜서 반복적으로 불리는 경우(몬스터 등)에서는 사용하지 않고

특정 시간을 주어서 삭제 시간을 정할 수도 있다. Destroy (obj : Object, t : float = 0.0F) 실제 객체 파괴는 항상 현재 Update 루프가 끝나기 전까지 지연되며 렌더링이 되기 전에 파괴된다고 한다.

DestroyImmediate

Destroy 이와 같은데 지연시간을 줄 수가 없다. 큰 차이점은 Update루프가 끝난 후가 아닌 바로 삭제(동일한 프레)된다는 점과 편집기 코드를 작성할 때 사용하고 게임 코드에서는 Destroy를 이용하라고 한다.  주의점으로는 영구적으로 파괴할 수 있다는데..) 

 

언제 DestroyImmediate를 사용해야 하는지는 조금 더 알아봐야겠다.

 

 

https://docs.unity3d.com/ScriptReference/Object.DestroyImmediate.html

반응형

+ Recent posts