월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
foreach문 에러에 대해 궁금합니다
foreach문은 T형식을 사용할 수 없다는데요..T에 GetEnumerator의 공개 인스턴스 또는 확장 정의가 없기 때문이라는데 어떡하면 좋죠?;;
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
++i. i++에대한 간단한 질문
안녕하세요 항상 수업 잘 듣고 있습니다.다름이아니라 어셈블리 코드로 보면 ++i가 i++보다 아주 약간의 리소스가 덜든다고 배워물론 아주 미묘한 차이겠지만 필요한 상황이 아니면 늘 ++i를 쓰려는 습관을 들려고하고있습니다.근데 선생님 코드는 for문을 돌리든 뭘할때 필요한 상황이 아니면 항상 i++로 코드를 작성하시던데 혹시 어떠한 사유가 있는건지 하여 질문드립니다...
- 해결됨[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
ResourceManager를 구현하면서 궁금한점
1. Resources.Load 함수를 사용하는 것은 성능상 좋지 않아 잘 안쓴다고 들었었는데 사용할 리소스들을 Inspector창에서 다 받아서 사용하는 방법은 너무 노과다 일거 같고 지금과 같은 구조라면 저걸 사용하는데에 있어선 무리가 있다는 생각이 들어서 궁금합니다. 2. 저런 리소스들을 관리 하는데 Dictionary 말고 List가 성능상으로 훨씬 좋다고 들었습니다. 그런데 List를 사용하면 탐색 시간에서 오래 걸리지 않을까라고 생각 하는데 이 부분은 어떤식으로 관리하나요 ?
- 해결됨[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
vs 2019에서 함수들 색상이 강의처럼 안변하는데 어느 설정을 바꾸면 될까요?
원래라면 Getkey랑 TransformDirection같은 함수가주황색으로 나와야하는데 이런식으로 하얗게 나오네요비주얼스튜디오 2019버전입니다
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
Fixed Duration 관련해서 질문드립니다.
State Machine #1 강의 말미에 Fixed Duration의 체크 여부에 따라 Exit Time과 Transition Duration 내에 기입된 수치가 비율에서 초로 변환된다고 말씀하습니다.제가 이것저것 만져본 결과 Exit Time은 Fixed Duration 체크 여부에 상관 없이 무조건 비율인 것 같습니다.반면에 Transition Duration은 Fixed Duration이 체크되었을 때는 초, 그렇지 않을 때는 비율로 적용되는 듯합니다. 확인 부탁드립니다..https://docs.unity3d.com/kr/2018.4/Manual/class-Transition.html
- 해결됨[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
텍스트 UI를 생성했는데 이게 떴는데 어떻게 할까요?
(사진)
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
널레퍼런스 오류
온드래그 핸들러 부분에서 널 레퍼런스 오류가 뜹니다 44번 줄에서 에러가 난다고 뜨는데 이 부분을 모르겠습니다.
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
뭐가 문제인지 궁금합니다!!
'static이 아닌 필드,메서드 또는 속성 'Object.name'에 개체 참조가 필요합니다'라고 뜨는데 뭘고쳐야 하나요?
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
카메라 플레이어 받는법에 대해 질문 있습니다
포톤을 이용하여 쿼터뷰 게임을 만들고 있는데 프리팹에서 생성되는 플레이어를 어떤식으로 카메라에 받아야할지 감이 잡히지 않아 질문드립니다. 런타임시 찾아와야하는지 아니면 따로 매니저를 만들어 받아야하는지 모르겠어어 질문드립니다.
- 해결됨[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
유니티짱의 물리에 관한 문제로 질문했었는데요
제가 살펴본 결과 정말 원인을 찾을수가 없었습니다. 제가 지난번에 제공해드린 스샷과 GIF를 통해서 한번 더 확인해 주실수있을까요? 참고로 제 버전은 2021.3.16버전입니다. 그리고 혹시 또 필요한 스크린샷이 있다면 제공하도록 하겠습니다. 만약 원인이 해결이 안된다면 부자연스럽더라도 y축을 고정시킨상태로 강의를 수강해도 큰 문제는 없을까요??
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
InputManager 질문
안녕하세요 Manager내의 _input 변수는 static으로 만들지 않으셨는데 Manager가 싱글톤 패턴이라 하나의 Instance만 생성되고 InputManager를 해당 Manager에서만 접근이 가능하다면 InputManager를 static으로 만들어도 무관한지 궁금하여 질문드립니다.
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
유니티짱의 물리가 이상하게 설정된것같습니다.
제 하이라키를 첨부해봤습니다. 유니티짱의 y축을 프리징하면 정상적으로 작동하는것처럼 보이지만 근본적인 해결책은 아닌것같습니다. 유니티짱의 하이라키와 플레인의 하이라키 스샷을 찍어봤습니다. ps. 혹시 몰라서 cube의 하이라키도 체크했습니다 cube에는 is trigger를 체크했는데 강의의 내용과 일치하는지 궁금합니다
- 해결됨[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
DataManager class 내의 Dictionary 에 관해 질문드립니다
c#문법에 익숙치 않아 차근차근 공부중인데이해가 잘 되지 않는 부분이 있어 질문드립니다DataManager class내의 Dictionary선언부분에 보면public Dictionary<int, Stat> StatDict { get; private set; } = new Dictionary<int, Stat>(); 라고 되어있는데 여기서 new Dictionary<int, Stat>(); 이부분이 반드시 필요한 건가요?Init() 에서 MakeDict 매소드 안에서 새로운 Dictionary를 할당받고 있는부분이 있어 제가 모르는 부분이 있는것같아 질문드립니다
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
싱글플레이 게임의 업데이트 방식
강사님 안녕하세요! 강의를 듣다가 궁금한점이 생겼는데 싱글플레이게임같은경우 업데이트를 할 때 파일을 통째로 갱신하는 방법을 사용하는지, 아니면 또다른 방법이 있는지 궁금합니다.
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
인벤토리 실습 #2 질문 AddUIEvent활용
인벤토리 실습을 보다 가 이해가 안가는 부분이 생겨서 질문을 남깁니다.보시면(안보일 수도 있겠지만), Get<GameObject>((int)GameObjects.ItemIcon).AddUIEvent((PonterEventData) => {Debug.Log)})를 했는데.. 여기서 Action 대리자로 람다식이 들어 가서 실행 이 되는건 알겠는데, ㅇ파라미터에 게임오브젝트는 안들어갔는데 이게 왜 바인드가 되는거죠?
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
unitychan의 프리팹을 만들 수가 없습니다.
"폴더 정리" 강의의 9:51초 화면에서는유니티쨩의 모델을 hierachy로 뺀 후 playercontroller 스크립트 적용 후, 프리팹 폴더로 빼면서 프리팹을 만드는 화면이 나오는데, 제쪽에서는 유니티쨩 모델을 hierachy 창으로 끌어당기면 강의 화면처럼 inspector 창에 Model 이라는 표시 대신, 이미 프리팹이 적용된 것 처럼 나와 유니티쨩을 프리팹으로 만들려고 하면 prefab variant로만 만들 수 있다고 창이 표시됩니다. 분명 말씀하신대로 tank 프리팹과 유니티쨩 모델의 앞부분 박스 상자 아이콘은 다른데, 유니티쨩에게서 모델이라는 내용을 강의와는 다르게 찾아볼 수가 없습니다. 현재 22.2.16 버전을 사용중인데 버전이 올라가면서 해당 내용이 뭔가 수정된 부분이 있는걸까요?
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
unitychan 프리팹에 Poolable 스크립크를 추가하면 nullreference 에러가 발생합니다.
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 게임 개발 시리즈] Part3: 유니티 엔진
Data Manager 와 Data.Contents 스크립트의 연결
강의 중에 기존에 DataManager.cs 에서 작성한 Stat class와 DataStat class를 Data.Contents 파일로 이동 후에다른 추가 작업 없이 DataManager 스크립트에서 자연스럽게 Stat 클래스와 DataStat 클래스를 활용할 수 있는 부분이 의문스럽습니다강의처럼 사용할 수 있는 이유를 예상해보았는데 "Asset 폴더 내에 있는 script간에는 접근제한자로 제한하지 않는 경우에는 특별한 지시문 없이도 접근이 가능하다"가 맞는걸까요? 아니면 다른 이유가 있는건지 궁금합니다.
- 미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
Samples 다운로드 주소
https://assetstore.unity.com/packages/essentials/ui-samples-25468
- 해결됨[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
recursive 조건을 만든 이유가 잘 이해가 되지 않습니다.
GetComponentsInChildren 함수가 자식 오브젝트의 특정 컴포넌트를 모두 반환해준다면 recursive가 true이거나 false이거나 모두 이 함수를 사용하면 되는 것이 아닌가 해서 질문드립니다. recursive 매개변수를 사용하여 조건을 분리한 이유가 무엇인가요?