목차
실시간으로 프리팹을 인스턴스화 하기 (Instantiating Prefabs at runtime)
이 시점에서 사용자는 프리팹 Prefabs
에 관한 기초적인 개념을 이해하고 있어야 한다. 프리팹은 사용자의게임을 통털어 재사용이 가능한 미리 설정된 게임오브젝트 GameObjects
들과 컴포넌트 Components
들의 집합이다. 프리팹이 무엇인지 모를 경우, 기초 지식을 위해 프리팹(Prefab) 페이지를 읽어보시길 권유한다.
프리팹은 복잡한 게임오브젝트를 실행 도중에 인스턴스화(instantiate) 시키길 원하는 경우 매우 유용하다. 프리팹을 인스턴스화 하는 것 외에 다른 방안은 코드를 사용해 처음부터 여러 게임오브젝트를 만드는 것이다. 프리팹 인스턴스화는 이런 대안에 비하여 더 많은 장점들을 가지고 있으며, 다음과 같다.
- 사용자는 한 줄의 코드로 완전한 기능을 가진 프리팹의 인스턴스화를 할 수 있다. 똑같은 게임오브젝트를 코드로 처음부터 만드는 것은 평균 5줄 혹은 더 이상이 필요하게 된다.
- 사용자는 씬 (Scene)과 인스펙터 (Inspector) 에서 프리팹을 더 빠르고 쉽게 설정하고 테스트하고 수정할 수 있다.
- 사용자는 인스턴스 되어진 프리팹을 그것을 인스턴스화하는 코드를 변경하지 않고도 해당 프리팹을 변경할 수 있다. 가령 코드 변경없이도 일반 로켓탄을 슈퍼 충전된 로켓으로 변경할 수 있다.
일반적인 시나리오들 Common Scenarios
프리팹의 강점을 설명하기 위해서, 프리팹이 유용하게 쓰여지는 기본적인 상황들을 고려해 보겠다:
- 벽돌 프리팹을 각각 다른 위치로 여러 차례 생성하여 벽을 만들수 있다.
- 로켓발사기는 로켓을 쏘았을 때 날으는 로켓탄 프리팹을 인스턴스화 한다. 그 프리팹은 메쉬,
강체Rigidbody
,충돌체 Collider
, 그리고, 자신의 흔적 trail파티클 시스템 Particle System
을 가진 자식 child 게임오브젝트를 포함한다. - 많은 조각들로 폭발하는 로봇. 완전하게 작동하던 로봇은 부서진 후 파괴된_로봇_프리팹 으로 교체된다. 이 프리팹들은 각각의 강체 Rigidbodies 들과 파티클 시스템들이 설정된 여러 개의 파트들로 나눠진 로봇으로 구성될 것이다. 이 테크닉은 로봇을 많은 조각으로 폭발시키며 한 개의 객체를 한 개의 프리팹으로 교체하는 것을 단 한 줄의 코드로 할 수 있게 해준다.
벽 짓기 (Building a wall)
아래의 설명은 프리팹을 사용하는 것과 코드로 객체를 생성하는 것의 비교를 통하여 프리팹 사용의 장점을 보여줄 것이다.
우선, 코드로부터 벽돌을 만들어 본다:
function Start () { for (var y = 0; y < 5; y++) { for (var x = 0; x < 5; x++) { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.AddComponent(Rigidbody); cube.transform.position = Vector3 (x, y, 0); } } }
- 위의 스크립트를 사용하기 위해서는, 사용자는 단순히 스크립트를 저장하고 비어있는 게임오브젝트로 스크립트를 드래그 한다.
- GameObject→Create Empty로 빈 게임오브젝트를 생성한다.
만약 위의 코드를 실행하면, 사용자가 플레이 모드 Play Mode 로 들어갈 때 벽돌 벽 전체를 보게 될 것이다. 거기에는 각각의 벽돌의 기능에 관련된 두 개의 코드 줄이 있다. 이들은 CreatePrimitive()와 AddComponent()이다. 지금 이것도 나쁘지는 않지만, 벽돌들은 텍스쳐를 가지고 있지 않는다. 텍스쳐 texture, 마찰력 friction, 강체의 질량 Rigidbody mass을 바꾸는 것처럼 벽돌에 행해지는 각각의 추가적인 행동들은 모두 추가적인 줄을 필요로 하게 된다.
만약 사용자가 프리팹을 생성하여, 모든 설정을 사전에 해놓는다면, 각 벽돌의 설정과 생성을 수행하기 위해서 한 줄의 코드만 사용하게 된다. 이것은 사용자가 무언가 수정을 해야한다고 느꼈을 때 많은 코드들을 관리하고 수정하는 수고를 덜어 줄 것이다. 프리팹을 사용하면 사용자는 단지 수정한 후 플레이하면 된다. 코드의 수정이 따로 필요 없다.
개개의 벽돌을 위해 프리팹을 사용할 것이라면, 다음이 사용자가 벽을 생성하기 위해 필요한 코드이다.
var cube : Transform; function Start () { for (var y = 0; y < 5; y++) { for (var x = 0; x < 5; x++) { var cube = Instantiate(cube, Vector3 (x, y, 0), Quaternion.identity); } } }
위의 코드는 아주 깔끔할 뿐만 아니라 재사용이 가능하다. 여기에는 큐브를 인스턴스화한다 (instantiating) 라던지 큐브가 강체 (rigidbody) 를 포함 해야 한다 라는 말이 필요 없다. 이 모든 것은 프리팹에서 정의되고 에디터에서 빠르게 생성될 수 있다.
이제 사용자는 에디터 안에서 프리팹을 만들기만 하면 된다. 다음과 같은 방법으로 프리팹을 생성한다:
- GameObject→Create Other→Cube를 선택한다.
- Component→Physics→Rigidbody를 선택한다.
- Assets→Create Prefab를 선택한다.
프로젝트 뷰 Project View
에서, 새로운 프리팹의 이름을 "Brick"으로 바꿉니다.계층 Hierarchy
에서 생성한 큐브를 드래그하여프로젝트 뷰
의 “Brick” 프리팹에 놓는다.
생성된 프리팹으로 사용자는 계층에서 안전하게 큐브를 삭제할 수 있다 (윈도우에서는 Delete, 맥에서는 Command-Backspace)
이제 벽돌 프리팹이 생성되었다. 이제 사용자는 이것을 스크립트의 cube 변수에 붙여야 attach 한다. 스크립트를 포함하는 빈 게임오브젝트를 선택하라. 인스펙터에서 새로운 변수인 "cube"가 나타난 것을 보게 될 것이다.
이 변수는 어떤 게임오브젝트나 프리팹도 받아들일 수 있다
이제 "Brick" 프리팹을 프로젝트 뷰에서 인스펙터의 cube 변수위로 드래그 한다. 플레이 Play 를 누르면 프리팹을 사용하는 벽을 보게 될 것이다.
이것은 유니티에서 굉장히 많이 사용되는 작업방식 패턴이다. 사용자는 처음에 코드로부터 큐브를 생성하는 스크립트가 단 2줄이 더 긴데 왜 이 방식이 훨씬 더 낫다는 건지 의아해 할 수도 있다.
그러나 지금은 프리팹 사용으로 인해 단 몇 초 내에 프리팹을 수정할 수 있다. 모든 인스턴스의 질량 수정이 필요합니까? 그렇다면 해당 프리팹 안의 강체를 수정하라. 모든 인스턴스들에 각각 다른 재질 Material
을 사용하기 원합니까? 그렇다면 해당 프리팹으로 재질을 한번만 드래그 해놓으면 된다. 마찰의 수정을 원하십니까? 그렇다면 프리팹의 충돌체에 다른 피직 재질 Physic Material
을 사용하십시오. 여기 모든 박스에 파티클 시스템을 추가 하시겠습니까? 그렇다면 프리팹에 자식 child 을 한 번만 추가하면 된다.
로켓과 폭발의 인스턴스화 (Instantiating rockets & explosions)
다음은 프리팹이 어떻게 이 시나리오에 들어맞는지에 대한 설명이다:
- 로켓 발사기는 사용자가 로켓을 발사할 때, 로켓 프리팹을 인스턴스화 instantiate 한다. 이 프리팹은 메쉬, 강체, 충돌체, 그리고 흔적 파티클 시스템 trail particle system 을 포함하는 자식 게임오브젝트를 가지고 있다.
- 로켓은 폭발 프리팹에 영향을 미치고 인스턴스화 시킵니다. 폭발 프리팹은 시간이 지나며 점점 희미해지는 빛의 파티클 시스템과 주위의 게임오브젝트들에 데미지를 가하는 스크립트를 지니고 있다.
로켓 게임오브젝트를 코드로 처음부터 작성하여 수동으로 컴포넌트 추가하고 속성 설정하는 것도 가능하지만 프리팹을 인스턴스화 하는 것이 훨씬 더 쉽다. 로켓의 프리팹이 얼마나 복잡하던지 간에 로켓을 한 줄의 코드로 로켓을 인스턴스화 할 수 있다. 프리팹을 인스턴스화 한 후에 사용자는 인스턴스화한 객체의 어떤 속성도 변경이 가능한다 (예: 로켓 강체의 속도 설정).
사용이 쉬운 것 이외에도 프리팹은 나중에 업데이트가 가능하다는 장점이 있다. 가령 사용자가 로켓을 만든다면, 로켓에 당장 흔적 파티클을 추가할 필요가 없다. 나중에 업데이트 하는 것이 가능하기 때문이다. 나중에 사용자가 자식 게임오브젝트로서 흔적 trail 을 프리팹에 추가하면, 사용자의 인스턴스 instance 화 된 모든 로켓들은 흔적 파티클들을 가지게 될 것이다. 그리고 마지막으로 사용자는 인스펙터의 로켓 프리팹의 속성들을 빨리 수정해서 게임을 정교하게 가다듬는 것을 더 쉽게 만들어 준다.
이 스크립트는 Instantiate() 함수를 사용하여 어떻게 로켓을 발사하는지 보여준다.
// 로켓이 강체가 되어야 한다. // 그래야 사용자가 강체없이 프리팹 할당을 못한다. var rocket : Rigidbody; var speed = 10.0; function FireRocket () { var rocketClone : Rigidbody = Instantiate(rocket, transform.position, transform.rotation); rocketClone.velocity = transform.forward * speed; // 사용자는 복제품의 컴포넌트나 스크립트에 접근할 수 있다. rocketClone.GetComponent(MyRocketScript).DoSomething(); } //CTRL나 마우스를 누르고 있을때 발사 메쏘드를 호출한다. function Update () { if (Input.GetButtonDown("Fire1")) { FireRocket(); } }
캐릭터를 랙돌 (ragdoll) 이나 망가진 것으로 교체하기 (Replacing a character with a ragdoll or wreck)
만약 사용자가 완전히 리깅된 적 캐릭터를 가지고 있는데 그가 죽었다고 가정해 보라. 이 경우 사용자는 단순히 캐릭터에 죽음 애니메이션이 작동하게 하고 그 적 캐릭터의 로직을 다루는 모든 스크립트를 사용불가 disable 상태로 만들수도 있다. 이때 사용자는 몇몇 스크립트들을 제거하고, 다른 아무도 죽은 적을 더 이상 공격하지 않는 몇몇 커스텀 로직을 더해야 하는 등등의 여러가지 청소 작업에 신경 써야 할 것이다.
하지만 이보다 더 좋은 접근법은 캐릭터를 통체로 즉시 삭제하고, 그 캐릭터를 인스턴스화된 망가진 프리팹 인스턴스로 교체하는 것이다. 이는 사용자에게 더 많은 유연성을 제공해 준다. 죽은 캐릭터를 위해서 다른 재질 을 사용할 수도 있고, 완전히 다른 스크립트들을 붙일 수도 있을 것이며, 산산조각난 적을 연출하기 위해 여러 개로 쪼개진 그 객체를 포함하는 프리팹을 만들거나 혹은 단순히 그 캐릭터의 버전을 포함하는 프리팹을 인스턴스 시킬 수도 있을 것이다.
이 어떤 선택사항들도 Instantiate()로 한번의 호출로 가능하며, 사용자는 간단히 그것을 맞는 프리팹으로 연결만하면 완료가 되는 것이다!
이 중 꼭 기억해야 할 중요한 파트은 사용자가 Instantiate() 명령어를 사용해 만든 부서진 캐릭터를 원래와 완전히 다른 객체들로 구성할 수 있다는 점이다. 예를 들어 사용자가 비행기가 있을시 사용자는 두 가지 버전으로 그것을 모델링 할 것이다. 비행기가 메쉬 렌더러 Mesh Renderer
와 비행기 피직스 (물리) physics 를 위한 스크립트들을 가진 하나의 게임오브젝트로 구성된 모델의 경우를 봅니다. 이 모델을 하나의 게임오브젝트에 간직함으로 인하여, 사용자는 더 적은 수의 삼각형들로 구성된 모델로 만들 수 있으므로 사용자의 게임 속도는 더욱 더 향상될 것이다. 그리고 더 적은 수의 객체들로 구성됨으로 인해, 여러개의 작은 파트들을 많이 사용하는 경우에 비해 렌더 속도도 더 빠를 것이다. 또한 비행기에 문제없이 날고 있다면, 굳이 분리된 파트들로 구성된 모델을 가지고 있을 이유가 없다.
망가진 비행기 프리팹을 만들기 위한 일반적인 단계는 다음과 같다:
- 사용자가 선호하는 3D 모델링 프로그램으로 많은 파트들로 구성된 비행기를 모델링한다.
- 비어있는 씬 empty Scene 을 생성한다.
- 모델을 빈 씬으로 드래그 한다.
- 모든 파트들을 선택하고 Component→Physics→Rigidbody 메뉴를 선택해서 강체 Rigidbodies 를 모든 파트들에 더한다.
- 모든 파트들을 선택하고 Component→Physics→Box Collider를 선택해서 박스 충돌체를 모든 파트들에 더한다.
- 추가적인 특수 효과를 위해서 각 파트들에 연기와 같은 파티클 시스템을 자식 child 게임오브젝트로써 더한다.
- 이제 사용자는 복수개의 폭발된 파트들로 구성된 비행기를 가지고 있고 이것들은 피직스 physic 에 의해 땅으로 떨어지고 부착된 파티클 시스템으로 인하여 흔적 파티클들을 만들어 낼 것이다. 재생 play 을 눌러 모델이 어떤 식으로 반응하는지 살펴본 후 필요한 변경을 가해 줘라.
- Assets→Create Prefab를 선택한다.
- 비행기 파트들을 모두 포함한 루트 root 게임오브젝트를 프리팹 안으로 드래그 한다.
var wreck : GameObject; //한 예로 우리는 3초 후에 자동적으로 게임오브젝트가 망가진 것으로 바뀌도록 해봅니다. function Start () { yield WaitForSeconds(3); KillSelf(); } // CTRL이나 마우스를 누를때 발사 메쏘드를 호출한다. function KillSelf () { // 우리가 있는 같은 위치로 부서진 게임오브젝트의 인스턴스를 생성한다. var wreckClone = Instantiate(wreck, transform.position, transform.rotation); // 때로 우리는 이 객체로부터 몇몇 변수들을 가져올 필요가 있다. // 부서진 게임오브젝트로 말이다. wreckClone.GetComponent(MyScript).someVariable = GetComponent(MyScript).someVariable; // 우리를 파괴한다. Destroy(gameObject); }
일인칭 슈팅 게임 튜토리얼(강좌)은 캐릭터를 랙돌 ragdoll 버젼으로 어떻게 바꾸고 애니메이션의 마지막 상태로 팔다리를 동기화하여 맞추는지 설명한다. 이 강좌는 Tutorials 페이지에서 찾으실 수 있다.
여러 개의 객체들을 특정한 패턴으로 배치하기 Placing a bunch of objects in a specific pattern
사용자가 다양한 오브젝트들을 그리드나 원형의 패턴에 놓고 싶어한다고 합시다. 예전에는 이것들을 다음과 같은 방법들로 실현했다:
- 완전히 코드만으로 객체를 만듭니다. 이는 굉장히 지루하다. 한 스크립트에 값들을 넣는 것은 작업속도가 느려질 뿐만 아니라 직관적이지도 않아 힘만 들게 된다.
- 완전히 리깅된 오브젝트를 만들고, 그것을 복제해서 씬 Scene 의 여러 곳에 배치하는 방법이다. 이 또한 지루한 작업이며, 객체들을 그리드 (좌표) grid 에 정확하게 놓는 것은 어렵다.
그러므로, 프리팹과 Instantiate() 를 이용하라! 이러한 시나리오들에서 왜 프리팹들이 유용한지 이제는 이해가 될 것이다. 이 시나리오들을 위해 필요한 코드는 다음과 같다:
// 원 안에서 프리팹 인스턴스들 만들기 var prefab : GameObject; var numberOfObjects = 20; var radius = 5; function Start () { for (var i = 0; i < numberOfObjects; i++) { var angle = i * Mathf.PI * 2 / numberOfObjects; var pos = Vector3 (Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius; Instantiate(prefab, pos, Quaternion.identity); } }
//그리드 안에 프리팹 인스턴스들 만들기 var prefab : GameObject; var gridX = 5; var gridY = 5; var spacing = 2.0; function Start () { for (var y = 0; y < gridY; y++) { for (var x=0;x<gridX;x++) { var pos = Vector3 (x, 0, y) * spacing; Instantiate(prefab, pos, Quaternion.identity); } } }
- 출처: 유니티코리아위키 (CC BY-NC-SA 2.0)