Version: 2017.3
Префабы (Prefabs)
Saving Your Work

Создание экземпляров префабов во время работы приложения

К данному моменту вы уже должны понимать основы концепта префабов (Prefabs). Префабы - это набор заранее установленных игровых объектов GameObjects и компонентов Components, которые используются более одного раза за всю игру. Если вы не знаете, что такое префаб, то мы рекомендуем вам для начала ознакомиться со страницей Prefabs, для базового объяснения префабов.

Префабы приходятся очень кстати, когда вы хотите создать экземпляры сложных игровых объектов во время игрового процесса. Альтернативой создания экземпляров префабов является создание GameObject с нуля используя код. Создание экземпляра префаба имеет много преимуществ над альтернативным подходом:

  • Вы можете создать экземпляр префаба с полным функционалом при помощи одной строчки кода. Создание эквивалентного GameObject из кода в среднем занимает 5 строк кода, а обычно всё же больше.
  • Вы можете легко и быстро настраивать, тестировать и модифицировать префабы в сцене и инспекторе.
  • Вы можете изменять префаб, экземпляр которого будет создан, без изменения кода, отвечающего за призыв. Простая ракета может быть превращена в супер-заряженную ракету без изменения кода.

Общие сценарии

Чтобы показать мощь префабов, давайте рассмотрим некоторые основные ситуации, где они могут пригодиться:

  1. Построение стены из одного префаба “кирпича” путём создания его несколько раз в разных позициях.
  2. Ракетная установка создаёт экземпляр префаба ракеты, когда пользователь жмёт кнопку атаки. Префаб содержит меш, Rigidbody, коллайдер и дочерний GameObject, который содержит систему частиц для следа.
  3. Робот разлетается на несколько кусков. Полный, функциональный робот уничтожается и заменяется префабом сломанного робота. Этот префаб будет состоять из робота, разделённого на много частей, содержащих Rigidbody и системы частиц. Эта техника позволяет вам взорвать робота на мелкие кусочки просто одной строкой кода, заменяющей один объект на префаб.

Построение стены

Это объяснение иллюстрирует преимущества использования префабов над созданием объектов из кода.

В первую очередь, давайте построим стену из кода:

// JavaScript
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);
        }
    }
}


// C#
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.
  • Создайте пустой GameObject при помощи GameObject->Create Empty.

Если вы исполните этот код, вы увидите целую кирпичную стену, создаваемую при входе в режим Play Mode. На отдельный кирпич приходится по 2 строки, отвечающие за функциональность: строки CreatePrimitive() и AddComponent(). Пока что не так и плохо, но все кирпичи не имеют текстур. Каждое дополнительное действие, которое мы хотим сделать с кирпичом, вроде изменения текстуры, трения или массы Rigidbody, требует дополнительную строку.

Если вы создадите префаб и выполните все установки вручную, то вам понадобится использовать лишь по одной строке на создание и установку каждого кирпича. Это освобождает вас от поддержания и изменения тонн кода, когда вы хотите внести изменения. При использовании префаба вы просто вносите изменения в него и жмёте Play. Совсем нет надобности изменять код.

Если вы используете префаб для каждого кирпича, то вот код, который вам понадобится для создания стены.

// JavaScript

//Instantiate accepts any component type, because it instantiates the GameObject attached to the component. Therefore, Transform is accepted here. 

var brick : Transform;
function Start () {
    for (var y = 0; y < 5; y++) {
        for (var x = 0; x < 5; x++) {
            Instantiate(brick, Vector3 (x, y, 0), Quaternion.identity);
        }
    }
}


// C#
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);
        }
    }
}

Он не только очень чистый, но им можно пользоваться много раз. В нём не упоминается ни создание куба, ни то, что он должен содержать Rigidbody. Всё это задано в префабе, который может быть быстро создан в редакторе

Теперь нам остаётся лишь создать префаб, который мы делаем в редакторе. Вот так:

  1. Выбрать GameObject->Create Other->Cube
  2. Выбрать Component->Physics->Rigidbody
  3. Выбрать Assets->Create->Prefab
  4. В окне Project View измените имя вашего нового префаба на “Brick”
  5. Перетащите созданный куб в иерархию поверх префаба “Brick” в окно Project View
  6. Теперь, когда префаб создан, вы можете безопасно удалить куб из иерархии (Delete в Windows, Command-Backspace на Mac)

We’ve created our Brick Prefab, so now we have to attach it to the brick variable in our script. When you select the empty GameObject that contains the script, the Brick variable will be visible in the inspector.

Теперь перетащите префаб “Brick” из окна Project View на переменную brick в инспекторе. Нажмите Play и вы увидите стену построенную из префабов.

Этот шаблон рабочего процесса можно использовать и использовать в Unity. В начале вы, возможно, будете интересоваться, почему же это лучше, чем создание куба из кода, который длиннее лишь на две строки.

Но т.к. вы сейчас используете префаб, вы можете настроить Prefab за секунды. Хотите внести изменения для всей кучи образцов? Настройте Rigidbody в префабе всего один раз. Хотите использовать иной Material для всех образцов? Перетащите материал на префаб всего один раз. Хотите изменить трение? Используйте другой физический материал (Physic Material) в коллайдере префаба. Хотите добавить системы частиц ко всем кирпичам? Добавьте дочернюю систему к префабу всего один раз.

Создание экземпляров ракет и взрывов

Вот как префабы подходят для данного сценария:

  1. Ракетная установка создаёт экземпляр префаба ракеты, когда пользователь жмёт кнопку атаки. Префаб содержит меш, Rigidbody, коллайдер и дочерний GameObject, который содержит систему частиц для следа.
  2. Ракета врезается и создаёт экземпляр префаба взрыва. Префаб взрыва содержит систему частиц, источник освещения, который угасает со временем, и скрипт, который применяет урон окружающим объектам.

В то время как можно собрать объект ракеты полностью из кода, вручную добавляя компоненты и устанавливая свойства, было бы гораздо проще просто создать экземпляр префаба. Вы можете создать экземпляр ракеты просто одной строкой кода, не важно, насколько сложным будет префаб ракеты. После создания экземпляра префаба, вы также можете изменить любые свойства созданного экземпляра объекта (например, вы можете установить скорость Rigidbody ракеты).

Кроме того, что префабы проще в использовании, вы можете позже обновить префаб . То есть если вы собираете ракету, вам нет надобности сразу же добавлять к ней след из частиц. Вы можете сделать это позже. Как только вы добавите след в виде дочернего объекта к префабу, все созданные экземпляры ракет получат след из частиц. И последнее, вы можете быстро настраивать свойства префаба ракеты в инспекторе, упрощая настройку желаемого результата для вашей игры.

Этот скрипт покажет, как запустить ракету используя функцию Instantiate().

// JavaScript

// Require the rocket to be a rigidbody.
// This way we the user can not assign a prefab without rigidbody
var rocket : Rigidbody;
var speed = 10.0;

function FireRocket () {
    var 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
function Update () {
    if (Input.GetButtonDown("Fire1")) {
        FireRocket();
    }
}


// C#

// 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();
    }
}


Замена персонажа на Ragdoll или обломки

Допустим у вас есть вражеский персонаж с ригом и он умирает. Вы можете просто проиграть анимацию смерти и отключить все скрипты, которые обычно отвечают за логику врага. Вам, скорее всего, придётся позаботиться об удалении нескольких скриптов, добавлении некоторой дополнительной логики, чтобы убедиться, что никто не будет атаковать уже мёртвого врага, и о других задачах очистки.

Гораздо лучше будет подход, включающий мгновенное удаление всего персонажа, и замена его созданным экземпляром префаба обломков. Это даёт вам больше возможностей. Вы можете использовать другой материал для мёртвого персонажа, присоединить совершенно другие скрипты, создать экземпляр префаба содержащий объект сломанный на много кусков, чтобы симулировать разбившегося врага, или просто создать экземпляр префаба, содержащий определённую версию персонажа.

Любой из этих вариантов может быть достигнут разовым вызовом Instantiate(). Вам надо просто привязать к нему правильный префаб и всё готово!

Важно помнить, что обломки, которые вы Instantiate() (создаёте их экземпляр), могут быть сделаны из совершенно отличных от оригинала объектов. Например, если у вас есть самолёт, вы можете смоделировать 2 версии. Одна из которых - самолёт, состоящий из одного GameObject с Mesh Renderer и скриптами физики самолёта. Если эта модель будет единым объектом, то игра будет работать быстрее, т.к. вы сможете сделать модель с меньшим количеством треугольников, а ввиду того, что в результате будет меньше объектов, то и рендер будет проходить быстрее. Также, пока ваш самолёт радостно летает вокруг, нет надобности разделять его на части.

Это стандартные шаги, если надо собрать префаб сломанного самолёта:

  1. Смоделируйте ваш самолёт с различными деталями в вашем любимом приложении для моделирования
  2. Создайте пустую сцену
  3. Перетащите модель в пустую сцену
  4. Добавьте Rigidbody всем частям, выделив все части и выбрав Component->Physics->Rigidbody
  5. Добавьте коллайдеры Box Collider всем частям, выделив их и выбрав Component->Physics->Box Collider
  6. Для дополнительного спец-эффекта, добавьте системы похожих на дым частиц в виде дочерних объектов для каждой части.
  7. Теперь у вас есть самолёт с множеством раздельных деталей. Они будут падать на землю по законам физики и будут создавать след из частиц, в силу того, что к ним присоединены системы частиц. Нажмите Play для предварительного просмотра того, как ваша модель будет себя вести и проведите все необходимые поправки.
  8. Выберите Assets->Create Prefab
  9. Перетяните на префаб корневой GameObject, содержащий все части самолёта.

Следующий пример покажет, как эти шаги моделируются в коде.

// JavaScript

var wreck : GameObject;

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

// Calls the fire method when holding down ctrl or mouse
function KillSelf () {
    // Instantiate the wreck game object at the same position we are at
    var wreckClone = 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);


// C#

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() с префабом! Мы думаем, что вы уже поняли нашу идею, почему префабы так удобны в этих случаях. Вот необходимый для этих сценариев код:

// JavaScript

// Instantiates a prefab in a circle

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);
    }
}


// C#
// 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);
    }
}


// JavaScript

// Instantiates a prefab in a grid

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);
        }
    }
}


// C#

// 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);
        }
    }
} 


Префабы (Prefabs)
Saving Your Work