2023/08/02 TIL

김도현·2023년 8월 2일
0

TIL

목록 보기
12/76

레트로의 유니티 게임 프로그래밍 에센스 : C#으로 배우는 입문부터 4가지 게임

5장 게임 오브젝트 제어하기

5.4 변수로 컴포넌트 사용하기

5.4.1 물리 큐브 만들기

GameObject에 Rigidbody를 추가하여 중력과 물리적인 힘에 영향받게 함

Public class Jumper : MonoBehaviour
{
	public Rigidbody myRigidbody;
    
    void Start()
    {
    	myRigidbody.AddForce(0, 500, 0);
    }
}

Rigidbody 타입의 변수 myRigidbody를 선언
myrigidbody의 AddForce() 메서드를 실행해서 Y방향으로 500만큼 힘을 사용

실행시 오류발생

에러 로그를 보면 참조를 할당하지 않았다(Unassigned Reference Exception)는 예외가 발생 -> 변수 myRigidbody에 참조를 할당하지 않았기 때문

5.5 마치며

유니티는 수많은 종류의 컴포넌트를 미리 만들어 제공합니다. 컴포넌트는 클래스 타입이며 클래스 타입의 변수는 참조 타입으로 동작한다.

결론적으로 씬에 존재하는 모든 '실체' 컴포넌트는 코드 상에서 참조 타입의 변수로 가리키고 사용할 수 있습니다.

정리하면 다음 순서로 씬을 구성하고 게임 오브젝트와 컴포넌트를 조종
1. 필요한 컴포넌트를 게임 오브젝트에 추가
2. 스크립트에서 조종할 컴포넌트에 대한 변수를 선언
3. 변수에 컴포넌트를 할당
4. 코드에서 변수를 사용하면 그것이 가리키는 실제 컴포넌트가 동작

이장에서 배운 내용 요약

  • 클래스는 묘사하려는 사물 (오브젝트)을 정의하는 틀입니다.
  • 클래스를 통해 대상에 관한 변수와 메서드를 정의합니다.
  • new 연산자로 클래스로부터 오브젝트를 생성할 수 있습니다.
  • 접근 제한자를 사용해 클래스의 멤버를 선택적으로 외부에 공개할 수 있습니다.
  • C# 기본 내장 변수는 값 타입입니다.
  • 값 타입의 변수는 변수가 값 자체를 저장합니다.
  • 클래스 타입의 변수는 참조 타입입니다.
  • 참조 타입의 변수는 오브젝트로 향하는 참조를 저장합니다.
  • 참조 타입에서는 여러 개의 변수가 하나의 오브젝트를 가리키는 것이 가능합니다.
  • 컴포넌트 큼래스로 만들어져 있습니다.
  • 씬에 있는 게임 오브젝트나 컴포넌트의 참조를 변수에 할당하면 코드 상에서 변수로 해당 게임 오브젝트와 컴포넌트를 사용할 수 있습니다.

3부 탄막 슈팅 게임 _닷지

6장 닷지 플레이어 제작

이장에서 다루는 내용

  • 머티리얼
  • 플레이어 조작 구현하기
  • Update() 메서드
  • Input 입력 감지 메서드
  • 입력 매니저

6.1 씬 구성하기

6.1.2 머티리얼

유니티에서 게임 오브젝트의 컬러는 머티리얼(Material)이 결정
머티리얼은 셰이더와 텍스처가 합쳐진 에셋으로, 오브젝트의 픽셀 컬러를 결정

알베도(Albedo): 반사율
물체가 어떤 색을 반사할지 결정, 알베도는 물체 표면의 기본색을 결정

정리하기

6.2 카메라 설정하기

카메라 컴포넌트는 기본값으로 배경을 가상 하늘인 스카이박스(Skybox)로 채우도록 설정 되어있다.
어색해 보이기 때문에 배경을 단색(Solid Color)으로 변경

6.3 플레이어 제작

  • 파란색 캡슐 모양
  • 상하좌우 혹은 WASD 키로 이동
  • 탄알에 맞으면 죽음

6.3.3 리지드바디 컴포넌트 설정

player 게임 오브젝트를 움직이는 데는 물리적인 힘과 속력이 필요, 따라서 player 게임 오브젝트가 물리적인 상호작용이 가능하도록 리지드바디 컴포넌트를 추가

  • 리지드 바디 제약 설정하기
    1. 인스펙터 창에서 Rigidbody 컴포넌트의 Constraints 필드 펼치기
    2. Freeze Position은 Y 체크, Freeze Rotation은 X, Z체크
    • 이제 Player게임 오브젝트의 높이가 변경되지 않으며, 오직 Y축으로만 회전할 수 있음

트랜스폼의 위치와 회전을 직접 변경하는 경우

리지드 바디의 제약 옵션을 사용하면 힘이나 충돌 등 물리적인 상호작용으로 위치나 회전이 변경되는 것을 막을 수 있습니다.
하지만 트랜스폼 컴포넌트의 위치나 회전에 새로운 값을 할당하여 위치나 회전을 변경하는 것을 막을 수 없다.

6.4 플레이어 스크립트 생성

6.5 사용자 입력 감지

6.5.2 Input을 사용한 입력 감지

유나티의 Input 클래스는 사용자 입력을 감지하는 메서드를 모아둔 집합, Input의 입력 감지 메서드는 실행 시점에 어떤 키를 눌렀는지 알려줍니다.
Update() 메서드는 프레임당 한번씩 실행됩니다. 따라서 Update() 메서드에서 입력 감지 메서드를 사용하면 입력 감지 메서드가 매우 짧은 간격으로 반복 실행되기 때문에 플레이어는 입력이 즉시 감지 된다고 느낀다.

Update() 메서드 작성하기

    1. PlayerController 스크립트의 Update() 메서드를 다음과 같이 수정
void Update()
{
	if (Input.GetKey(KeyCode.UpArrow) == true)
    {
    	playerRigidbody.AddForce(0f, 0f, speed);
    }
    
    if (Input.GetKey(KeyCode.DownArrow) == true)
    {
    	playerRigidbody.AddForce(0f, 0f, -speed);
    }
    
    if (Input.GetKey(KeyCode.RightArrow) == true)
    {
    	playerRigidbody.AddForce(speed, 0f, 0f);
    }

	if (Input.GetKey(KeyCode.LeftArrow) == true)
    {
    	playerRigidbody.AddForce(-speed, 0f, 0f);
    }

}

6.5.3 Input.GetKey()

Input.GetKey() 메서드는 키보드의 식별자를 KeyCode 타입으로 입력받는다.

Input.GetKey()메서드는 실행될 때 해당 키를 누르고 있으면 true, 그렇지 않으면 flase를 반환한다.

KeyCode

Input.GetKey() 메서드의 입력으로 사용하는 KeyCode는 키보드의 키 식별자를 쉽게 가리키기 위한 타입입니다. KeyCode 타입은 내부적으로는 숫자로 동작합니다.
키보드의 키에는 식별자가 할당되어 있습니다. 예를 들어 위쪽 방향키의 식별자는 273입니다. 하지만 숫자로 된 키 식별자를 모두 외우는 것은 무리입니다. 따라서 키 식별자 273에 대응하는 KeyCode.UpArrow를 사용합니다.

Input.GetKey() 계열 메서드

Input.GetKey()메서드는 지정한 키를 누르는 동안 true를 반환합니다. 이외에도 Input.GetKey() 메서드처럼 키보드 입력을 감지하지만, 감지 시점이 다른 Input.GetKey() 계열의 메서드들이 있습니다.

  • Input.GetKey() : 해당 키를 '누르는 동안' true, 그 외에는 false반환
  • Input.GetKeyDown() : 해당 키를 '누르는 순간' true, 그 외에는 false 반환
  • Input.GetKeyUp() : 해당 키를 누르다가 손을 '떼는 순간' true, 그 외에는 false반환

6.6 플레이어 사망 처리

이번에는 사망 처리를 실행하는 Die() 메서드를 만들어 플레이어 사망을 구현

6.6.1 Die() 메서드 추가

Die() 메서드는 자신의 게임 오브젝트를 비활성화하여 '죽음'을 구현하는 메서드
Die() 메서드는 탄알과 플레이어가 충돌했을 때 실행, 탄알과 플레이어 사이의 충돌 감지는 7장에서 탄알과 함께 구현

Die() 메서드는 PlayerController 스크립트가 스스로 실행하지 않습니다. 대신 플레이어에 부딪친 탄알이 Player 게임 오브젝트의 PlayerController 컴포넌트에 접근하여 실행
따라서 Die() 메서드는 Public으로 지정되어 PlayerController 클래스 외부에서 접근할 수 있어야 합니다.

public void Die()
{
	gameObject.SetActive(false);
}

6.6.2 gameObject

gameObject는 컴포넌트 입장에서 자신이 추가된 게임 오브젝트를 가리키는 변수
gameObject는 GameObject 타입의 변수이며 컴포넌트들의 기반 클래스인 MonoBehaviour에서 제공

모든 컴포넌트는 gameObject 변수를 이용해 자신을 사용 중인 게임 오브젝트(자신의 게임 오브젝트)에 접근 가능

PlayerController스크립트는 Player 게임 오브젝트에 추가할 것입니다. 따라서 이 스크립트에서 gameObject는 Player게임 오브젝트를 가리키게 됩니다.

6.6.3 SetActive() 메서드

모든 게임 오브젝트는 스스로를 끄고 켜는 기능을 가지고 있습니다. 인스펙터 창에서 게임 오브젝트 이름 왼쪽에 보이는 체크 박스가 게임 오브젝트를 활성화/비활성하는 버튼입니다.

6.7 PlayerController 스크립트 개선하기

PlayerController 스크립트를 완성했지만 몇 가지 문제가 있습니다.
1. 조작이 게임에 즉시 반영되지 않습니다
리지드바디 컴포넌트의 AddForce() 메서드는 힘을 추가합니다. 누적된 힘으로 속도를 점진적으로 증가 시키기 대문에 속도가 충분히 빨라질 때까지 시간이 걸립니다. 또한 이동 중에 반대 방향으로 이동하려는 경우 관성에 의해 힘이 상새되어 방향 전환이 금방 이루어지지 않습니다.
2. 입력 감지 코드가 복잡합니다.
방향키를 감지하는 데 if문을 네 개 사용했습니다. 이것을 좀 더 쉽고 간결한 코드로 개선하고 싶습니다.
3. playerRigidbody에 컴포넌트 드래그&드롭으로 할당하는 것이 불편합니다.
6.6.5절 'PlayerController 컴포넌트 설정하기' 에서 인스펙터 창에서 PlayerController 컴포넌트의 Player Rigidbody 필드로 리지드바디 컴포넌트를 직접 드래그&드롭 했습니다. 변수에 컴포넌트를 직접 드래그&드롭하는 방식은 불편하며, 잘못된 값을 할당할 위험이 있습니다. 따라서 변수에 컴포넌트의 첨조를 할당하는 과정을 코드로 실행하고 싶습니다.

6.7.1 Start() 메서드 수정

[과정01]리지드바디 컴포넌트를 코드에서 할당하기

  1. 변수 PlayerRigidbody와 Start()메서드 부분을 다음과 같이 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
	// 이동에 사용할 리지드 바디 컴포넌트
    private Rigidbody playerRigidbody;
    // 이동 속력
    public float speed = 8f;

    void Start()
    {
    	// 게임 오브젝트에서 Rigidbody 컴포넌트를 찾아 PlayerRigidbody에 할당
        playerRigidbody = GetComponent<Rigidbody>();
    }

    void Update()
    {
    	// Update() 메서드 내용 생략
    }

    public void Die()
    {
    	// 자신의 게임 오브젝트를 비활성화
        gameObject.SetActive(false);
    }

}

변수 playerRigidbody의 접근 한정자를 public에서 private로 변경했습니다. 따라서 나중에 인스펙터 창에서 PlayerController 컴포넌트를 봤을 때 이전의 PlayerRigidbody 필드가 더 이상 표시되지 않 것을 알 수 있습니다.

6.7.2 GetComponent() 메서드

GetComponent() 메서드는 원하는 타입의 컴포넌트를 자신의 게임 오브젝트에서 찾아오는 메서드입니다. GetComponent() 메서드는 꺾쇠 <>로 가져올 타입을 받습니다.

Start() 메서드에서 실행된 GetComponent<Rgidbody>();는 자신의 게임 오브젝트에서 Rigidbody 타입의 컴포넌트를 찾아서 가져옵니다. 즉, 아래 코드는 Player 게임 오브젝트에서 리지드바디 컴포넌트를 찾아서 playerRigidbody 변수에 할당합니다.

void Start()
{
	playerRigidbody = GetComponent<Rigidbody>();
}

GetComponent() 메서드가 컴포넌트를 찾지 못했을 때

게임 오브젝트에 찾으려는 차입의 컴포넌트가 추가되어 있지 않으면 GetComponent() 메서드는 NULL을 반환합니다.

제네릭

GetComponent() 메서드에서 사용한 꺾쇠 <>는 제네릭(Generic) 기법입니다. 제네릭은 메서드나 클래스가 여러 타입에 호환되게 합니다. 꺾쇠 안에 원하는 타입을 명시하면 클래스나 메서드가 해당 타입에 맞춰 동작합니다.
제네릭을 사용하지 않으면 같은 처리를 위한 여러 타입의 메서드나 클래스를 일일이 만들어야 합니다. 유니티가 제공하는 컴포넌트의 종류는 수없이 많습니다. 따라서 다음과 같은 방법으로 모든 종류의 컴포넌트에 대한 GetComponent() 메서드를 준비하는 것은 무리입니다.
이러 문제를 해결하기 위해 GetComponent()는 제네릭을 사용할 수 있도록 구현되어 있으며, 제네릭 덕분에 하나의 GetComponent() 메서드로 모든 타입의 컴포넌트에 대응할 수 있다.

6.7.3 조작감 개선하기

[과정01] PlayerController의 기존 Update() 메서드 개선

  1. PlayerController 스크립트의 Update() 메서드를 다음과 같이 수정
	void Update()
    {
    	// 수평축과 수직축의 입력값을 감지하여 저장
        float xInput = Input.GetAxis("Horizontal");
        float zInput = Input.GetAxis("Vertical");
		
        // 실제 이동 속도를 입력값과 이동 속력을 사용해 결정
        float xSpeed = xInput * speed;
        float zSpeed = zInput * speed;
		
        // Vector3 속도를 (xSpeed, 0, zSpeed)로 생성
        Vector3 newVelocity = new Vector3 (xSpeed, 0, zSpeed);
		// 리지드바디의 속도에 newVelocity 할당
        playerRigidbody.velocity = newVelocity;
    }

수정된 코드는 다음과 같은 동작을 수행합니다.

  • 수평축과 수직축의 입력값을 감지
  • 속도를 나타낼 새로운 Vector3를 생성
  • 리지드바디 컴포넌트의 속도를 변경

6.7.4 GetAxis() 메서드

앞의 개선된 Update() 메서드를 보면 Input.GetKey() 대신 Input.GetAxis() 메서드가 등장했습니다. Input.GetAxis() 메서드는 어떤 축에 대한 입력값을 숫자로 반환하는 메서드 입니다.

float Input.GetAxis(string axisName); 

Input.GetAxis() 메서드는 축(Axis)의 이름을 받습니다. 그리고 다음 경우에 따라 감지된 입력값을 반환합니다.

  • 축의 음의 방향에 대응되는 버튼을 누름 : -1.0
  • 아무것도 누르지 않음 : 0
  • 축의 양의 방향에 대응되는 버튼을 누름 : +1.0
    입력축은 6.8절 '입력 매니저'에서 설명할 입력 매니저를 이용해 설정합니다. 기본 설정으로 추가 되어 있는 Horizontal 축과 Vertical 축의 대응 입력키와 출력되는 입력값은 다음과 같습니다.

Horizontal 축의 경우

  • Horizontal(수평) 축에 대응되는 키
    • 음의 방향 : ←(왼쪽 방향키), A 키
    • 양의 방향 : →(오른쪽 방향키), D키
  • Input.GetAxis("Horizontal")의 출력값
    • ← 또는 A 키를 누름 : -1.0
    • 아무것도 누르지 않음 : 0
    • → 또는 D 키를 누름 : +1.0

Vertical 축의 경우

  • Vertical(수직)축에 대응되는 키
    • 음의 방향 : ↓(아래쪽 방향키), S키
    • 양의 방향 : ↑(위쪽 방향키), W키
  • Input.GetAxix("Vertical")의 출력값
    • ↓ 또는 S 키를 누름 : -1.0
    • 아무것도 누르지 않음 : 0
    • ↑ 또는 W 키를 누름 : +1.0

6.7.5 속도 계산하기

float xSpeed = xInput * speed;
float zSpeed = zInput * speed;

xInput는 X 방향을 표시 speed는 속도를 나타냄 -> xSpeed는 X 방향 이동 속도
(왼쪽, 정지, 오른쪽 이동을 모두 표현)

Vector3 newVelocity = new Vector3 (xSpeed, 0, zSpeed);
playerRigidbody.velocity = newVelocity;

xSpeed, zSpeed에 값을 할당한 다음 이들을 기반으로 새로운 Vector3 변수 newVelocity를 선언했습니다.
newVelocity는 X, Y, Z 방향으로의 속도를 나태내기 위한 변수입니다.

리지드바디 컴포넌트는 현재 속도를 표현하는 Vector3 타입의 velocity 변수를 제공합니다.
리지드바디의 velocity 변수로 현재 속도를 알 수 있으며, 반대로 해당 변수에 새로운 값을 할당하여 현재 속도를 변경할 수 있습니다.

우리는 playerRigidbody.velocitynewVelocity를 할당했습니다. 그러면 playerRigidbody에 할당된 리지드바디 컴포넌트의 현재 속도가 newVelocity로 변경됩니다.

6.7.6 Vector3

Vector3는 원소 x, y, z를 가지는 타입입니다. 위치, 크기, 속도, 방향 등을 나타낼 때 주로 사용합니다.

Rigidbody의 AddForce()와 velocity의 차이

이전 예제에서 조작이 무겁게 느겨졌던 이유는 관성 때문입니다. 기존 Update() 메서드에서는 AddForce() 메서드를 사용해 힘을 누적하고 속력을 점진적으로 증가시켰습니다.
Rigidbody의 Velocity를 수정하는 것은 이전속도를 지우고 새로운 속도를 사용하는 겁니다. 따라서 관성을 무시하고 속도가 즉시 변경됩니다.

6.7.7 완성된 스크립트 확인(2차)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
	private Rigidbody playerRigidbody;
	public float speed = 8f;
   
	void Start()
    {
    	playerRigidbody = GetComponent<Rigidbody>();
    }

    void Update()
    {
        float xInput = Input.GetAxis("Horizontal");
        float zInput = Input.GetAxis("Vertical");

        float xSpeed = xInput * speed;
        float zSpeed = zInput * speed;

        Vector3 newVelocity = new Vector3 (xSpeed, 0, zSpeed);
        playerRigidbody.velocity = newVelocity;
    }

    public void Die()
    {
       gameObject.SetActive(false);
    }
}

1개의 댓글

comment-user-thumbnail
2023년 8월 2일

많은 도움이 되었습니다, 감사합니다.

답글 달기