Version: 2018.2
프리팹
작업 저장

런타임 시 프리팹 인스턴스화

이제 프리팹(Prefabs) 의 개념을 기초적인 수준에서 이해하게 되었을 것입니다. 프리팹은 전체 게임에서 재사용할 수 있는 미리 정의된 게임 오브젝트컴포넌트 의 컬렉션입니다. 프리팹이 무엇인지 모르는 경우 프리팹 페이지에서 더 기초적인 설명을 읽어보는 것이 좋습니다.

프리팹은 복잡한 게임 오브젝트를 런타임 시점에 인스턴스화하려는 경우에 매우 유용합니다. 프리팹을 인스턴스화하는 대신 코드를 사용하여 게임 오브젝트를 처음부터 만들 수도 있습니다. 프리팹을 인스턴스화하는 방법은 후자의 방법에 비해 장점이 많습니다.

  • 코드 한 줄에서 기능이 완전한 프리팹을 인스턴스화할 수 있습니다. 코드에서 동등한 게임 오브젝트를 만들려면 코드가 평균 다섯 줄 필요하고, 더 많이 필요할 수도 있습니다.
  • 씬과 인스펙터에서 프리팹을 빠르고 쉽게 설정하고 테스트하고 수정할 수 있습니다.
  • 프리팹을 인스턴스화하는 코드를 변경하지 않고도 인스턴스화되는 프리팹을 변경할 수 있습니다. 단순 로켓을 초강력 로켓으로 변경하면서 코드를 변경하지 않아도 됩니다.

일반적인 시나리오

프리팹의 강점을 설명하기 위해, 프리팹이 유용한 몇 가지 기본적인 상황에 대해 생각해 보겠습니다.

  1. “벽돌” 프리팹 하나를 여러 포지션에 여러 번 만들어서 벽을 세웁니다.
  2. 로켓 런처를 발사하면 비행 로켓 프리팹이 인스턴스화됩니다. 프리팹에는 메시, 리지드바디, 콜라이더, 그리고 자체적인 트레일 파티클 시스템 이 있는 자식 게임 오브젝트가 포함됩니다.
  3. 로봇이 여러 조각으로 폭발합니다. 완전하게 작동하는 로봇이 파괴되고, 파손된 로봇 프리팹으로 대체됩니다. 프리팹은 여러 부분으로 분리하여 구성되고, 모두 자체적인 리지드바디와 파티클 시스템으로 설정합니다. 이 기법을 사용하면 코드 한 줄만으로 오브젝트 하나를 프리팹으로 대체하여 로봇을 여러 조각으로 폭발시킬 수 있습니다.

벽 생성

아래에서는 프리팹을 사용하는 방식의 장점을 코드로 오브젝트를 만드는 방식과 비교하여 설명합니다.

먼저 코드를 사용하여 벽돌 벽을 만듭니다.

public class Instantiation : MonoBehaviour 
{
    void Start()
    {
        for (int y = 0; y < 5; y++) 
        {
            for (int x = 0; x < 5; x++) 
            {
                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.AddComponent<Rigidbody>();
                cube.transform.position = new Vector3(x, y, 0);
            }
        }
    }
}
  • 위 스크립트를 사용하기 위해, 간단히 스크립트를 저장하고 빈 게임 오브젝트로 드래그합니다.
  • 게임 오브젝트(GameObject)->빈 오브젝트 생성(Create Empty) 을 사용하여 빈 게임 오브젝트를 만듭니다.

코드를 실행할 경우, 플레이 모드에 진입하면 전체 벽돌 벽이 만들어진 것이 보입니다. 코드에는 각 개별 벽돌의 기능과 관련된 CreatePrimitive 줄과 AddComponent 줄이 있습니다. 지금도 아주 나쁘진 않지만, 각 벽돌에는 텍스처가 없습니다. 벽돌에 대해 수행하려는 텍스처, 마찰 또는 리지드바디 mass 변경과 같은 모든 추가 액션마다 줄이 하나씩 추가됩니다.

프리팹을 만들고 모든 설정을 미리 수행하는 경우, 각 벽돌을 만들고 설정하는 데 코드를 한 줄만 사용합니다. 따라서 변화를 주고 싶다고 결정할 때 많은 코드를 관리하거나 변경하지 않아도 됩니다. 프리팹을 사용할 경우 변경 사항을 적용하고 플레이하기만 하면 되고, 코드를 변경할 필요가 없습니다.

개별 벽돌에 각각 프리팹을 사용하는 경우, 벽을 만드는 데 필요한 코드는 다음과 같습니다.

//Instantiate accepts any component type, because it instantiates the GameObject 

public Transform brick;

void Start() 
{
    for (int y = 0; y < 5; y++)
    {
        for (int x = 0; x < 5; x++) 
        {
            Instantiate(brick, new Vector3(x, y, 0), Quaternion.identity);
        }
    }
}

코드는 매우 클린할 뿐만 아니라 재사용하기도 아주 쉽습니다. 큐브를 인스턴스화하거나 리지드바디를 포함해야 한다고 지정하는 내용이 없습니다. 이런 내용은 모두 프리팹에서 정의되고, 에디터에서 빨리 작성할 수 있습니다.

이제 프리팹만 만들면 됩니다. 작업은 에디터에서 다음과 같이 수행합니다.

  1. 게임 오브젝트(GameObject) > 3D 오브젝트(3D Object) > 큐브(Cube) 를 선택합니다.
  2. 컴포넌트(Component) > 물리(Physics) > 리지드바디(Rigidbody) 를 선택합니다.
  3. 에셋(Assets) > 생성(Create) > 프리팹(Prefab) 을 선택합니다.
  4. 프로젝트 뷰(Project View) 에서 새 프리팹의 이름을 “Brick”으로 변경합니다.
  5. 만든 큐브를 계층 구조(Hierarchy) 에서 프로젝트 뷰 의 “Brick” 프리팹으로 드래그합니다.
  6. 프리팹을 만든 후에는 큐브를 계층 구조에서 안전하게 삭제할 수 있습니다(Windows에서는 Delete, Mac에서는 Command-Backspace).

이제 벽돌 프리팹을 만들었으므로 스크립트의 brick 변수에 연결해야 합니다. 스크립트가 포함된 빈 게임 오브젝트를 선택하면 Brick 변수가 인스펙터에 보입니다.

이제 “Brick” 프리팹을 프로젝트 뷰에서 인스펙터의 brick 변수로 드래그합니다. Play를 누르면 프리팹을 사용하여 만든 벽이 보입니다.

이러한 작업 방식은 Unity에서 계속 반복적으로 사용합니다. 처음에는 코드로 큐브를 만드는 스크립트가 두 줄만 더 길기 때문에, 프리팹을 사용하는 것이 왜 훨씬 나은 것인지 잘 모를 수도 있습니다.

하지만 이제는 프리팹을 사용하기 때문에 프리팹을 몇 초만에 조정할 수 있습니다. 많은 양의 모든 인스턴스를 변경하고 싶습니까? 프리팹의 리지드바디를 한 번만 조정해야 합니다. 모든 인스턴스에 다른 머티리얼 을 적용하고 싶습니까? 머티리얼을 프리팹으로 한 번만 드래그해야 합니다. 마찰을 변경하고 싶습니까? 프리팹의 콜라이더에서 다른 물리 머티리얼 을 사용해야 합니다. 파티클 시스템을 상자에 추가하고 싶습니까? 프리팹에 자식을 한 번만 추가해야 합니다.

로켓 및 폭발 인스턴스화

시나리오에서는 프리팹이 다음과 같이 사용됩니다.

  1. 사용자가 발사를 누르면 로켓 런처가 로켓 프리팹을 인스턴스화합니다. 프리팹에는 메시, 리지드바디, 콜라이더, 그리고 트레일 파티클 시스템이 있는 자식 게임 오브젝트가 포함됩니다.
  2. 로켓이 충돌하여 폭발 프리팹을 인스턴스화합니다. 폭발 프리팹에는 파티클 시스템, 시간에 따라 페이드 아웃되는 광원, 그리고 주변 게임 오브젝트에 손상을 적용하는 스크립트가 포함됩니다.

전체 로켓 게임 오브젝트를 코드만 사용하여 만들고 컴포넌트를 수동으로 추가하고 프로퍼티를 설정할 수 있지만, 프리팹을 인스턴스화하면 훨씬 더 간편합니다. 로켓의 프리팹이 아무리 복잡해도 로켓을 코드 한 줄만으로 인스턴스화할 수 있습니다. 프리팹을 인스턴스화한 후 인스턴스화된 오브젝트의 프로퍼티를 수정할 수도 있습니다. 예를 들어 로켓의 리지드바디 속도를 설정할 수 있습니다.

프리팹은 사용하기 쉬울 뿐만 아니라, 나중에 업데이트할 수도 있습니다. 따라서 로켓을 빌드하는 경우 파티클 트레일을 로켓에 곧바로 추가하지 않고 나중에 추가해도 됩니다. 프리팹에 트레일을 자식 게임 오브젝트로 추가하는 즉시 모든 인스턴스화된 로켓에 파티클 트레일이 추가됩니다. 마지막으로, 인스펙터에서 로켓 프리팹의 프로퍼티를 빨리 미세 조정함으로써 훨씬 더 쉽게 세부적인 튜닝을 할 수 있습니다.

다음은 Instantiate() 함수를 사용하여 로켓을 발사하는 스크립트의 예입니다.

// Require the rocket to be a rigidbody.
// This way we the user can not assign a prefab without rigidbody
public Rigidbody rocket;
public float speed = 10f;

void FireRocket () 
{
    Rigidbody rocketClone = (Rigidbody) Instantiate(rocket, transform.position, transform.rotation);
    rocketClone.velocity = transform.forward * speed;
    
    // You can also access other components / scripts of the clone
    rocketClone.GetComponent<MyRocketScript>().DoSomething();
}

// Calls the fire method when holding down ctrl or mouse
void Update () 
{
    if (Input.GetButtonDown("Fire1"))
    {
        FireRocket();
    }
}


래그돌 또는 잔해로 캐릭터 대체

전체 리그가 적용된 적군 캐릭터가 죽는 경우를 가정해 보겠습니다. 이 경우 단순히 캐릭터에 대해 사망 애니메이션을 재생하고 일반적인 경우에 적군 로직을 처리하는 모든 스크립트를 비활성화할 수 있습니다. 그러면 여러 스크립트를 제거하고 죽은 적을 더 이상 아무도 공격하지 않도록 하는 커스텀 로직을 몇 개 추가하는 작업과 기타 정리 작업을 직접 처리해야 할 수 있습니다.

하지만 캐릭터 전체를 즉시 삭제하고 인스턴스화된 잔해 프리팹으로 대체하는 방법이 훨씬 더 효과적입니다. 융통성이 많아지기 때문입니다. 죽은 캐릭터에 다른 머티리얼을 사용하거나, 완전히 다른 스크립트를 연결하거나, 여러 조각으로 부서진 오브젝트가 포함된 프리팹을 스폰하여 박살난 적을 시뮬레이션하거나, 캐릭터의 버전이 포함된 프리팹을 간단히 인스턴스화할 수 있습니다.

위 작업은 모두 Instantiate() 를 한 번만 호출하여 수행할 수 있습니다. 적절한 프리팹에 연결하기만 하면 됩니다!

Instantiate() 하는 잔해를 원래 캐릭터와 완전히 다른 오브젝트로 구성할 수 있음을 기억하는 것이 중요합니다. 예를 들어, 비행기가 있는 경우 두 가지 버전으로 모델링할 수 있습니다. 하나는 메시 렌더러 와 비행기 물리에 대한 스크립트가 포함된 하나의 게임 오브젝트로 구성된 비행기입니다. 이 모델을 단 하나의 게임 오브젝트로 계속 유지하면 게임이 더 빨리 실행됩니다. 왜냐하면 삼각형이 더 적은 모델을 만들 수 있고, 모델은 더 적은 오브젝트로 구성되므로 작은 파트를 많이 사용할 때보다 더 빨리 렌더링될 것이기 때문입니다. 또한 멀쩡하게 날아다니는 비행기에는 여러 파트가 있을 이유도 없습니다.

부서진 비행기 프리팹을 빌드하는 일반적인 절차는 다음과 같습니다.

  1. 즐겨 사용하는 모델러에서 여러 다양한 파트를 사용해 비행기를 모델링합니다.
  2. 비어 있는 씬을 만듭니다.
  3. 모델을 빈 씬으로 드래그합니다.
  4. 모든 파트를 선택하고 컴포넌트(Component)->물리(Physics)->리지드바디(Rigidbody) 를 선택하여 모든 파트에 리지드바디를 추가합니다.
  5. 모든 파트를 선택하고 컴포넌트(Component)->물리(Physics)->박스 콜라이더(Box Collider) 를 선택하여 모든 파트에 박스 콜라이더를 추가합니다.
  6. 연기 같은 파티클 시스템을 각 파트에 자식 게임 오브젝트로 추가하여 특수 효과를 더합니다.
  7. 이제 폭발된 파트가 여러 개 포함된 비행기가 만들어졌습니다. 비행기는 물리에 의해 땅에 떨어지고, 추가된 파티클 시스템으로 인해 파티클 트레일을 만듭니다. Play를 눌러 모델이 어떻게 반응하는지 미리 보고 필요한 조정을 가할 수 있습니다.
  8. 에셋(Assets)->프리팹 생성(Create Prefab) 을 선택합니다.
  9. 모든 비행기 파트가 포함된 루트 게임 오브젝트를 프리팹으로 드래그합니다.

다음은 위 절차가 코드에서 모델링되는 방법의 예입니다.

public GameObject wreck;

// As an example, we turn the game object into a wreck after 3 seconds automatically
IEnumerator Start() 
{
    yield return new WaitForSeconds(3);
    KillSelf();
}

// Calls the fire method when holding down ctrl or mouse
void KillSelf () 
{
    // Instantiate the wreck game object at the same position we are at
    GameObject wreckClone = (GameObject) Instantiate(wreck, transform.position, transform.rotation);
    
    // Sometimes we need to carry over some variables from this object
    // to the wreck
    wreckClone.GetComponent<MyScript>().someVariable = GetComponent<MyScript>().someVariable;
    
    // Kill ourselves
    Destroy(gameObject);
}

여러 오브젝트를 특정 패턴으로 배치

여러 오브젝트를 격자형 또는 원형 패턴으로 배치하려는 경우를 가정해 보겠습니다. 과거에는 작업을 다음 중 한 가지 방법으로 수행했습니다.

  1. 코드로 전체 오브젝트를 빌드하는 방법. 이 방법을 사용하면 일이 너무 많아집니다! 스크립트에서 값을 입력하려면 느리고 직관적이지 않기 때문에 공들일 가치가 없습니다.
  2. 전체 리그가 적용된 오브젝트를 만들고 복제하여 씬에 여러 번 배치하는 방법. 이 방법도 일이 많아지고, 오브젝트를 격자에 정확히 배치하기가 어렵습니다.

따라서 Instantiate() 를 프리팹 대신 사용합니다! 프리팹이 이런 시나리오에서 매우 유용한 이유를 이제 알고 있을 것입니다. 다음은 이런 시나리오에 필요한 코드입니다.

// Instantiates a prefab in a circle

public GameObject prefab;
public int numberOfObjects = 20;
public float radius = 5f;

void Start() 
{
    for (int i = 0; i < numberOfObjects; i++)
    {
        float angle = i * Mathf.PI * 2 / numberOfObjects;
        Vector3 pos = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius;
        Instantiate(prefab, pos, Quaternion.identity);
    }
}

// Instantiates a prefab in a grid

public GameObject prefab;
public float gridX = 5f;
public float gridY = 5f;
public float spacing = 2f;

void Start()
{
    for (int y = 0; y < gridY; y++) 
    {
        for (int x = 0; x < gridX; x++)
        {
            Vector3 pos = new Vector3(x, 0, y) * spacing;
            Instantiate(prefab, pos, Quaternion.identity);
        }
    }
}


프리팹
작업 저장