2021년 4월 30일 금요일

유니티에서 쓰레드 사용 방법

 유니티로 개발하다보면 어쩌다 Thread로 처리를 하면 좋을 때가 있습니다.

그런데 유니티는 기본으로 Thread를 지원하지 않아서 Thread에서 처리할 부분은 최소화 하는게 좋긴하죠.

어쨋든 Thread로 처리하는 방법을 설명 해드리겠습니다.

개발자들은 소스로 보는게 편하죠..


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System.Threading;

class ThreadManager : MonoBehaviour
{
    private Thread thread = null;
    private object lockObject = new object();       // 임계영역 처리를 위한 오브젝트 생성
    private static Queue<Task> TaskQueue = new Queue<Task>();
    
    private class Task
    {
        public int type;    // 작업 방식
        public string data;
    }
    
    void Start()
    {
        if (thread == null)
        {
            thread = new Thread(RunThread);
            thread.Start();
        }
    }
    
    void Update()
    {
        while (TaskQueue.Count > 0)
        {
            Task task;
            
            lock (lockObject)
            {
                task = TaskQueue.Dequeue();
            }
        }
    }
    
    // 쓰레드 함수 구현
    void RunThread()
    {
        while (true)
        {
            lock (lockObject)
            {
                // 처리
                TaskQueue.Enqueue(new Task());
            }
        }
    }
}
cs


첨언을 드리자면 실제로 멀티스레드로 동작하는 RunThread 함수 내에서 유니티에서 제공하는 기능을 실행하면 에러가 발생합니다. 유니티는 단일 스레드만 지원합니다.

서브 스레드에서 처리할 항목을 TaskQueue 객체에 넣습니다.

그리고나서 매 프레임당 호출되는 Update 함수에서 그 객체를 꺼냅니다.

Update 함수에서 바로 lock (lockObject) 로 동기화 처리를 하게 되면 굉장히 느려집니다.

lock 자체가 빨리 처리가 되는 기능이 아니기 때문에 작성하신 프레임이 굉장히 떨어지는걸 보실 수 있습니다.

그래서 queue 의 내용이 있을 경우에만 안의 내용을 꺼내와서 처리를 하면 됩니다.

게임 오브젝트가 죽지 않게 할려면???

 게임을 개발하다보면 특정 게임 오브젝트를 전역적으로 사용해야할 경우가 생깁니다.

여러 씬에서 한가지의 오브젝트로 다 사용하는거죠.

방법은 여러가지가 있습니다. 대표적으로 싱글톤 객체를 만들어서 사용하면 됩니다.

그런데 간단히 객체 자체를 다른 씬으로 전환해도 안 죽게 만들면 어떨까요???

싱글톤도 어렵지는 않지만 개인적으로 이게 더 쉽다고 생각되네요.


1
2
3
4
void Start()
{
    DontDestroyOnLoad(gameObject);
}
cs



그럴 경우 위와 같이 하면 됩니다. 고작 Start에서 함수 하나를 호출하면 되는거죠.

인자로는 죽지 않게 할 객체의 인스턴스를 적어주면 됩니다.

유니티 동적 Sprite 로딩하기

 개발하다보면 Spirte 를 SpriteRenderer 한테 동적으로 넘겨줘야할 때가 있습니다.

그럴때는 가볍게 해결할 수 있는데요.


1
2
3
4
SpriteRenderer renderer;
int playerNo = 1;

renderer.sprite = Resources.Load<Sprite>("Sprites/Common/Player/player " + playerNo);
cs


위와 같이 하면 됩니다.

그런데 as 로 캐스팅하게 되면 안되더군요.


밑의 코드와 같이 하면 이상하게 안됩니다.


1
2
3
4
SpriteRenderer renderer;
int playerNo = 1;

renderer.sprite = Resources.Load("Sprites/Common/Player/player " + playerNo) as Sprite;
cs


저는 이렇게 해서 한참을 헤맸는데요. 저처럼 헤매지 마세요.


※ 주의 사항 - 동적으로 연결이 안될 경우

1. Resources.Load 를 사용할 경우 'Resources' 폴더 밑에 해당 파일이 있어야 합니다.


 이와 같이 'Resources' 폴더명이 정확해야합니다.


2. Resource.Load(경로) => 경로가 'Resources' 폴더부터 상대 경로입니다. 해당 경로도 정확해야합니다. 단, 확장자는 필요 없습니다.


3. Sprite 의 경우 이미지 타입을 inspector 에서 Sprite 로 변경을 안 하면 null 을 리턴합니다.


유니티 희한한 에러 - SUCCEDED(hr) 과 rc.right == m_GfxWindow->GetWidth()...

 열심히 유니티로 개발 중에 뜬금없이 console 창에 처음보는 에러가 발생했다.



구글링해도 딱히 해결방법을 못 찾겠고.. 에라이 모르겠다 하고 유니티 에디터를 껐다가 다시 실행하니

정상 작동..


이것도 유니티 버그인가???

해당 에러 아시는 분 있으면 좀 알려줘용~~~

유니티 게임 개발 OnLevelWasLoaded deprecated 경고 해결 방법

 OnLevelWasLoaded 를 가지고 사용하는 코드가 있는데요.

어느날 갑자기 deprecated 경고가 뜨는군요..


deprecated 경고는 해당 함수등이 사라지기 전에 코드 사용자에게 경고를 주기 위해서 미리 알려주는 경고입니다. 언제가 될지는 모르겠지만 사라질 운명이 된거죠..



위 캡쳐 파일이 console에서 친절하게 없어질 기능이니 지워버려~ 라고 알려주는 화면입니다.

저는 이 기능을 지울 수 없기 때문에 수정을 하기로 했습니다..


먼저 수정 방법을 설명하기 전에 OnLevelWasLoaded가 하는 역할을 알려드리죠..


OnLevelWasLoaded 이벤트 함수는 씬이 변경되면 호출되는 함수입니다. 최초에는 호출이 안되고요.. 다른 씬으로 전환 시 호출된답니다. 보통 이 함수를 사용하는 곳은 싱글턴으로 사용하는 객체를 쓸 경우죠.

GameObejct 를 static 으로 로드하고 죽지 않도록 해서 어느 씬에서나 사용할 경우 해당 기능이 필요할 때가 있을 수 있겠죠??


위와 같이 OnLevelWasLoaded 이벤트 함수를 사용하시는 분은 밑의 코드로 대체해서 사용하시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
    void OnEnable()
    {
        SceneManager.sceneLoaded += OnLevelFinishedLoading;
    }

    void OnDisable()
    {
        SceneManager.sceneLoaded -= OnLevelFinishedLoading;
    }

    void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
    {
        // 코드작성
    }
cs


sceneLoaded 이벤트 함수를 직접 정의하는거죠..


※ 새로운 방법

- 제공 : 지나가는행인(댓글)님의 도움으로 링크 첨부드립니다.


https://answers.unity.com/questions/1174255/since-onlevelwasloaded-is-deprecated-in-540b15-wha.html


참고하세요~


유니티에서 Multiple Sprite 를 동적으로 로딩하기

 예전에 Sprite 를 동적으로 로딩하는 방법을 적었었는데요. 작업하다가 Multiple 로 나눠서 사용하는 녀석은 기존 방법대로는 안되더군요. 그래서 다시 글을 작성합니다.


동적으로 Spirte 로딩방법 바로가기


사실 별거는 없습니다. 기존 Resouces.Load 함수 대신 LoadAll로 대체하는거죠.

아래와 같은 코드 형태로 하면 됩니다.


1
public Sprite[] sprites = Resources.LoadAll<Sprite>("경로/multipleSpriteFile");
cs


이런 코드로 하면 해당 스프라이트 파일로 쪼개진 모든 Sprite 를 배열 형태로 들고 오게 됩니다. 파일의 특정 스프라이트만 가져올 수는 없습니다. 불편할 수도 있겠지만 별로 시간도 안 걸리니 그냥 편히 쓰시면 됩니다. 보통 애니메이션 등을 처리 하기 위해서 스프라이트 파일 하나로 작성한걸로 작업을 하죠.

그럴 때는 빈번하게 동적으로 Sprite 를 변경해야할 수도 있으니 그냥 한번에 가져오는게 오히려 성능상으로 더 좋습니다.

Default GameObject Tag : Player already registered 에러 해결 방법

 어느날 열심히 유니티로 개발을 하던 중 Console 창에 'Default GameObject Tag : Player already registered'

이 메시지를 발견했습니다. 단순히 warning 이긴한데요. 그러나 불안한 느낌이죠.

일단 에러 메시지의 뜻을 보겠습니다.

Default 로 정의되어 있는 Tag : 'Player' => 이 태그가 이미 등록되어 있습니다. 라는 말입니다.

Player 태그는 Default 로 정해져있고요. 해당 태그는 변경이 불가능합니다.


동일한 태그가 여러군데에 등록이 되면 유니티는 해당 태그에 한해서 작동이 완벽하게 수행되지 않을 수 있습니다.


이 경고는 저에게 실제로 발생하였습니다. 제 본업이 유니티 게임 개발자가 아닌 관계로 몇일에 걸려서 간신히 해결했네요. 몇일이라고는 하지만 유니티를 할 시간이 너무 없었던게 문제였죠. 그리고 해결 방법은 의외로 간단했습니다.



제가 실수로 'Player' 태그를 등록했던걸까요?? 등록한 기억은 없는데 어느덧 등록이 되어 있더군요.

처음 유니티를 실행하였을 때에는 위와 같은 메시지에서 2줄만 나온답니다.. 왜??? 두줄이나 나오지??

Tags & Layers 창을 열어봐야겠죠??

Tags & Layers 창은 상단 메뉴에서 Edit - Project Settings - Tags & Layers 를 선택하시거나, 유니티 에디터의 화면 우측 상단 Layers 콤보 버튼을 누르신 후 'Edit Layers...' 항목을 선택 후 Inspector 창을 확인하시면 됩니다.



저의 경우는 위와 같이 나왔습니다. Tag 0 과 Tag 9 가 똑같이 Player 항목이 설정되어 있습니다.

제가 캡처해놓지는 않았지만 Sorting Layers 와 Layers 의 경우 명칭 변경이 된답니다.


1차 해결 방법


일반적으로 위의 화면에서 지우고자 하는 항목을 선택 후 빨간 박스의 '-' 버튼을 누르시면 해당 항목이 삭제가 되면서 저와 같은 문제는 간단히 해결됩니다. 그러나 빨간 박스를 눌렀음에도 불구하고 첫번째 이미지와 같이 'UnityException : Tag : Player is not defined. UnityEngine.GameObject.FindWithTag (System.String tag) (at C:/buildslave/unity/build/artifacts/generated/common/runtime/GameObjectBindings.gen.cs:273)' 이라는 허무맹랑한 에러가 발생하실 경우에는 밑의 내용을 따라와 주세요.

유니티 에디터가 뱉은 경로는 제 폴더에 존재하지도 않은 경로의 파일입니다. 아마도 저 경로는 유니티 에디터 개발팀에서 지정되어 있던 빌드 경로로 추정됩니다. 간단히 무시하세요.


먼저 유니티 에디터에서 실제 파일을 변경하는걸 실패했으니 그냥 종료시켜버리세요. 굳이 종료는 안하셔도 됩니다만, 저는 태그 관리 파일을 직접 수정하여 해당 태그를 정상적으로 다시 로딩 시키기를 원하기 때문에 종료 시켰습니다. 뭐 이것은 추정이지만요. 유니티 에디터가 실행하는 시점에 태그 정보와 다른 정보들을 읽지 않을까 싶더군요.


 탐색기에서 경로를 이동하자.


저의 유니티 프로젝트 폴더는 d:\Source-Unity\ 입니다. 여기서 제 프로젝트 명이 붙는것이죠.

해당 프로젝트 경로의 하위 폴더는 Assets, Library, ProjectSettings, Temp 폴더가 있습니다. 여기서 살펴볼 폴더는 ProjectSettings 폴더 입니다. 아까 Tags & Layers 창을 띄울 때 있었던 메뉴의 항목명과 동일하죠.. 유니티 에디터에서는 해당 경로는 직접 수정이 불가능 했습니다.



해당 경로를 가보니 친절하게 이름이 딱 눈에 들어오게 작명해주셨습니다. 유니티 팀의 작명센스 최고~!


  


TagManager.assest 파일을 에디터 프로그램으로 실행합니다. 여기서 메모장으로 열게 되면 글자가 깨져 보이게 됩니다. 인코딩 문제로 추정되는데요. 그냥 귀찮아서 안 알아봤습니다. 저의 경우 노트패드++ 프로그램으로 실행하니 간단히 열리더군요. 왼쪽과 같이 있던 항목을 지웠습니다. 저의 경우 테스트용 Tag 를 추가로 입력했습니다. 333 태그와 Player 태그 2개를 지웠죠. 지운 완성된 화면은 오른쪽 화면입니다.


감사해요 Notepad++


Notepad++ 다운받으러 가기


다시 유니티 에디터를 실행해보니 Console 창에 아무런 경고가 나오지 않습니다.



Tags & Layers 창을 열어서 등록된 태그를 확인하니 제가 의도된 대로 정상적으로 나와있습니다.


 해결 완료 - 두 줄 간단 요약 


쉽게 해결이 됐습니다.


  1. Tags & Layers 창에서 잘못된 Tag 를 지운다. - 해결이 안되면 2번
  2. ProjectSettings 창에서 TagManager.asset 파일을 직접 수정한다.

이렇게 간단히 해결이 되었습니다.

제법 글을 길지만 이미지와 잡설로 인해 실제로 한거에 비해서 많아보일뿐입니다. 저와 같은 문제가 발생하신 분들은 참고해주세요.

그나저나 유니티 일반 개발자들에게는 쉽게 접근할 수 있는 편리함은 있지만 역시 버그는 많네요.