1) Addressables.CheckForCatalogUpdates()를 통해 업데이트 할께 있다. 없다를 판단 하는거 같습니다. 체크를 통해 Count가 0 이상이면 업데이트 할께 있으므로 다운로드 진행으로 알고 있습니다. 그런데 의문점 드는 부분의 코드가 CheckForCatalogUpdates() 체크 후 Count 0 이상 > 카탈로그 업데이트 > 다운로드 사이즈 체크 Count 0 이하 > 다운로드 사이즈 체크 2번 같은 경우 카탈로그 체크로 인해 0 이므로 다운로드를 받을 필요가 없는데 굳이 다운로드 사이즈 체크해서 다운로드를 하는 이유가 무엇인가요? 2) 제공 해주신 프로젝트로 카탈로그 테스트 중 이상 현상이 나와 질문 드립니다. 번들 빌드를 한후 서버에 올리고 다운을 받으면, AppData\LocalLow\Unity/프로젝트이름/ 위치로 다운 받아 지는 거 같습니다. 다시 테스트를 위해 위 위치의 캐시를 지운 후 테스트를 하면 카탈로그체크카운트가 0입니다. 내 캐쉬에 없는데도 카탈로그체크카운트가 0이상이지 않습니다. 혹시나 몰라 addressables_content_state.bin도 삭제 Group > Clean Build 를 해보아도 카탈로그체크카운트가 카운트 되지 않습니다. 최초 실행은 카탈로그체크카운트가 카운트 되지 않나요? 아님 리셋 시키는 방법이 있을까요?
pool manager #3강을 듣고 있습니다. 유니티짱 프리팹에 Poolable을 삽입하고 실행하면 NullReferenceException 에러가 발생합니다. 코드는 아래와 같습니다. 답변해주시면 감사하겠습니다. ㅜㅜ //Poolable.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class Poolable : MonoBehaviour { //오브젝트가 이 컴포넌트를 들고 있으면 메모리 풀링 적용 public bool isUsing; //풀링이 됐는지 확인 } //PoolManager.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class PoolManager { #region Pool class Pool { public GameObject Original { get; private set; } #region 설명 /*이 속성은 get 접근자를 가지고 있어 Original GameObject을 가져올 수 있습니다. 또한 private set 접근자를 가지고 있어 Original GameObject을 Pool 클래스 내부에서만 설정할 수 있습니다. private set 접근자가 있는 속성을 사용하면 값을 읽을 수는 있지만 클래스 외부에서 그 값을 변경할 수 없습니다. 이렇게 하면 Original GameObject은 Pool 클래스의 lnit() 메서드로만 설정할 수 있으므로 풀을 특정 GameObject로 초기화할 수 있습니다.*/ #endregion public Transform Root { get; set; } #region 설명 /* //이 코드는 Pool 클래스의 Root 속성을 나타냅니다. 이 속성은 해당 풀에 속한 오브젝트들을 담을 게임 오브젝트의 Transform 컴포넌트입니다. get 접근자와 set 접근자를 모두 가지고 있으므로, 해당 속성은 읽기 및 쓰기가 가능합니다. Root 속성이 null인 경우, 새로운 게임 오브젝트의 Transform 컴포넌트를 만들어 Root 속성으로 할당합니다. */ #endregion Stack<Poolable> _poolStack = new Stack<Poolable>(); #region 설명 /* Pool 클래스 내부에 Stack<Poolable> 타입의 _poolStack 멤버 변수를 선언합니다. 이 스택은 풀링을 위해 사용되며, Poolable 객체의 인스턴스들을 담아 놓습니다*/ #endregion public void lnit(GameObject original, int count = 5) #region 설명 /*인자로 original, count를 받아서 Original, Root를 초기화합니다. Original은 Pool 클래스 내에서 사용되는 GameObject 변수이며, Root는 Pool 내에서 생성된 객체들의 부모 객체로 사용됩니다. 그리고 for문을 사용하여 count만큼 Create 함수를 호출하여 Poolable 객체들을 생성하고 Push 메서드를 호출하여 Stack에 추가합니다. Push 메서드는 Pool 클래스 내부에 구현되어 있으며, Poolable 객체를 Stack에 추가하고 비활성화합니다. */ #endregion { Original = original; Root = new GameObject().transform; Root.name = $"{original.name}_Root"; for (int i = 0; i < count; i++) { Push(Create()); } } Poolable Create() #region 설명 /*Create() 메소드는 새로운 객체를 생성하고, 이를 Poolable 컴포넌트를 추가한 뒤 반환합니다. 첫 번째로 Instantiate 메소드를 사용하여 Original 게임 오브젝트의 복사본을 만들어 go 변수에 할당합니다. 이후 go 변수의 이름을 Original 게임 오브젝트의 이름으로 변경합니다. 마지막으로 go.GetOrAddComponent<Poolable>() 메소드를 호출하여 Poolable 컴포넌트가 이미 추가되어 있으면 해당 컴포넌트를 반환하고, 추가되어 있지 않으면 새로운 Poolable 컴포넌트를 추가한 뒤 반환합니다. 이렇게 반환된 Poolable 컴포넌트가 Push 메소드에서 사용됩니다. */ #endregion { GameObject go = Object.Instantiate<GameObject>(Original); // 복사본은 go에 저장 go.name = Original.name; return go.GetOrAddComponent<Poolable>(); } public void Push(Poolable poolable) #region 설명 /*Push 함수는 Poolable 객체를 받아서 _poolStack 스택에 저장합니다. 이 함수는 객체 풀링의 핵심입니다. 먼저 poolable이 null인 경우, 즉 재활용할 객체가 없는 경우 함수를 바로 종료합니다. 그렇지 않으면, poolable의 transform의 parent를 Root로 설정하여 원본 객체의 하위 개체로 추가합니다. 그리고 gameObject를 비활성화하고, Poolable.isUsing 변수를 false로 설정하여 해당 객체가 사용 중이 아님을 나타냅니다. 마지막으로, poolable을 _poolStack 스택에 푸시합니다. 이렇게 되면 풀에 객체 가 추가되고, 다음에 필요할 때 재활용할 수 있습니다.*/ #endregion { if (poolable == null) return; poolable.transform.parent = Root; #region 설명 /* transform은 Unity 게임 오브젝트의 구성 요소 중 하나로, 게임 오브젝트의 위치, 회전 및 크기를 관리합니다. transform.parent 속성은 해당 게임 오브젝트의 부모 객체의 transform을 나타냅니다. 따라서 poolable.transform.parent = Root; 코드는 poolable 게임 오브젝트의 transform의 부모를 Root로 설정하여, Root 아래에 생성된 모든 게임 오브젝트가 Root의 자식으로서 계층 구조적으로 관리되도록 합니다.*/ #endregion poolable.gameObject.SetActive(false); #region 설명 /* 인자로 전달된 poolable 객체의 게임 오브젝트를 비활성화합니다. 이것은 해당 객체가 풀에 반환되어 사용 가능한 상태가 되었음을 의미합니다.*/ #endregion //업데이트문을 받지않고 수면상태 poolable.isUsing = false; _poolStack.Push(poolable); } public Poolable Pop(Transform parent) #region 설명 /*Pop 함수는 오브젝트 풀에서 사용 가능한 Poolable 객체를 꺼내서 반환하는 함수입니다. parent 매개변수는 반환된 Poolable 객체를 부모로 하는 Transform을 설정할 때 사용됩니다. 함수는 다음과 같은 동작을 수행합니다: _poolStack 스택에서 사용 가능한 Poolable 객체를 가져옵니다. _poolStack 스택에 사용 가능한 Poolable 객체가 없으면 Create() 함수를 호출하여 새로운 Poolable 객체를 만듭니다. 가져온 혹은 새로 만든 Poolable 객체를 활성화합니다. parent 매개변수로 받은 Transform을 부모로 하는 Poolable 객체의 Transform을 설정합니다. isUsing 변수를 true로 설정하여 해당 객체가 사용 중임을 나타냅니다. Poolable 객체를 반환합니다.*/ #endregion { Poolable poolable; if (_poolStack.Count > 0) poolable = _poolStack.Pop(); else poolable = Create(); poolable.gameObject.SetActive(true); poolable.transform.parent = parent; poolable.isUsing = true; return poolable; } } #endregion Dictionary<string, Pool> _pool = new Dictionary<string, Pool>(); #region 설명 /*Dictionary는 key-value pair를 저장할 수 있는 C#의 데이터 구조 중 하나입니다 이 코드는 Dictionary<string, Pool> 타입의 _pool 변수를 선언합니다. Dictionary는 key-value 쌍의 컬렉션으로, 각각의 key에 대응하는 value를 저장합니다. 여기서 key는 string 타입이고 value는 Pool 타입입니다. 따라서 _pool 변수는 string 타입의 key와 Pool 타입의 value를 가지는 Dictionary입니다. 이 변수는 나중에 게임 오브젝트를 풀링할 때 사용됩니다.*/ #endregion //다른곳에 기생 리소스 매니저 보조 Transform _root; #region 설명 /*_root 라는 이름의 Transform 타입 변수를 선언하고 있습니다. Transform은 Unity Engine에서 게임 오브젝트의 위치, 회전, 크기 등의 정보를 담고 있는 컴포넌트입니다. _root 변수는 풀링된 객체들이 모여있는 루트 오브젝트를 가리키는 역할을 합니다. 이 변수는 lnit() 함수에서 생성됩니다.*/ #endregion //트렌스폼은 게임 오브젝트와 연결 public void lnit() #region 설명 /*lnit 함수는 Pool 클래스가 생성될 때 호출되며, Pool 클래스 내부에서 사용되는 변수인 _root를 초기화합니다. _root는 Pool 클래스에서 생성된 모든 객체들이 생성될 때 사용되는 부모 객체입니다. 먼저 if문을 사용하여 _root가 null인지 확인합니다. null이라면, _root가 존재하지 않으므로 새로운 GameObject를 생성하고 _root 변수에 할당합니다. GameObject가 생성되면 해당 GameObject의 이름을 "@Pool_Root"으로 설정하고, Object.DontDestroyOnLoad 함수를 사용하여 게임 오브젝트를 씬 전환시 파괴되지 않도록 합니다. 이렇게 함으로써, Pool 클래스는 게임이 실행되는 동안 계속해서 _root 객체를 사용할 수 있으며, 필요한 경우 Poolable 객체들을 _root 객체의 하위에 위치시켜 관리할 수 있습니다.*/ #endregion { if (_root == null) { _root = new GameObject { name = "@Pool_Root" }.transform; Object.DontDestroyOnLoad(_root); } //게임이 실행된이후로 영구적으로 존재 //풀링이 필요하면 pool_root 산하로 이동해서 보관 } public void CreatePool(GameObject original, int count = 5) #region 설명 /*이 함수는 GameObject을 이용해서 새로운 객체 풀을 생성하는 역할을 합니다. 함수 인자로 original과 count를 받습니다. original은 객체 풀링을 위해 복제할 원본 게임 오브젝트를 의미합니다. count는 생성할 초기 오브젝트 개수를 나타냅니다. 함수 내부에서는 Pool 객체를 생성하고 lnit() 메소드를 이용해 생성한 original과 count를 초기화합니다. 그리고 이 Pool 객체의 Root 프로퍼티를 _root의 자식으로 설정합니다. 마지막으로, _pool 딕셔너리에 original의 이름과 pool을 추가합니다. 따라서, CreatePool() 메소드를 호출하여 객체 풀을 생성하면 해당 원본 게임 오브젝트를 original로 하는 Pool 객체가 생성되고, 이 Pool 객체는 _root 아래에 생성된 후, _pool 딕셔너리에 추가됩니다. 이렇게 생성된 객체 풀은 나중에 필요할 때 GetPool() 메소드를 이용하여 언제든지 접근할 수 있습니다.*/ #endregion { Pool pool = new Pool(); pool.lnit(original, count); pool.Root.parent = _root; //루트를 pool_root에 연결 _pool.Add(original.name, pool); } public void Push(Poolable poolable) #region 설명 /*위 코드는 입력받은 Poolable 객체를 다시 풀에 반환하는 메서드입니다. 입력받은 Poolable 객체의 GameObject 이름을 키로 사용하여, 해당 GameObject의 프리팹이 풀에 존재하는지 검사합니다. ContainsKey 메서드는 Dictionary 클래스에 있는 메서드로, 지정된 키가 Dictionary 컬렉션에 포함되어 있는지 여부를 확인합니다. 여기서는 _pool 딕셔너리에 입력받은 Poolable 객체의 GameObject 이름이 존재하지 않으면, 해당 객체의 게임 오브젝트를 파괴하고 메서드를 종료합니다. 만약 프리팹이 존재하면 해당 객체를 다시 Push 메서드를 이용하여 해당 프리팹의 Pool 객체에 반환합니다.*/ #endregion //풀에다가 푸시하는 함수 //다 사용한다음에 반환하는 작업 { string name = poolable.gameObject.name; if (_pool.ContainsKey(name) == false) #region 설명 /*ContainsKey는 Dictionary<TKey, TValue>에 키가 포함되어 있는지 여부를 반환하는 메서드입니다. 키가 포함되어 있으면 true를, 그렇지 않으면 false를 반환합니다. 예를 들어, Dictionary<string, int> 인스턴스 dict가 있다고 가정해보겠습니다. 이 경우 dict.ContainsKey("apple")은 dict에 "apple"이라는 키가 포함되어 있는지 확인하고, 있으면 true를, 없으면 false를 반환합니다.*/ #endregion { GameObject.Destroy(poolable.gameObject); return; } _pool[name].Push(poolable); } public Poolable Pop(GameObject original, Transform parent = null) #region 설명 /*해당 함수는 풀에서 객체를 꺼내는 역할을 수행하는 함수입니다. 먼저, _pool이 해당 original 객체의 키를 가지고 있는지 확인합니다. 만약 해당 original 객체의 키가 없으면, CreatePool 함수를 통해 새로운 풀을 생성합니다. 그러나 해당 함수에서는 객체를 반환하지 않고, null을 반환하고 있습니다. 객체를 반환하기 위해서는 Pool 클래스의 Pop 함수를 호출해야 합니다. 또한, Transform parent 인자를 활용하여, 해당 객체를 생성한 후, 지정된 부모 객체의 자식으로 추가할 수 있습니다.*/ #endregion // 꺼내는 함수 // parent는 없을 수 도 있다고 체크함. { if (_pool.ContainsKey(original.name) == false) //풀에 오리지날 네임이 있는지 확인 CreatePool(original); return null; } public GameObject GetOriginal(string name) #region 설명 /*이 코드는 이름이 name인 GameObject가 포함된 풀의 Original GameObject를 가져오는 함수입니다. name: 가져오려는 GameObject의 이름 _pool Dictionary에 name을 key로 가지는 Pool이 있는지 검사합니다. 없다면 null을 반환합니다. 있다면 해당 Pool의 Original GameObject를 반환합니다. 이를 통해 필요한 GameObject를 재사용할 수 있습니다.*/ #endregion { if (_pool.ContainsKey(name) == false) return null; return _pool[name].Original; } //@@신이동할때 캐시를 날린다. public void Clear() { foreach (Transform child in _root) #region 설명 /* _root의 자식들에 대해 반복문을 실행하는 코드입니다. _root는 Transform 타입의 변수이며, 생성한 Pool 객체들의 Root Transform을 담고 있는 부모 객체입니다. 즉, _root의 자식들은 Pool 객체의 Root Transform입니다. 따라서 이 코드는 생성한 모든 Pool 객체들의 Root Transform에 대해 반복문을 실행합니다.*/ #endregion //루트 산하에 접근 GameObject.Destroy(child.gameObject); _pool.Clear(); //풀 초기화 } }
[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part7: MMO 컨텐츠 구현 (Unity + C# 서버 연동 기초)
실무에 가깝게 적용할만한 캐릭터간 이동 동기화 중에서 딜레이후 동기화 처리에 대한 참고할만한 이론이나 문서 같은것이 있을까요? 게임 장르는 존 방식이고 한 방에 최대 100명 이하? 정도 될것 같은데요 환경은 모바일이고요 약.. 서버에서 1초에 4~8번 정도(정확히 아직 1초에 몇번 보내는지는 모릅니다)의 이동관련 패킷을 TCP나 UDP로 보낸다고 가정할 경우 일정 시간 이동 관련 위치 값을 수집해서 보간 처리 해볼까 하는데 언리얼 데디케이트 서버에서 어떻게 해야 할지 좀 막막한것 같은데 관련된 문서나 방식등이 있을까요? 루키스님 강의 중 C++ 쪽 서버강의에서 이미 강의는 어느정도 듣긴했지만 이동 관련된것이 없는것 같아서 질문 남깁니다 답변좀 부탁드리겠습니다!
StageController 스크립트 작성할 때, IEnumerator Start() 함수를 작성하였는데 별도의 StartCoroutine("Start"); 이 없는데 어떻게 실행이 되는건가요?? 마우스 왼쪽 버튼을 눌렀을때 text가 없어지고 게임이 시작되는 부분입니다.
강의 중에 기존에 DataManager.cs 에서 작성한 Stat class와 DataStat class를 Data.Contents 파일로 이동 후에 다른 추가 작업 없이 DataManager 스크립트에서 자연스럽게 Stat 클래스와 DataStat 클래스를 활용할 수 있는 부분이 의문스럽습니다 강의처럼 사용할 수 있는 이유를 예상해보았는데 "Asset 폴더 내에 있는 script간에는 접근제한자로 제한하지 않는 경우에는 특별한 지시문 없이도 접근이 가능하다" 가 맞는걸까요? 아니면 다른 이유가 있는건지 궁금합니다.
안녕하세요 선생님! 강의 잘 보고있습니다. 현재 제공해주신 샘플 코드가 한개의 라벨에서 어드레서블을 다운로드하는 방식으로 구현되어있는데요. 여러 라벨을 한번에 다운로드하고 관리하는 샘플도 추가해주실수 없을까요? 그게 조금 어려우시면 좀 번거로우실듯 하지만 ..ㅜㅜ 답변으로 심플 하게라도 현재 제공해주신 코드에서 어떤 코드 (몇번째 라인)에 라벨 스트링 배열 값(라벨 목록) 을 삽입하여 처리하면 되는지 권장하시는 방식을 간단 하게라도 알려주시면 감사하겠습니다! ^^
상속 강의에서 Animal 스크립트와 Test 스크립트를 작성하셨는데, Animal 스크립트에서 Animal 부모 클래스와 캣 독의 자식 상속까지는 이해가 되었습니다. 하지만 해당 내용을 별개의 스크립트인 Test에서 불러와 Animal 클래스의 속성을 사용하는건 어떤 연결고리가 있나요? MonoBehaviour 때문에 연결이 되는건가요? 아니면 Animal 스크립트의 클래스들이 Public 인 상태로 클래스변수를 만들었기 때문인가요? 만약 그렇다면 스크립트들의 구조를 부모클래스만 모아져있는 스크립트를 별개로 두고 구성하게되는 경우도 생기나요?
GetComponentsInChildren 함수가 자식 오브젝트의 특정 컴포넌트를 모두 반환해준다면 recursive가 true이거나 false이거나 모두 이 함수를 사용하면 되는 것이 아닌가 해서 질문드립니다. recursive 매개변수를 사용하여 조건을 분리한 이유가 무엇인가요?
[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part7: MMO 컨텐츠 구현 (Unity + C# 서버 연동 기초)
구글 프로토 버퍼 연동을 하던중 서버에서는 패킷을 보내도 클라쪽에서 recvArgs 는 받은 바이트배열이 하나도 없는데 구글 프로토버프 패킷 형식이나 그냥 바이트 배열을 보내봐도 똑같습니다. 분명히 socketError == SocketError.Success인데 받은 바이트가 없다는게 이해가 안됩니다ㅠ 혹시 왜 그런지 알 수 있을까요?
[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part1: C# 기초 프로그래밍 입문
// 값 순서대로 정렬하기 static void Sort(int[] scores) { for (int i =0; i < scores.Length; i++) { //제일 작은 수 찾기 -> 제일 작은 숫자가 있는 index 찾기 int minIndex = i; for (int j= i; j <scores.Length; j++) { if (scores[j] < scores[minIndex]) minIndex = j; } // 자리바꾸기 (swap) int temp = scores[i]; // 값을 덮어씌우면 날라가기 떄문에 임시로 저장 해둔다 scores[i] = scores[minIndex]; scores[minIndex] = temp; // 임시로 저장해둔 값을 다시 minIndex에 옮긴다 } } 여기서 특히 이부분이 이해가 안되고 있어요 for (int j= i; j <scores.Length; j++) { if (scores[j] < scores[minIndex]) minIndex = j; } 첫번째 루프때 int i = 0이 되고 for 문에서 j=i라고 해서 j도 0이 됩니다 그런다음에 if (scores[j] < scores[minIndex]) 여기에 따라서 scores[0] < scores[0] 이렇게 되는거 아닌가요?? 둘다 첫번째 루프고 minIndex도 i의 값을 따르니깐요... minIndex = j 로 넘어 갈수 없다고 생각하고 있습니다..
void Start() { Bind<Button>(typeof(Buttons)); Bind<TextMeshProUGUI>(typeof(Texts)); Get<TextMeshProUGUI>((int)Texts.ScoreText).text = "Bind Test"; } void Bind<T>(Type type) where T : UnityEngine.Object //reflection 사용 { string[]names = Enum.GetNames(type); UnityEngine.Object[] objects = new UnityEngine.Object[names.Length]; _objects.Add(typeof(T), objects); for (int i = 0; i<names.Length;i++) { objects[i] = Util.FindChild<T>(gameObject, names[i], true); } } T Get<T>(int idx) where T : UnityEngine.Object { UnityEngine.Object[] objects = null; if (_objects.TryGetValue(typeof(T), out objects)==false) return null; return objects[idx] as T; } 기존에 Text를 TextMeshProUGUI로 바꾸고 using TMPro;도 추가했는데도 계속해서 해당 오류가 발생하네요...도저히 원인을 못찾겠습니다 ㅠㅠ
안녕하세요. 강의 열심히 보고 있습니다. ^^ JobQueue #1 강의중 19분51초에서.. 멀티스레드 환경에서 14줄에서 null 체크를 하고 접속이 해제된다면 17줄에 room을 넣을 때 null이 반환되지 않을까요?? 검색해봐도 해당 내용은 없는거 같아서 질문 드립니다.
장애물도 navigation static이고 not walkable인데도 저 안쪽이 자꾸 걸을 수 있는 곳으로 표시가 되어서 몬스터들이 랜덤으로 walkable좌표를 찍는데 저기로 잡아서 자꾸 벽안으로 기어들어갈려고합니다. 제기억에 장애물을 설치해 놓으면 그 안쪽도 당연히 장애물로 막혀서 걸을 수 없는 구역으로 나왔던거 같은데... 왜이러는 걸까요?