5. 들어가기 전에
게임 월드 속 사물을 오브젝트라고 한다. 온전한 하나의 사물이라고 인식할 수 있다면 어떠한 것도 오브젝트가 될 수 있다. ex) 캐릭터 오브젝트, 탄알 오브젝트, 바위 오브젝트
게임이 아니더라도 대부분의 프로그램에서는 하나의 독립적인 사물을 오브젝트라는 단위로 표현한다. 그러한 오브젝트들이 상호작용하여 프로그램을 이룬다.
이 장에서 배울 객체지향은 독립적이며 스스로동작하는 여러 객체(오브젝트)가 모여 거대한 프로그램이 완성되는 구조를 만드는 방법이다. 이것이 유니티 C# 스크립트의 클래스가 동작하는 방식이다.
이 장에서 다루는 내용
- 클래스와 오브젝트의 개념을 이해한다.
- C# 클래스로 원하는 사물을 정의한다.
- 오브젝트를 생성하는 방법을 배운다.
- 참조 변수를 사용하여 유니티의 컴포넌트를 조작한다.
레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고
5.1 클래스와 오브젝트
클래스와 오브젝트는 객체지향의 핵심이다. 객체지향은 사람이 현실 세상을 보는 방식에 가깝게 프로그램을 완성하는 것이다.
일반적으로 사람은 현실의 사물을 분해하여 생각하지 않는다. 사람은 사물을 하나의 온전한 독립체로 여기는 경향이 있다. ex) 휴대폰을 프로세서, 카메라, 디스플레이, 각종 센서와 엄청난 수의 논리회로 등 여러 부품의 집합으로 여기지 않고 하나의 온전한 물건(오브젝트) 로 본다.
객체지향은 프로그램을 이러한 오브젝트의 집합으로 구성하는 방식이다.
5.1.1 클래스
3장에서 클래스를 묘사할(추상화) 대상과 관련된 코드(변수와 메서드) 를 묶는 틀이라고 했다. 예를 들어 사람을 추상화한 Human 클래스는 사람의 특징을 규정하는 변수와 메서드가 구현된다. 사람, 몬스터, 아이템 등 다양한 사물에 관한 코드를 각자의 클래스로 만들어 묶어두면 관리가 쉬워진다.
다음과 같이 사람에 관한 Human 클래스를 만든다고 하자.
class Human{
string name;
void Walk();
// ...Human 에 관한 변수와 메서드들
}
여기서 Human 클래스 내부의 변수 name 과 Walk() 메서드는 그냥 이름과 걷기가 아니다. 이들은 사람의 이름과 사람의 걷기이다.
정리하면 클래스는 표현하고 싶은 대상을 추상화(대상의 핵심적인 개념과 기능을 추려내는 것) 하여 대상과 관련된 변수와 메서드를 정의하는 틀이다.
클래스는 프로그램 속에 실제로 존재하는 사물(실체 또는 오브젝트) 이 아니다. 클래스는 틀이다. 틀에 쇳물을 부어 실제 물건을 만들 수 있듯이, 클래스를 이용하여 오브젝트를 만든다고 생각하면 된다.
5.1.2 오브젝트
물건의 설계도인 클래스와 달리 오브젝트는 실제로 존재하는 물건(실체) 이다.
클래스는 실제로 존재하는 오브젝트가 아니다. Human 클래스로 더 자세히 생각해보면 다음과 같다.
Human 클래스는 실제로 존재하는 오브젝트가 아니다. 하지만 Human 클래스라는 틀을 사용해서 실제 존재하는 Human 오브젝트를 찍어낼 수 있다. Human 클래스로 두 개의 Human 오브젝트(철수와 영희) 를 생성한다고 해보자.
여기서 클래스는 하나만 존재하지만 클래스를 기반으로 생성한 오브젝트는 여러 개 존재할 수 있다는 사실에 주목한다.
클래스라는 틀로 오브젝트를 찍어내 실체화하는 것을 인스턴스화한다고 한다. 인스턴스화를 통해 생성된 오브젝트를 인스턴스라고 한다.
오브젝트는 인스턴스를 포함하는 개념이므로 두 단어는 혼용된다. 이 책에서는 오브젝트가 실시간으로 어떤 클래스에서 복제 생성되었다는 사실을 강조하기 위해 때로은 오브젝트를 인스턴스라고도 표기할 것이라고 한다.
5.1.3 오브젝트의 독립성
하나의 원본 클래스에서 여러 개의 오브젝트를 생성할 수 있다. 오브젝트는 서로 독립적이며 구별 가능한 실체이다.
Human 클래스를 통해 더 자세히 생각해본다.
철수와 영희 모두 Human 타입의 오브젝트이다. 하지만 이들을 서로 다른 개별적인 실체로 구분하고 접근한다.
이것은 지구상에 수십억 명의 사람이 있고, 이들이 모두 사람이라는 하나의 분류에 속한다는 것을 떠올리면 이해하기 쉽다.
철수와 영희는 서로 구분되는 실체이기 때문에 철수가 죽거나 사는 것은 영희에게 어떠한 영향도 주지 않는다.
5.2 C# 클래스 만들기
C# 에서 클래스와 오브젝트가 어떻게 동작하는지 확인해본다. 동물(Animal) 클래스를 만들고 그것을 사용하여 동물 오브젝트들이 모여 있는 동물원을 만든다.
5.2.1 Animal 클래스 만들기
동물원을 만들기 위해서는 동물 오브젝트를 만들어야 하며, 동물 오브젝트를 만들기 위해서는 먼저 동물 클래스를 만들어야 한다.
그러므로 Animal 클래스부터 만든다. Animal 클래스는 동물을 표현하는 클래스이다. 예제를 쉽게 진행하기 위해 동물은 변수로 이름과 울음소리만 가지며, 메서드는 울음소리를 내는 기능만 가지고 있다고 한다.
1. Animal 스크립트 생성하기
- 프로젝트 창에서 + > C# Script 클릭
- 생성된 스크립트 이름을 Animal 로 설정 > 더블 클릭으로 스크립트 열기
2. Animal 클래스 완성하기
- Animal 클래스의 전체 코드를 다음과 같이 수정한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Animal
{
// 동물에 대한 변수
public string name;
public string sound;
// 울음소리를 재생하는 메서드
public void PlaySound(){
Debug.Log(name + " : " + sound);
}
}
- 스크립트를 저장한다.
Animal 클래스는 이름 name 과 울음소리 sound 변수를 가지며, 울음소리를 재생하는 메서드 PlaySound() 를 갖는다.
이렇게 Animal 클래스라는 새로운 타입을 정의했지만 아직은 어떠한 일도 일어나지 않는다. Animal 클래스는 실체(오브젝트) 가 아니기 때문이다.
MonoBehaviour 를 상속하지 않은 경우
이 장은 순수한 클래스의 역할만 살펴볼 것이기에 Animal 스크립트에서 MonoBehavoiur 를 지웠다. 그러면 Animal 클래스는 MonoBehaviour 클래스를 상속하지 않는다.
MonoBehaviour 클래스는 게임 오브젝트의 컴포넌트로서 필요한 기능들을 제공한다. 따라서 이 클래스를 상속하지 않은 Animal 클래스는 게임 오브젝트에 컴포넌트로 추가할 수 없다.
class Animal : MonoBehaviour -> class Animal
5.2.2 Animal 오브젝트 만들기
이제 Animal 클래스를 기반으로 Animal 오브젝트를 만드는 Zoo 스크립트를 작성한다.
1. Zoo 스크립트 생성하기
- 프로젝트 창에서 + > C# Script 클릭
- 생성된 스크립트의 이름을 Zoo로 설정 > 더블 클릭으로 스크립트 열기
2. 새로운 Animal 오브젝트 생성
- Zoo 스크립트를 다음과 같이 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Zoo : MonoBehaviour
{
void Start()
{
Animal tom = new Animal();
tom.name = "톰";
tom.sound = "냐옹";
tom.PlaySound();
}
}
- 스크립트 저장
이제 수정한 Zoo 스크립트가 어떻게 동작하는지 테스트한다.
4장에서 스크립트를 완성했다고 해서 코드가 동작하는 것은 아니라고 했다. 프로젝트 창의 Zoo 스크립트는 단순 텍스트일 뿐이다. Zoo 스크립트를 게임 오브젝트에 컴포넌트로 추가해야 한다. 그렇게 해야 Zoo 스크립트는 게임상의 오브젝트가 되고 게이미 속에서 동작한다.
새로운 게임 오브젝트를 만들고 Zoo 스크립트를 컴포넌트로 추가한다.
3. 새 게임 오브젝트에 Zoo 스크립트 붙이기
- 빈 게임 오브젝트 생성(하이어라키 창에서 + > Create Empty 클릭)
- 프로젝트 창의 Zoo 스크립트를 하이어라키 창의 GameObject 로 드래그&드롭
4. Zoo 스크립트 테스트하기
- 플레이 버튼을 눌러 씬 시작 > 콘솔 창의 로그 확인
5.2.3 Zoo 스크립트 분석하기
Zoo 스크립트의 Start() 메서드에서 작성한 코드를 살펴본다.
Animal tom = new Animal();
새로운 Animal 오브젝트를 생성하고 그것을 Animal 타입의 변수 tom 에 할당한다. 여기서 new 연산자를 사용하는 부분을 자세히 본다.
1. 새로운 Animal 오브젝트를 생성하는 부분
new Animal();
1. new 연산자는 클래스로부터 인스턴스를 생성한다.
new 연산자는 어떤 클래스의 오브젝트를 새로 하나 생성한다. 여기서는 새로운 Animal 오브젝트를 생성했다. new 연산자 뒤에는 실행할 클래스의 생성자가 온다.
2. Animal() 메서드는 Animal 클래스의 생성자이다.
생성자의 이름은 클래스의 이름과 같다. 생성자는 오브젝트를 생성할 때 실행되며, 오브젝트가 생성될 때 어떻게 초기화할지 정의하기 위해 사용하는 특수한 메서드이다.
클래스를 정의할 때 생성자를 따로 만들지 않아도 기본 생성자가 암시적으로 생성된다. 현재 코드에서 사용한 Animal() 생성자가 Animal 클래스의 기본 생성자이다. Animal() 기본 생성자는 초기화에 필요한 별다른 입력을 받지 않는다.
생성자에서 초기화에 필요한 입력을 받는 경우는 6장에서 보게 될 것이다. 지금은 생성자를 새로운 오브젝트를 생성할 때 실행하는 특수한 메서드라는 정도로만 이해하면 된다.
정리하면 new Animal(); 은 새로운 Animal 오브젝트(인스턴스) 를 탄생시켜라라고 하는 명령이다.
2. 생성한 Animal 오브젝트를 Animal 타입의 변수 tom 에 할당하는 부분
Animal tom = new Animal();
이제 tom 은 방금 생성한 Animal 오브젝트를 가리키게 된다. 생성된 Animal 오브젝트는 변수로 name 과 sound 를 갖는다.
3. tom 에 할당된 Animal 오브젝트의 name 과 sound 값을 변경하는 부분
tom.name = "톰";
tom.sound = "냐옹";
오브젝트 내부의 변수나 메서드를 멤버라고 부른다. 멤버는 점(.) 연산자로 접근할 수 있다.
4. tom 에 할당된 Animal 오브젝트의 PlaySound() 메서드를 실행하는 부분
tom.PlaySound();
같은 원리로 tom 에 할당된 Animal 오브젝트의 PlaySound() 메서드를 점 연산자로 접근하고 실행한다.
[추가 내용]
MonoBehaviour 는 new 를 사용하지 않는다.
유니티에서 작성하는 대부분의 스크립트는 MonoBehaviour 클래스를 상속한다. 그런데 MonoBehaviour 를 상속한 클래스는 new 로 오브젝트로 생성할 수 없다.
MonoBehaviour 를 상속한 클래스는 드래그&드롭 등으로 스크립트를 게임 오브젝트에 컴포넌트로 추가하는 방법으로만 오브젝트로 만들 수 있다.
C# 에서는 클래스를 기반으로 새로운 오브젝트를 생성할 때 new 를 사용하는 것이 일반적이다. 그래서 이전에 C# 프로그래밍을 접했던 사람들은 유니티 C# 스크립트는 new 를 거의 사용하지 않는다는 사실에 당황하기도 한다.
MonoBehaviour 를 상속한 클래스는 컴포넌트로 동작하며, 컴포넌트는 게임 오브젝트의 부품으로만 존재할 수 있다. 또한 컴포넌트는 게임 오브젝트에 추가될 대 컴포넌트로서 필요한 초기화 과정을 거친다.
new 연산자로 MonoBehaviour 를 상속한 클래스를 오브젝트로 생성하면 필요한 초기화 과정과 게임 오브젝트에 추가되는 과정을 전부 생략하고 즉시 오브젝트가 생성된다. 따라서 생성된 오브젝트가 정상적으로 동작하지 않는다.
5.2.4 멤버와 접근 제한자
지금까지 클래스에서 오브젝트를 생성하는 방법을 다루었다. 이번에는 클래스를 이루는 요소를 좀 더 자세히 살펴본다. Animal 스크립트를 열고 Animal 클래스 부분을 다시 살펴본다.
public class Animal
{
// 동물에 대한 변수
public string name;
public string sound;
// 울음소리를 재생하는 메서드
public void PlaySound()
{
Debug.Log(name + " : " + sound);
}
}
1. 클래스의 멤버
어떤 클래스에 속하며, 해당 클래스의 데이터와 행위를 표현하는 요소를 멤버라고 한다. 즉, Animal 클래스에 속한 변수 name 과 sound, 그리고 PlaySound() 메서드가 Animal 의 멤버이다.
2. 클래스의 필드
클래스의 멤버 중에서 변수를 필드라고 부른다. 즉, Animal 클래스의 멤버 중에서 변수 name 과 sound 가 Animal 클래스의 필드이다.
이 책에서는 때때로 클래스의 멤버 변수를 특별히 강조할 때 필드라고 표시한다.
3. 접근 제한자
기본적으로 클래스의 멤버들은 외부에서 보이지 않도록 감추어져 있다. 하지만 원한다면 접근 제한자를 사용해 선택적으로 멤버의 공개 여부를 결정할 수 있다.
접근 제한자는 클래스의 멤버의 공개 여부를 정하는 키워드이다. 다음 세 가지 접근 제한자가 가장 자주 사용된다(protected 는 16장에서 처음 사용하기 때문에 여기서는 자세히 다루지 않는다).
- public: 클래스 외부에서 멤버에 접근 가능
- private: 클래스 내부에서만 멤버에 접근 가능
- protected: 클래스 내부와 파생 클래스에서만 멤버에 접근 가능
public 은 멤버를 외부에서 접근 가능하게 공개하는 키워드이다. 클래스 외부에서 어떤 멤버에 접근을 허용하려면 멤버 앞에 public 키워드를 명시해야 한다.
private 는 멤버를 외부에서 접근할 수 없도록 감추는 키워드이다. private 가 기본값이다. 그러므로 멤버에 어떠한 접근 제한자도 명시하지 않으면 암묵적으로 private 가 적용된다.
예를 들어 접근 제한자를 명시하지 않고 멤버 변수를 string sound; 로 선언하면 private string sound; 로 처리된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Animal
{
// 동물에 대한 변수
public string name;
private string sound;
// 울음소리를 재생하는 메서드
public void PlaySound()
{
Debug.Log(name + " : " + sound);
}
}
위와 같이 Animal 클래스의 name 은 public, sound 는 private 으로 선언했다고 가정한다. 이렇게 되면 Zoo 스크립트에서 Animal 타입인 tom 을 다룰 때 tom.name 은 접근 가능하고 tom.sound 는 접근 불가능하다. 따라서 tom.name = "톰"; 은 정상적으로 실행되고 tom.sound = "냐옹"; 은 에러가 발생한다.
Zoo 스크립트에서 tom.name, tom.sound, tom.PlaySound() 를 모두 접근하고 사용할 수 있었던 이유는 Animal 클래스에서 이들을 public 으로 선언했기 때문이다.
5.3 참조 타입
여기까지 진행했다면 어째서 int, string 등의 원시적인 타입의 변수들은 new 키워드를 사용하지 않고 변수에 곧바로 값을 할당하는지 의문이 생길 수 있다.
int year = 1999;
string movie = "바람과 함께 사라지다";
이들과 반대로 Animal 타입의 변수 tom 에 값을 할당할 때는 new 키워드를 사용하여 새로운 Animal 오브젝트를 생성하고 tom 에 할당했다.
Animal tom = new Animal();
결론부터 말하면 클래스로 만든 변수는 참조(reference) 타입이기 때문이다. 참조 타입의 변수는 실체화된 오브젝트가 아니다. 참조 타입의 변수를 선언하는 것만으로는 오브젝트가 생성되지 않기 때문에 new 를 사용해 오브젝트를 개별적으로 생성해야한다.
예제에서 Animal 타입의 변수 tom 은 생성된 Animal 오브젝트 그 자체가 아니다.
tom 은 생성된 Animal 오브젝트를 가리키는 참조값을 저장하는 변수이다. 예제에서 tom 에 할당된 값은 new 로 생성된 Animal 오브젝트가 아니라 생성된 Animal 오브젝트로 향하는 참조값이다.
5.3.1 두 마리의 동물 오브젝트
Zoo 스크립트에서 Animal 타입의 오브젝트를 추가 생성해서 참조 타입이 무엇인지 살펴본다. tom 과 jerry 라는 Animal 오브젝트를 추가로 생성한다.
1. Animal 오브젝트 두 개 생성하기
- Zoo 스크립트의 Start() 메서드 부분을 다음과 같이 수정
- 스크립트 저장
public class Zoo : MonoBehaviour
{
void Start()
{
Animal tom = new Animal();
tom.name = "톰";
tom.sound = "냐옹";
Animal jerry = new Animal();
tom.name = "제리";
tom.sound = "찍찍!";
tom.PlaySound();
jerry.PlaySound();
}
}
먼저 tom 에 새로운 Animal 오브젝트를 생성하여 할당한다.
그다음 Animal 타입의 변수 jerry 를 선언하고 jerry 에 새로운 Animal 오브젝트를 생성하여 할당한다. 그리고 jerry 의 멤버 변수 name 과 sound 에 적절한 값을 할당한다. 마지막 부분에서는 울음소리를 콘솔로 출력한다.
플레이 버튼을 눌러서 출력을 확인하면 다음과 같다.
5.3.2 참조값 변경하기
이번에는 기존 코드에 변경사항을 추가한다. 변수 jerry 에 변수 tom 의 값을 대입한다.
1. jerry 에 tom 대입하기
- Zoo 클래스의 Start() 메서드 부분을 다음과 같이 수정
- 스크립트 저장
public class Zoo : MonoBehaviour
{
void Start()
{
Animal tom = new Animal();
tom.name = "톰";
tom.sound = "냐옹";
Animal jerry = new Animal();
jerry.name = "제리";
jerry.sound = "찍찍!";
jerry = tom;
jerry.name = "미키";
tom.PlaySound();
jerry.PlaySound();
}
}
플레이 버튼을 눌러서 Console 창을 확인해보면 다음과 같다.
5.3.3 참조 타입의 동작
5.3.2 의 결과를 이해하기 위해 참조 타입이 동작하는 방식을 알아보고자 한다.
변수 tom 과 jerry 는 Animal 오브젝트 그 자체가 아니라 Animal 오브젝트를 가리키는 참조값을 저장한다. 참조값은 실제 오브젝트의 메모리 주소에 대응되는 값이다(즉, 오브젝트가 저장되어 있는 메모리의 위치를 변수가 값으로 가지고 있는 것).
즉, tom 과 jerry 에 할당된 참조값을 찾아가면 진짜 오브젝트가 있다. 예를 들어 tom.name = "톰"; 이 실행되면 tom 그 자체가 수정되는 것이 아니다. 대신 tom 에 저장된 참조값을 통해 실제 Animal 오브젝트를 찾아가서 해당 Animal 오브젝트의 name 을 수정한다.
jerry = tom; 을 실행하면 jerry 의 모든 멤버 변수의 값이 tom 의 모든 멤버 변수의 값으로 덮어 쓰기 된다고 착각하기 쉽지만 실상은 그렇지 않다.
실제로는 jerry 에 할당된 참조값을 tom 에 할당된 참조값으로 덮어쓰기 한다(즉, 이제 jerry 는 tom 이 가리키고 있는 오브젝트의 참조값을 가지게 되는 것이다).
jerry 에 할당된 참조값이 tom 의 참조값으로 변경되면서 tom 이 가리키는 Animal 오브젝트를 가리키게 되는 것이다.
결론적으로 변수는 tom 과 jerry 두 개 존재하지만 두 변수가 참조값을 통해 가리키는 Animal 오브젝트는 하나뿐이며 jerry 를 통해 Animal 오브젝트를 수정하는 것은 tom 을 통해 Animal 오브젝트를 수정하는 것과 같은 의미가 된다.
따라서 jerry.name 을 미키로 수정했을 때, tom.name 도 미키가 된 것이다.
그런데 jerry 가 tom 이 가리키는 오브젝트를 가리키게 되면서 jerry 가 본래 가리키던 오브젝트는 아무도 가리키지 않는 미아가 되었다. 이렇게 아무도 가리키지 않는 오브젝트는 사용할 방법이 없다. 해당 오브젝트를 부를 이름(변수) 이 없어 접근할 방법이 없기 때문이다.
이렇게 아무도 가리키지 않는 오브젝트는 C# 의 가비지 컬렉터가 틈틈이 자동으로 파괴하여 정리한다.
5.3.4 하나의 실체와 여러 개의 참조 변수
이와 같이 변수에 실체가 아니라 실체로 향하는 참조가 할당되고, 변수에 접근하면 참조를 통해 실체에 접근하는 변수를 참조 타입이라고 한다. C# 에서 클래스 타입의 변수는 참조 타입으로 동작한다.
참조 타입은 한 사람을 여러 개의 별명으로 부르는 상황을 만들 수 있다. 한 사람을 다양한 별명으로 부를 수 있지만, 그 모든 별명이 가리키는 실체는 하나이다. 즉, 오브젝트는 하나지만 그것을 여러 개의 참조 변수가 동시에 가리킬 수 있다.
Some a = new Some();
Some b = a;
Some c = a;
위와 같은 코드가 있을 때, 셋 중 어느 하나를 수정하면 나머지에도 동일하게 반영된다. 세 개의 변수가 동일한 오브젝트를 가리키고 있기 때문이다.
5.3.5 값 타입과 참조 타입
모든 변수가 참조로 동작하는 것은 아니다. 4장에서 봤던 float, int, string 등의 C# 내장 변수는 참조로 동작하지 않는다. 이런 타입을 값(value) 타입이라고 한다.
참조 타입 변수는 값(실체) 으로 향하는 참조를 저장하고, 값 타입의 변수는 해당 변수 공간에 값 자체를 저장한다.
값 타입은 여러 변수가 하나의 실체를 공유하는 상황이 생기지 않는다. 값 타입 중 하나인 int 타입을 사용하는 예를 보면 알 수 있다.
int a = 0;
int b = 10;
a = b; // a=10, b=10
b = 100; // a=10, b=100
주석으로 각 변수가 가지는 값을 작성했다. 값 타입에서는 a=b; 이후 b 를 수정했을 때 a 에 같은 수정 사항이 반영되지 않는다. 값 타입 변수에 값을 할당하면 값의 참조가 아니라 값의 사본이 할당된다.
참조 타입 | 값 타입 |
class 타입 | C# 내장 변수 bool int float char double string(immutable 로 선언된 class) ... |
유니티의 모든 컴포넌트 | struct(구조체) 타입 Vector3 Color ... |
우리가 작성할 C# 스크립트(MonoBehaviour 를 상속받는 클래스) |
몇 가지 예외가 있지만 class 로 만든 대부분의 타입은 참조로 동작한다. 유니티의 게임 오브젝트, 컴포넌트, C# 의 많은 타입이 클래스로 정의되어 있다. C# 내장 변수 타입과 Vector3 같은 struct 타입을 제외하면 우리가 사용할 대부분의 변수는 참조 타입으로 동작한다.
5.4 변수로 컴포넌트 사용하기
지금까지의 내용을 정리하면 참조 변수는 실체가 아니라 실체로 향하는 참조값을 저장한다.
변수의 참조값으로 실체에 접근하는 참조 타입 덕분에 변수로 씬에 있는 게임 오브젝트와 컴포넌트에 접근하고 이들을 조종할 수 있다. 변수로 게임 오브젝트를 직접 제어해본다.
5.4.1 물리 큐브 만들기
씬에 큐브를 하나 만들고, 코드로 해당 큐브가 점프하게 해본다.
1. Cube 게임 오브젝트 만들기
- 큐브 게임 오브젝트 생성(하이어라키 창에서 + > 3D Object > Cube 클릭)
- 인스펙터 창에서 Add Component > Physics > Rigidbody 클릭
리지드바디 컴포넌트가 추가되었으므로 Cube 는 중력과 물리적인 힘의 영향을 받는 게임 오브젝트가 된다.
리지드바디 컴포넌트는 물리 기능을 담당한다. 만약 Cube 에 추가된 리지드바디 컴포넌트를 직접 조종할 수 있다면 Cube 게임 오브젝트에 물리적인 힘을 추가해 원하는 곳으로 점프하게 만들 수 있다.
5.4.2 변수로 리지드바디 컴포넌트 사용하기
코드 상에서 씬에 있는 게임 오브젝트와 컴포넌트를 직접 사용하려고 할 때 참조의 중요성을 이해하게 된다. 참조 타입의 변수는 오브젝트 그 자체가 아니지만 오브젝트를 가리킬 수 있다.
즉, Rigidbody 타입의 변수는 Rigidbody 오브젝트 그 자체는 아니지만 실제 Rigidbody 오브젝트(리지드바디 컴포넌트) 를 가리킬 수 있다. 따라서 해당 변수로 실제 리지드바디 컴포넌트에 접근해 조종할 수 있다.
변수로 Cube 게임 오브젝트의 리지드바디 컴포넌트에 접근하고 힘을 추가하는 C# 스크립트를 만들어본다.
1. Jumper 스크립트 만들기
- 프로젝트 창에서 + > C# Script 클릭
- 생성된 스크립트 이름을 Jumper 로 설정 > 스크립트를 더블 클릭으로 열기
2. Jumper 스크립트 완성하기
- Jumper 스크립트의 전체 코드를 다음과 같이 수정
- 스크립트 저장
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Jumper : MonoBehaviour
{
public Rigidbody myRigidbody;
void Start()
{
myRigidbody.AddForce(0, 500, 0);
}
}
Rigidbody 타입의 변수 myRigidbody 를 선언했다. myRigidbody 는 public 으로 지정되어 클래스 외부에서 접근할 수 있다.
Start() 메서드에 게임 오브젝트가 위로 점프하도록 리지드 바디 컴포넌트에 위쪽으로 힘을 주는 처리를 구현했다.
Rigidbody 타입에 내장된 AddForce() 메서드는 x, y, z 방향으로 입력한 값만큼 힘을 준다. 여기서는 myRigidbody 의 AddForce() 메서드를 실행하고 y 방향으로 500만큼 힘을 주었다.
3. Jumper 스크립트를 Cube 게임 오브젝트에 추가하기
- 프로젝트 창의 Jumper 스크립트를 하이어라키 창의 Cube 로 드래그&드롭
그다음 플레이 버튼을 눌러 씬을 플레이하고 코드의 동작을 테스트한다. 하지만 큐브는 기대와 달리 점프하지 않고 그대로 떨어진다. 콘솔 창에는 에러 로그가 생긴다.
에러 로그를 보면 참조를 할당하지 않았다는 예외가 발생한다. 변수 myRigidbody 에 아직 참조를 할당하지 않았기 때문이다.
5.4.3 컴포넌트를 변수에 연결하기
인스펙터 창에서 Cube 게임 오브젝트의 Jumper 컴포넌트(스크립트) 를 살펴본다(게임 오브젝트에 추가된 스크립트는 해당 게임 오브젝트의 컴포넌트로 취급된다).
Jumper 컴포넌트에서 My Rigidbody 필드를 확인할 수 있다. My Rigidbody 필드는 myRigidbody 변수가 인스펙터 창에 표시된 모습이다.
스크립트의 public 변수는 인스펙터 창에서 편집 가능하도록 표시된다. 변수 myRigidbody 는 public 으로 선언되었기 때문에 My Rigidbody 라는 이름으로 표시된다.
그런데 My Rigidbody 필드에 할당된 값이 None 으로 표시되어 있다. 즉, 변수 myRigidbody 가 어떠한 오브젝트도 가리키고 있지 않다.
myRigidbody.AddForce(0, 500, 0); 은 myRigidbody 가 가리키는 Rigidbody 타입의 오브젝트로 접근하고 AddForce() 메서드를 실행한다. 하지만 myRigidbody 는 어떠한 실체도 가리키고 있지 않았기 때문에 에러가 발생한 것이다.
따라서 인스펙터 창에서 myRigidbody 에 실제 오브젝트인 리지드바디 컴포넌트를 연결한다.
1. 변수 myRigidbody 에 리지드바디 컴포넌트 할당하기
- Cube 게임 오브젝트의 Rigidbody 컴포넌트를 Jumper 컴포넌트의 My Rigidbody 필드로 드래그&드롭
- 그냥 Cube 게임 오브젝트를 드래그&드롭해도 된다(Cube 게임 오브젝트의 Rigidbody 컴포넌트를 알아서 찾아서 넣어줌: myRigidbody 변수의 타입이 Rigidbody 이므로 게임 오브젝트에 존재하는 Rigidbody 컴포넌트를 알아서 가져와서 할당해 주는 거).
이렇게 하면 myRigidbody 에 실제 리지드바디 컴포넌트가 연결된다. 즉, myRigidbody 에 Cube 게임 오브젝트의 리지드바디 컴포넌트로 향하는 참조가 할당된다.
따라서 myRigidbody 를 사용하는 것은 Cube 게임 오브젝트의 리지드바디 컴포넌트를 사용하는 것과 같은 의미가 된다.
이제 플레이 버튼을 눌러보면 정상적으로 동작하는 모습을 확인할 수 있다.
5.5 마치며
3장에서 5장까지의 모든 설명은 유니티의 컴포넌트와 게임 오브젝트를 코드 상에서 변수를 통해 조종할 수 있다는 것을 이해하기 위한 과정이었다.
유니티는 수많은 종류의 컴포넌트를 미리 만들어 제공한다. 컴포넌트는 클래스 타입이다. 클래스 타입의 변수는 참조 타입으로 동작한다.
결론적으로 씬에 존재하는 모든 실체 컴포넌트는 코드 상에서 참조 타입의 변수로 가리키고 사용할 수 있다.