Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
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
Archives
Today
Total
관리 메뉴

개발 달리기

[내일배움캠프 사전캠프] + 복습 본문

Unity

[내일배움캠프 사전캠프] + 복습

옹즤 2025. 3. 31. 17:49

1. 오늘 학습 한 내용
- 오목 게임 간단 기획

- 사전 강의 1 ~ 3강 내용을 응용한 '칼 피하는 르탄이' 제작하며 복습


2. 학습하며 겪었던 문제점 & 에러

- 처음 아무것도 모르는 상태에서 강의를 봤을때와 어느정도 개념이 살짝 잡힌 후 봤을때 의문점 생기는 부분들이 달라서 그 부분에 대한 궁금증을 학습 질문방과 챗 GPT를 이용하여 해소하였음


3. 내일 학습 할 것은 무엇인지
- '칼 피하는 르탄이' 더 진행
- C# 문법 걷기반 개념 완료

4. 오늘 학습 후기

주말 포함해서 학습하며 생겼던 궁금증은 다음과 같다.

 

1) foreach 함수와 for문 + Length 기능을 사용하는 것은 거의 비슷한 기능을 하는데 어떤 방식이 맞는지?

-> 이는 챗 GPT가 이해가 잘 되는 예제와 함께 대답을 제시했다.

결과적으로 for문을 사용하며 Length를 사용하는 것이 좋은 방법이었다.

더보기

foreach의 기능과 for + Length의 공통점과 차이점 (Unity 기준)

C#에서 foreach와 for + Length는 모두 반복문이지만, 사용 방식과 성능, 동작 방식에서 차이점이 있습니다.
특히 Unity에서는 최적화가 중요하므로, 어떤 경우에 어떤 반복문을 사용하는 것이 좋은지 이해하는 것이 중요합니다.


1. foreach의 기능

foreach (var item in collection)
{
    // item 사용
}

foreach는 배열, 리스트, 컬렉션을 자동으로 순회하면서 각 요소에 접근할 수 있는 반복문입니다.
반복할 개수를 직접 지정하지 않아도 됨 (배열 또는 리스트 크기에 맞게 자동으로 반복)
✅ 내부적으로 IEnumerator를 사용하여 각 요소를 순차적으로 가져옴

예제 (foreach)

string[] names = { "Alice", "Bob", "Charlie" };
foreach (string name in names)
{
    Debug.Log(name); // Alice, Bob, Charlie 출력
}

2. for + Length의 기능

for (int i = 0; i < collection.Length; i++)
{
    // collection[i] 사용
}

배열 또는 리스트의 인덱스를 직접 조작하면서 반복 가능
반복 횟수를 원하는 만큼 조정 가능 (i+=2 같은 방식도 가능)
성능을 고려하여 최적화 가능 (예: Length를 미리 변수에 저장)

예제 (for + Length)

string[] names = { "Alice", "Bob", "Charlie" };
for (int i = 0; i < names.Length; i++)
{
    Debug.Log(names[i]); // Alice, Bob, Charlie 출력
}

3. foreach vs for + Length 공통점

✅ 둘 다 배열(List, Array) 등의 요소를 순회하는 데 사용
✅ 동일한 개수만큼 반복하지만, 접근 방식이 다름
✅ Unity에서 모두 GameObject 배열, List<> 등의 반복 작업에 사용 가능

예제 (GameObject 리스트 순회)

GameObject[] objects = GameObject.FindGameObjectsWithTag("Enemy");

// foreach 사용
foreach (GameObject obj in objects)
{
    obj.SetActive(false);
}

// for 사용
for (int i = 0; i < objects.Length; i++)
{
    objects[i].SetActive(false);
}

4. 차이점 비교

구분 foreach for + Length
반복 방식 IEnumerator를 사용하여 자동 순회 인덱스를 사용하여 직접 접근
가변 길이 컬렉션 List<>, Dictionary<> 같은 컬렉션에 적합 List<>, Array 모두 사용 가능
성능 약간 느릴 수 있음 (C# 내부적으로 IEnumerator 사용) 빠름 (Length 값을 미리 저장하면 더 최적화 가능)
수정 가능 여부 읽기 전용 (Read-only) (요소를 직접 삭제하거나 변경 불가) 수정 가능 (리스트 요소 변경, 삭제 가능)
인덱스 접근 인덱스 접근 불가능 (collection[i] 사용 불가) 인덱스로 직접 접근 가능 (collection[i])
예제 foreach (var item in list) { ... } for (int i = 0; i < list.Length; i++) { ... }

5. Unity에서 어떤 경우에 사용하면 좋을까?

상황 foreach 추천 for 추천
리스트 전체를 순회하며 값 변경 없이 읽기만 할 때
배열 또는 리스트 내부 요소를 수정할 때 ✅ (가능)
배열을 조작해야 할 때 (삭제, 추가 등)
성능 최적화가 중요한 경우 (Frame Drop 방지) ✅ (더 빠름)
코드를 간결하게 작성하고 싶을 때

Unity에서 foreach 사용 시 주의할 점

  • foreach는 리스트 요소를 변경할 수 없음
  • Unity의 List<T>.Remove()를 사용하면 foreach 내부에서 오류 발생
  • List<GameObject> objects = new List<GameObject>(GameObject.FindGameObjectsWithTag("Enemy"));
    foreach (GameObject obj in objects)
    {
        objects.Remove(obj); // 오류 발생!
    }
  •  for 루프를 사용하여 뒤에서부터 제거해야 함
  • for (int i = objects.Count - 1; i >= 0; i--)
    {
        objects.RemoveAt(i); // 올바른 방식
    }

6. 결론

foreach는 간결한 코드 작성에 유리하지만, 성능이 중요한 경우 for이 더 적합
배열을 수정해야 한다면 for을 사용 (특히 Remove() 또는 Add()가 필요한 경우)
✅ Unity에서는 실시간 성능 최적화가 중요하므로 for 사용을 권장 (특히 Update() 내 반복문)

최적화된 for 사용 예제 (Unity에서 더 빠르게 실행)

GameObject[] objects = GameObject.FindGameObjectsWithTag("Enemy");
int length = objects.Length; // Length 값을 미리 저장하여 반복문 내 연산 최소화

for (int i = 0; i < length; i++)
{
    objects[i].SetActive(false);
}

➡️ 결론적으로, foreach는 직관적인 코드 작성에 좋고, for은 성능 최적화 및 요소 수정이 필요할 때 더 적합합니다! 🚀

2) Vector 2, Vector 3 이런 것을 사용하는 이유

-> 2는 2차원, 3은 3차원을 구성하는 벡터 함수

사실 벡터야 고등학교 필수 과정으로 배우는 것이기에 벡터가 무엇인지 자체는 알고 있었으나, 프로그램 언어로 받아들이니 뭔가 쉽게 이해가 되지 않는 느낌이었다.

아직도 100% 이해가 된 것은 아니어서 Vector를 여러 코드에서 어떻게 적용하는지 많은 케이스들을 보며 적응해나가야 될 듯 싶다.

 

3) Unity에서 스크립트를 작성할 때, 컴포넌트 값을 기준으로 transform은 바로 사용 가능한데 다른 값들은 선언해서 써야하는 이유는?

-> 이 부분은 너무 기초적인 부분인지 강의 내에서도 설명이 없어서 챗 GPT에세 물어보았다.

결론은 Unity에서 활용 가능한 모든 Object들에는 transform이 들어있는데 다른 컴포넌트는 달라서 때에 따라 선언해서 써야 한다는 것이다.

더보기

Unity에서 transform은 바로 사용할 수 있는데, SpriteRenderer는 따로 선언해야 하는 이유

Unity에서는 컴포넌트(Component) 시스템을 기반으로 객체(Object)에 다양한 기능을 추가합니다. transform과 SpriteRenderer의 차이를 이해하려면 Unity의 컴포넌트 구조를 알아야 합니다.


1. transform은 모든 GameObject에 기본적으로 포함됨

transform.position = new Vector3(1, 1, 0);
  • transform은 모든 GameObject에 기본적으로 포함된 컴포넌트입니다.
  • Unity의 GameObject는 위치, 회전, 크기 정보를 가지는 Transform을 기본적으로 내장하고 있습니다.
  • 즉, transform은 모든 GameObject에서 바로 사용할 수 있는 내장 필드입니다.

Unity 엔진 내부에서의 GameObject 구조

GameObject
 ├── Transform (모든 GameObject에 기본 포함)
 ├── SpriteRenderer (필요하면 추가)
 ├── Rigidbody2D (필요하면 추가)
 ├── Collider2D (필요하면 추가)
  • 모든 GameObject는 반드시 하나의 Transform을 포함합니다.
  • Transform은 GameObject의 위치, 회전, 크기(scale) 를 관리하는 핵심 컴포넌트이기 때문에 transform 필드가 기본 제공됩니다.

2. SpriteRenderer는 선택적 컴포넌트이므로 GetComponent<>로 가져와야 함

SpriteRenderer sr = GetComponent<SpriteRenderer>();
sr.flipX = true;
  • SpriteRenderer는 모든 GameObject가 기본적으로 포함하는 컴포넌트가 아닙니다.
  • 따라서, GameObject에 SpriteRenderer가 추가되지 않았다면 GetComponent<SpriteRenderer>()를 호출해도 null이 반환됩니다.
  • 이를 방지하기 위해, SpriteRenderer를 사용하려면 직접 GetComponent<>를 호출하여 해당 컴포넌트를 가져와야 합니다.

3. Unity 내부에서 transform과 GetComponent<>가 동작하는 방식

  • transform은 Unity 내부에서 GameObject의 필수 기본 필드로 선언되어 있습니다.
  • public Transform transform { get; } 
    • 그래서 GetComponent<Transform>()을 호출하지 않아도 transform을 바로 사용할 수 있습니다.
  • 반면, SpriteRenderer는 선택적 컴포넌트이므로 GetComponent<>를 사용해야 합니다.
  • SpriteRenderer sr = GetComponent<SpriteRenderer>();

4. SpriteRenderer 없이 flipX을 사용하면 오류가 발생하는 이유

void Start() {
    transform.flipX = true; // 오류 발생!
}
  • flipX는 Transform이 아닌 SpriteRenderer의 속성이므로, transform에서 직접 사용할 수 없습니다.
  • flipX을 사용하려면 SpriteRenderer를 가져와야 합니다.

올바른 코드

void Start() {
    SpriteRenderer sr = GetComponent<SpriteRenderer>(); 
    sr.flipX = true;
}

결론

transform은 GameObject에 항상 포함되어 있어서 바로 사용 가능
SpriteRenderer는 선택적 컴포넌트이므로 GetComponent<SpriteRenderer>()로 가져와야 사용 가능

즉, transform은 모든 객체가 기본적으로 가지는 핵심 컴포넌트이기 때문에 직접 접근 가능하지만, SpriteRenderer는 필요한 경우 추가하는 선택적 컴포넌트이므로 GetComponent<>를 사용해야 한다는 차이가 있습니다! 😊

4) Public으로 모두 열어두면 언제든지 사용 가능성이 생기는데 굳이 Private를 사용하는 이유는?

-> 이 부분은 너무 완벽한 챗 GPT의 명쾌한 대답으로 의문이 해결되었다.

더보기

C#에서 private 접근 제한자를 사용하는 이유는 캡슐화(Encapsulation) 원칙을 지키고, 코드의 안정성과 유지보수성을 높이기 위해서입니다. public으로 모든 함수를 열어두면 특정 상황에서 의도치 않게 사용될 가능성이 생기므로, private을 사용하여 이를 방지합니다.

private를 사용하는 주요 이유

  1. 내부 동작 숨기기 (Encapsulation)
    • 특정 메서드는 클래스 내부에서만 사용되도록 제한하여, 불필요한 외부 접근을 막고 코드의 일관성을 유지할 수 있습니다.
    • 예를 들어, 데이터 검증용 메서드는 외부에서 호출될 필요 없이 내부에서만 실행되도록 해야 합니다.
  2. 잘못된 사용 방지
    • 모든 메서드를 public으로 공개하면, 개발자가 예상하지 못한 방식으로 호출될 수 있습니다.
    • 예를 들어, 객체가 완전히 초기화되지 않은 상태에서 public 메서드가 호출되면 예기치 않은 오류가 발생할 수 있습니다.
  3. 코드 유지보수 용이
    • private 메서드는 클래스 내부에서만 수정되므로, 코드 변경 시 외부 코드에 미치는 영향을 최소화할 수 있습니다.
    • 반면, public 메서드를 수정하면 이를 사용하는 다른 클래스에도 영향을 줄 수 있어 유지보수가 어려워질 수 있습니다.
  4. 의도적인 접근 제한
    • 특정 기능이 오직 해당 클래스에서만 사용되어야 하는 경우, 외부에서 호출할 필요가 없으므로 private을 사용합니다.
    • 예를 들어, 데이터베이스 연결을 관리하는 클래스에서 연결을 닫는 메서드를 public으로 제공하면, 개발자가 실수로 필요하지 않은 시점에 호출할 수 있습니다.

예제 코드

class User
{
    private string password; // 외부에서 접근 불가

    public void SetPassword(string newPassword)
    {
        if (IsValidPassword(newPassword))
        {
            password = newPassword;
        }
    }

    private bool IsValidPassword(string pwd)
    {
        return pwd.Length >= 8; // 최소 8자 이상이어야 함
    }
}
  • IsValidPassword 메서드는 private으로 설정되어 외부에서는 호출할 수 없고 SetPassword 내부에서만 사용됩니다.
  • 만약 IsValidPassword를 public으로 열어두면, 의도하지 않은 곳에서 호출될 위험이 있습니다.

언제 private을 사용해야 할까?

  • 메서드나 변수(필드)가 외부에서 직접 접근할 필요가 없을 때
  • 특정 기능이 클래스 내부에서만 동작해야 할 때
  • 데이터 무결성(Integrity)을 유지하고, 잘못된 사용을 방지해야 할 때

➡️ 결론적으로, private을 사용하면 코드의 안전성과 유지보수성을 높이고, 예기치 않은 오류를 방지할 수 있습니다. 🚀

 

5. 칼 피하는 르탄이 제작 이슈

시작부터 어느 강의의 도움도 받지 않고 실제로 만들어본다는 생각으로 제작을 시작했다.

일단 오브젝트를 배치하여 어떤 게임의 모습을 만들지까진 너무 기본적인 배치라 크게 막히는 부분이 없었으나, 강의와 다른 새로운 움직임의 방식으로 코드로 짜려고 하니 굉장히 어려웠다.

일단 개념이 제대로 안잡힌 상태에서 뭔가 코드를 짜보려고 하니 생각이 나질 않았다.

그래서 기존에 '빗물받는 르탄이' 를 만들며 작성했던 코드를 다시 참고해보기로 했다.

화면은 이렇다.

여기서 빗물 -> 검으로 대체되었고 검을 받는게 아니라 검을 피하는 방식으로 만들 것이다.

피하는 방식이기 때문에 2강의 '풍선을 지켜라' 처럼 시간 자체가 점수가 되는 방식으로 바꿔야한다.

그리고 마우스 클릭으로 방향을 바꾸는 방식 -> 키보드의 A, D를 이용하여 조종하는 방식으로 바꾸었다.

 

플레이어 캐릭터가 움직이는 방식을 이렇게 구현하였으나 문제가 발생했다.

방향키를 같은 방향으로 누르고 있거나 여러번 누를 시 속도가 중첩되는 문제이다.

그리고 X축의 제한 벽에 부딪혔을 때 아예 멈추게 하고 싶었는데 그 방법도 어려웠다.

결국 전반적인 코드 리뷰를 챗 GPT를 통해 받았다.

 

이후 제안에 의해 다음과 같이 코드를 변경했다.

아예 일정한 스피드로 움직이게 하기 위해 speed 변수를 선언하였고

챗 GPT가 Mathf.Clamp 기능을 추천해서 이 방식으로 X축 제한 세팅을 하였더니 코드가 깔끔해졌다.

잘 적용되는지도 확인했는데

 

의도대로 아주 잘 작동했다.

여기까지 제작하는데 도움 없이 해보려니 결국 결론이 나지 않아 시간적인 소비가 꽤 있었다.

르탄 캐릭터를 의도대로 움직이게 만들었으니 내일은 시간이 지나면서 점수가 되는 방식과

검이 비처럼 떨어지는 것을 구현하면 마무리가 가능해보인다.

이후 3강의 '고양이 밥주기 게임' 을 변형한 적이 플레이어를 공격하는 게임을 만들어 볼 것이다.