개발/유니티

[유니티] 유니티 learn #5

kimchangmin02 2025. 8. 24. 17:43

방법 1: 화면의 픽셀(Pixel) 너비를 알고 싶을 때 (UI 작업용)

정답: Screen.width

이것이 사용자님의 질문에 가장 직접적인 답변입니다.

  • 설명: Screen은 현재 게임이 실행되고 있는 화면(모니터, 스마트폰 액정 등) 자체를 의미하는 클래스입니다. Screen.width는 그 화면의 가로 픽셀 수를 정수(int)로 알려줍니다.
  • 언제 사용하나요?:
    • UI 버튼을 화면 오른쪽 끝에 붙이고 싶을 때.
    • 화면 해상도에 따라 UI 크기를 조절하고 싶을 때.
    • 주로 UI 관련 작업에 사용됩니다.

 

방법 2: **게임 세상(World)**의 너비를 알고 싶을 때 (게임플레이용)

정답: Camera.main.ScreenToWorldPoint()를 이용한 계산

이것이 실제로 캐릭터를 움직이거나 게임 로직을 짤 때 거의 항상 사용되는 방법입니다.

  • 설명: Screen.width는 단순 픽셀 값이라서, 실제 게임 공간에서 캐릭터가 서 있는 좌표와는 단위가 다릅니다. 캐릭터가 화면 밖으로 나가지 못하게 막으려면, "카메라가 지금 보고 있는 게임 세상의 실제 너비가 얼마인가?" 를 알아야 합니다.
  • 언제 사용하나요?:
    • 캐릭터가 화면 밖으로 나가지 못하게 막을 때. (가장 흔한 경우)
    • 화면 끝에서 몬스터가 나타나게 하고 싶을 때.
    • 화면 크기에 맞춰 게임 월드의 크기를 조절하고 싶을 때.

 

 

 

 

 

 

 

 

 

Screen.width 게임플레이 로직이 아닌, 사용자 인터페이스(UI)를 다룰 때 거의 항상 사용됩니다.

"TV 화면에 스티커 붙이기" 라고 생각하시면 완벽하게 이해됩니다.

  • 게임 월드 (카메라가 비추는 세상): TV에서 방영되는 드라마입니다. 배우(캐릭터)들은 드라마 세트장 안에서 움직입니다.
  • 스크린 (Screen): 드라마를 보는 TV 화면 그 자체입니다.
  • UI 요소 (버튼, HP바 등): TV 화면에 붙이는 스티커입니다.

배우가 드라마 세트장에서 어디로 움직이든, TV 화면 구석에 붙여놓은 '방송사 로고' 스티커는 항상 그 자리에 있죠? Screen.width는 바로 이 '스티커'의 위치를 잡을 때 사용합니다.


Screen.width (방법 1)을 사용하는 구체적인 경우

1. UI 요소(스티커)를 화면 특정 위치에 고정시킬 때

  • 상황: "체력(HP) 바를 항상 화면 왼쪽 위에 표시하고 싶다."
  • 상황: "점수판을 항상 화면 오른쪽 위에 표시하고 싶다."
  • 상황: "일시정지 버튼을 항상 화면 오른쪽 아래 구석에 놓고 싶다."

이때, "오른쪽 끝"의 픽셀 좌표를 알아내기 위해 Screen.width가 필요합니다.

 

 

2. 마우스 클릭이나 화면 터치 위치를 확인할 때

  • 상황: "플레이어가 화면의 오른쪽 절반을 클릭했는지, 왼쪽 절반을 클릭했는지 알고 싶다."

Input.mousePosition은 마우스의 위치를 픽셀 좌표로 알려줍니다. 그래서 화면 너비의 절반(Screen.width / 2)보다 마우스의 x좌표가 큰지 작은지 비교해서 알아낼 수 있습니다.

 

 

 

3. 화면 해상도에 따라 UI 크기를 조절할 때

  • 상황: "이 버튼의 너비를 항상 화면 전체 너비의 10% 크기로 만들고 싶다."

다양한 크기의 모니터나 스마트폰에서 게임이 실행될 때, UI가 깨지지 않고 예쁘게 보이게 하려면 화면 크기에 비례해서 UI 크기를 조절해야 합니다. 이때 기준이 되는 것이 Screen.width Screen.height입니다.


황금 규칙

게임 세상 안에서 움직이는 캐릭터, 총알, 몬스터 등에는 Screen.width를 절대 직접 사용하면 안 됩니다.

TV 속 배우(캐릭터)의 위치는 TV 화면 스티커(UI)의 위치와 아무 상관이 없는 것과 똑같습니다. 이 둘은 완전히 다른 세상에 살고 있기 때문입니다.

요약:
Screen.width UI(스티커) 를 위한 것이고,
ScreenToWorldPoint() 계산은 게임 캐릭터(배우) 를 위한 것입니다.

 

 

 

 

 

 

 

 

1. 유니티 방식: Random.Range() (가장 추천!)

// 0부터 9 사이의 정수를 랜덤으로 뽑습니다. (중요: 10은 포함되지 않음!)
int randomNumber = Random.Range(0, 10);

// 1부터 6 사이의 주사위 눈을 뽑습니다.
int diceRoll = Random.Range(1, 7); // 7은 포함되지 않으므로 1,2,3,4,5,6 중 하나가 나옵니다.

다른방법도 있지만..(보통  c#에서 사용하는)

왜 유니티에서는 UnityEngine.Random을 더 선호할까요?

  1. 더 편리함: 매번 new System.Random()이라는 '도구'를 만들 필요 없이, Random.Range()라고 바로 쓸 수 있어서 코드가 짧고 간결합니다.
  2. 안전함: System.Random은 아주 짧은 순간에 여러 번 만들면 똑같은 숫자 패턴이 나오는 문제가 발생할 수 있지만, UnityEngine.Random은 유니티 엔진이 알아서 관리해주기 때문에 그런 걱정이 없습니다.

 

 

 

 

 

 

 

 

왜 코루틴을 추천하는가? ("요리사" 비유)

Update와 델타 타임 방식 (바쁜 요리사)

  • Update 함수는 "아주 바쁘고 정신없는 요리사" 와 같습니다.
  • 이 요리사는 1초에 60번씩 주방 전체를 둘러보며 모든 것을 확인해야 합니다.
  • "파스타 삶을 시간 됐나? (타이머1 확인)"
  • "스테이크 뒤집을 시간 됐나? (타이머2 확인)"
  • "소스 저어줄 시간 됐나? (타이머3 확인)"
  • 주방에 할 일이 많아질수록(타이머가 늘어날수록), 이 요리사(Update 함수)는 점점 더 많은 if문과 timer 변수들로 지저분해지고, 뭘 하는지 파악하기 어려워집니다.

코루틴 방식 (똑똑한 요리사)

  • 코루틴은 "주방 타이머를 아주 잘 쓰는 똑똑한 요리사" 와 같습니다.
  • 이 요리사는 파스타를 냄비에 넣고, 주방 타이머를 8분으로 맞춘 뒤, 파스타에 대해서는 완전히 잊어버립니다.
  • 그리고 다른 중요한 일(스테이크 굽기)에 집중합니다.
  • 8분 뒤에 타이머가 "띠리링!" 하고 울리면, 그때서야 파스타를 확인하러 갑니다.
  • 각 요리가 자기만의 타이머를 가지고 알아서 요리사에게 알려주는 방식이죠. Update라는 메인 주방은 항상 깔끔하게 유지됩니다.

결론: 델타 타임 방식은 간단한 요리 하나에는 좋지만, 여러 요리를 동시에 할 때는 코루틴 방식이 훨씬 체계적이고 실수를 줄여줍니다.


public GameObject fallingRockPrefab; 완벽하게 이해하기

이 한 줄은 유니티의 핵심적인 디자인 철학을 담고 있습니다.

  1. public: "이 변수를 유니티 에디터의 인스펙터(Inspector) 창에 보여줘!" 라는 명령입니다.
  2. GameObject: 이 변수는 '게임 오브젝트'라는 종류의 데이터를 담을 수 있다는 뜻입니다.
  3. fallingRockPrefab: 우리가 지어준 변수의 이름입니다.

이 코드를 저장하고 유니티 에디터로 돌아가면, 이 스크립트 컴포넌트에 Falling Rock Prefab이라는 이름의 빈 슬롯이 생긴 것을 볼 수 있습니다.


"유니티 연결이 GetObject<> 역할인가?"

네, 정확히 보셨습니다! 이것이 바로 유니티의 위대한 점입니다.

방법 1: 코드로 찾기 (GetComponent, GameObject.Find)

  • 이건 코드가 게임 세상 속에서 "이름이 '돌멩이'인 녀석 이리 와봐!" 라고 직접 찾아다니는 방식입니다.
  • 단점: 오브젝트 이름이 바뀌거나, 찾기 힘든 곳에 있으면 코드가 에러를 내거나 게임이 느려질 수 있습니다.

방법 2: 인스펙터에서 연결하기 (public 변수)

  • 이건 우리가 게임을 시작하기 전에 미리 프로젝트 창에 있는 '돌멩이 프리팹'을 마우스로 끌어서, 스크립트의 빈 슬롯에 '직접 연결' 해주는 방식입니다.
  • 장점:
    • 안전함: 이름이 바뀌어도 연결은 유지됩니다.
    • 빠름: 게임이 시작될 때마다 힘들게 찾아다닐 필요가 없습니다.
    • 편리함: 코드를 모르는 기획자나 아티스트도 어떤 돌멩이가 떨어질지 쉽게 바꿀 수 있습니다.

결론: public 변수로 슬롯을 열어두고 유니티 에디터에서 드래그 앤 드롭으로 연결하는 방식은, GetComponent Find보다 훨씬 더 안정적이고 효율적인 방법이라서 적극적으로 권장됩니다.


코루틴 다시 이해하기 ("똑똑한 비서")

코루틴이 아직 어렵다면, '똑똑한 비서' 라고 생각해보세요.

일반 함수 (멍청한 비서)

  • void DropRocks() 라는 일반 함수에게 "돌 100개 떨어뜨려!" 라고 시키면, 이 비서는 그 자리에서 100개를 다 떨어뜨릴 때까지 다른 일을 전혀 하지 않습니다. 그동안 게임은 완전히 멈춰버리겠죠.

코루틴 함수 (똑똑한 비서)

  • IEnumerator DropRocksRoutine() 이라는 똑똑한 비서에게 똑같이 시키면, 비서는 이렇게 행동합니다.
    1. 돌을 하나 떨어뜨립니다.
    2. yield return new WaitForSeconds(1.0f); 라는 마법의 주문을 봅니다.
    3. "아! 여기서 1초 쉬라고 하셨지!" 라고 말하며, 하던 일을 잠시 멈추고 자기 자리로 돌아갑니다. (이 동안 게임은 멈추지 않고 계속 돌아갑니다!)
    4. 1초가 지나면, 다시 돌아와서 멈췄던 그 다음 일부터 다시 시작합니다. (돌을 또 하나 떨어뜨립니다.)

yield return "여기까지 하고, 잠시 쉬었다가, 조건이 되면 여기서부터 다시 시작해!" 라는 책갈피 같은 역할을 하는 것입니다.

 

 

 

 

 

 

 

 

 

1. IEnumerator는 뭔데?

"시간이 포함된 특별한 임무 목록" 이라고 생각하시면 가장 쉽습니다.

일반 함수 (예: void Start())

  • 이건 "쉬지 않고 끝까지 해야 하는 임무 목록" 입니다.
  • 컴퓨터는 이 목록에 적힌 일들을 첫 줄부터 마지막 줄까지 한 번에, 중간에 절대 멈추지 않고 처리해야 합니다.

코루틴 함수 (예: IEnumerator SpawnRocksRoutine())

  • 이건 "중간에 '잠깐 쉬어!' 라는 지시가 포함된 특별한 임무 목록" 입니다.
  • IEnumerator라는 이름표는 C#과 유니티에게 이렇게 말해주는 것과 같습니다:
  • "이 함수는 보통 함수가 아니야! 중간에 yield return이라는 '멈춤' 신호가 나올 수 있으니, 그렇게 알고 있어!"

yield return new WaitForSeconds(1.0f); 라는 코드는 이 특별한 임무 목록에 적힌 "여기서 1초 동안 쉬었다가 다음 일을 해!" 라는 지시인 셈이죠.

결론: IEnumerator는 함수가 시간과 관련된 '멈춤/대기' 기능을 가질 수 있도록 만들어주는 특별한 '타입' 이름표입니다.


2. StartCoroutine(SpawnRocksRoutine());도 뭔데?

"이 특별 임무 목록을 비서에게 주고, '이제 시작해!' 라고 명령하는 것" 입니다.

IEnumerator 함수(특별 임무 목록)는 우리가 만들어 놓기만 해서는 아무 일도 하지 않습니다. 그냥 종이에 적힌 계획일 뿐이죠.

이 계획을 실제로 실행하려면, '코루틴 관리자' 라는 똑똑한 비서에게 이 임무 목록을 전달해야 합니다. StartCoroutine()이 바로 그 '전달하고 실행시키는' 명령어입니다.

컴퓨터의 행동 순서:

  1. StartCoroutine(...) 명령을 봅니다.
  2. "아! SpawnRocksRoutine이라는 특별 임무를 시작하라는 명령이구나!"
  3. 똑똑한 비서(코루틴 관리자)에게 이 임무 목록을 넘깁니다.
  4. 비서는 목록의 첫 줄부터 일을 시작합니다.
  5. yield return new WaitForSeconds(1.0f); 라는 '1초 쉬어!' 지시를 보면, 타이머를 1초로 맞추고 자기 자리로 돌아가 다른 일을 합니다. (그래서 게임이 멈추지 않습니다!)
  6. 1초 뒤에 타이머가 울리면, 다시 돌아와서 멈췄던 바로 그 다음 줄부터 임무를 다시 시작합니다.

결론: IEnumerator 계획을 짜고, StartCoroutine()으로 그 계획을 실행시킨다고 생각하시면 됩니다.


3. Instantiate(...) vs 자바의 new Rock()

네, 역할이 매우 비슷하다는 점에서 정말 정확하게 보셨습니다! 둘 다 '새로운 무언가를 만든다'는 공통점이 있습니다.

하지만 그 결과물은 하늘과 땅 차이입니다.

자바의 new Rock() (아이디어 스케치)

  • 이것은 컴퓨터 메모리(머릿속) 안에 Rock이라는 '데이터 덩어리' 를 만드는 것입니다.
  • 이 '데이터'는 눈에 보이지도 않고, 게임 세상에 위치도 없고, 물리 법칙도 적용되지 않습니다. 그냥 '돌멩이의 정보'라는 아이디어 스케치일 뿐입니다.

유니티의 Instantiate(...) (3D 프린터)

  • 이것은 '프리팹(Prefab)' 이라는 완벽한 설계도를 가지고, 게임 세상 (씬 Scene) 안에 '실존하는 게임 오브젝트' 를 3D 프린터로 찍어내는 것과 같습니다.
  • Instantiate를 하는 순간, 다음과 같은 일이 모두 일어납니다:
    1. fallingRockPrefab이라는 설계도를 복제합니다.
    2. 게임 세상에 눈에 보이는 새로운 오브젝트가 나타납니다.
    3. 이 오브젝트는 Transform 컴포넌트를 가져서 spawnPosition이라는 실제 위치를 갖게 됩니다.
    4. Sprite Renderer가 있어서 실제 모습(그림) 이 보입니다.
    5. Rigidbody 2D가 있어서 중력의 영향을 받습니다.
    6. Collider가 있어서 다른 물체와 부딪힐 수 있습니다.

최종 요약:

new Rock() 이 '돌멩이'라는 아이디어를 종이에 적는 것이라면,
Instantiate(fallingRockPrefab, ...) 는 그 아이디어를 바탕으로 실제 돌멩이를 3D 프린트해서 책상 위에 올려놓는 것입니다.

유니티에서는 게임 세상에 실제로 나타나는 모든 것은 Instantiate를 통해 만들어야 합니다.

 

 

 

 

 

 

 

일반 함수 (void Start())

이것은 '일시정지 버튼이 없는' 옛날 비디오테이프입니다.

  • [▶ 재생] 버튼을 누르는 순간, 처음부터 끝까지 한 번에 쭉~ 봐야 합니다.
  • 중간에 멈추거나, 5분만 보고 나중에 이어서 보는 것이 절대 불가능합니다.

코루틴 함수 (IEnumerator SpawnRocksRoutine())

이것은 우리가 매일 보는 '일시정지 버튼이 있는' 유튜브 동영상입니다.

  • IEnumerator라는 이름표는 유니티에게 "이건 그냥 비디오가 아니라 일시정지 할 수 있는 유튜브 영상이야!" 라고 알려주는 신호입니다.
  • yield return이 바로 그 '일시정지 버튼' 입니다.
  • 컴퓨터가 이 함수를 실행하다가 yield return 이라는 '일시정지 버튼'을 만나면, 영상을 그 자리에서 딱 멈춥니다. 하지만 비디오를 끈 것은 아니죠.
  • new WaitForSeconds(1.0f)는 그 '일시정지 버튼'에 붙어있는 "정확히 1초 뒤에 자동으로 다시 재생해!" 라는 타이머입니다.

그래서 IEnumerator가 도대체 뭔가요?

결론적으로 IEnumerator는, 함수에게 '일시정지' 할 수 있는 초능력을 주는 마법의 주문 같은 것입니다.

이 주문을 함수에 걸어주면, 그 함수는 더 이상 쉬지 않고 끝까지 달리기만 하는 바보가 아니라, 중간에 잠시 멈춰서 숨을 고르고(1초 기다리고), 다시이어서 달릴 수 있는 똑똑한 함수가 되는 것입니다.

한눈에 보는 정리

구분 일반 함수 (void) 코루틴 (IEnumerator)
비유 일시정지 없는 비디오테이프 일시정지 있는 유튜브 동영상
특징 한번 시작하면 절대 못 멈춤 yield return 으로 잠시 멈출 수 있음
핵심 모든 일을 한 번에 처리 일을 나눠서 처리 (기다렸다가 이어서 하기)

 

 

 

 

 

 

 

'yield'는 "양보하다" 라는 뜻입니다.
교차로의 'YIELD (양보)' 표지판을 상상해 보세요. 그게 yield의 모든 것입니다.


일반 함수: 신호등 없는 직진 도로

  • void MyFunction() 같은 일반 함수는 신호등이 없는 고속도로입니다.
  • 차가 이 도로에 진입하면, 출구(함수의 끝)에 도달할 때까지 절대 멈추지 않고 전속력으로 달려야 합니다.
  • 만약 이 도로가 엄청나게 길면(함수 안의 일이 많으면), 다른 차들은 이 차가 나올 때까지 꼼짝없이 기다려야 합니다. -> 게임이 멈춥니다!

코루틴과 yield: '양보' 표지판이 있는 똑똑한 도로

  • IEnumerator MyCoroutine() 함수는 중간중간에 'YIELD' 표지판이 있는 똑똑한 도로입니다.
  • 차가 이 도로를 달리다가 yield 라는 표지판을 만납니다.
  • yield 표지판에는 이렇게 쓰여 있습니다:
  • "일단 멈춰서 교차로 밖의 다른 차들 먼저 보내주고 (다른 게임 로직 실행), 그 다음에 네가 다시 출발해!"
  • yield return 이라는 코드는, 이 표지판 앞에서 운전자가 하는 행동입니다.
    1. yield (양보하다): "알겠습니다. 제가 먼저 가는 걸 양보할게요." 운전자는 잠시 차를 멈추고 운전대를 유니티 엔진에게 넘겨줍니다. (그래서 게임이 멈추지 않고, 다른 Update 같은 일들을 계속 할 수 있습니다.)
    2. return (다시 돌아올 것을 약속하다): "하지만 제 운전은 아직 끝나지 않았어요. 잠시 후에 다시 돌아와서 여기서부터 계속 갈 겁니다." 라고 약속하는 것입니다.

'언제' 돌아올지는 return 뒤에 있는 내용이 결정합니다.

  • yield return new WaitForSeconds(1.0f);
  • "저는 여기서 정확히 1초만 양보하고 쉬겠습니다. 1초 뒤에 깨워주시면 다시 운전하겠습니다."
  • yield return null;
  • "다른 차 한 대만 먼저 보내주고 (다음 프레임까지), 바로 다음에 제가 다시 출발하겠습니다."

그래서 yield가 정확히 하는 일이 뭔가요?

yield는 함수를 '종료'시키지 않고, '일시정지' 시키는 마법의 키워드입니다.

return 은 함수에게 "너의 임무는 끝났어. 이제 집에 가!" 라고 말하는 '해고 통지서' 입니다.

yield return 은 함수에게 "일단 여기까지 하고, 잠시 쉬어. 내가 다시 부를 때까지 기다려!" 라고 말하는 '휴가 명령서' 입니다.

요약:
yield는 코루틴이라는 특별한 함수가 자신의 실행을 잠시 멈추고, 제어권을 유니티의 다른 부분에게 '양보'했다가, 약속된 시간이 되면 다시 돌아와 멈췄던 그 자리부터 일을 재개할 수 있도록 만들어주는 C#의 약속(키워드)입니다. 이 '양보' 덕분에 게임이 멈추지 않는 것입니다.

 

 

 

 

 

 

IEnumerator를 안 쓰면 C# 컴파일러가 yield를 만났을 때, "이게 대체 무슨 말이야?" 하고 전혀 알아먹지 못합니다.

IEnumerator는 C# 언어의 '약속' 또는 '규칙' 입니다.
이번에는 "특별한 자격증" 에 비유해서 설명해 드릴게요.


일반 함수 (void, int, string 등)

이것들은 '일반 자격증' 이나 '신분증' 과 같습니다.
이 자격증을 가진 함수는 정해진 일만 할 수 있습니다.

  • void: "저는 일을 하고 아무것도 돌려주지 않을 겁니다." (결과물 없음)
  • int: "저는 일을 하고 최종 결과물로 숫자 하나를 반드시 돌려줄 겁니다." (정수 반환)

이 '일반 자격증'을 가진 함수에게 "시간을 멈춰라! (yield)" 라고 명령하면, 함수는 이렇게 대답합니다.

"저는 그런 걸 배운 적이 없는데요? 제 자격증에는 그런 능력이 없어요!"
-> 에러 발생!


코루틴 함수 (IEnumerator)

이것은 "시간을 조종할 수 있는 특별 조종사 면허" 와 같습니다.

함수 앞에 IEnumerator 라는 면허증을 붙여주는 순간, 우리는 C# 컴파일러(규칙을 검사하는 감독관)에게 이렇게 선언하는 것입니다.

"이 함수는 보통 함수가 아닙니다. '시간 조종'이라는 특수 훈련을 받았으니, 중간에 yield라는 '시간 정지' 기술을 사용해도 놀라지 마세요!"

컴파일러는 이 IEnumerator 면허증을 보고 나서야 비로소 안심하고 yield라는 코드를 이해할 준비를 합니다.

yield 는 '특별 조종사'가 실제로 사용하는 '시간 정지 레버' 인 셈이죠.

 

 

 

 

 

 

 

방법 1: public 변수로 연결하기 (수동 연결)

이것은 우리가 이전에 했던 "열쇠와 자물쇠" 방식입니다.

    • public GameObject fallingRockPrefab;
    • 이 방식은, 우리가 게임을 시작하기 전에, 이 문을

"열쇠와 자물쇠" 방식입니다.

      • public GameObject fallingRockPrefab;
      • 이 방식은, 우리가 게임을 시작하기 전에, 이 문을 열 수 있는 **'유일한 열쇠(루비)'**를 자물쇠(스크립트)에 미리 꽂아두는 것과 같습니다. 스크립트는 오직 그 지정된 열쇠에만 반응합니다.

방법 2: OnTriggerEnter2D (자동 연결)

    •  

이것이 바로 지금 보고 계신 코드의 방식입니다. "마트의 자동문" 이라고 생각하시면 됩니다.

      • 체력 회복 아이템: 이것은 '자동문' 입니다.
      • 아이템의 콜라이더 (Collider2D): 이것은 자동문의 '센서' 입니다.
      • OnTriggerEnter2D(Collider2D other) 함수: 이것은 "센서에 누군가 들어왔을 때 자동으로 실행되는 시스템" 입니다.

이 자동문은 누가 들어올지 미리 알 필요가 전혀 없습니다. 그냥 누군가 센서 범위 안으로 들어오면, 그 즉시 시스템이 작동할 뿐입니다.