Voltar para o Blog
Quest Log

Instantiate na Unity: Criar Objetos e Prefabs por Código

Nave espacial disparando projéteis que surgem de um ponto na ponta do canhão, com cópias de inimigos aparecendo ao fundo

Unity Instantiate na prática: spawn de tiro e inimigo, sobrecargas com posição, rotação e pai, componente tipado no retorno e a hora de usar object pooling.

Ferramenta:UnityC#

Instantiate na Unity: Criar Objetos e Prefabs por Código

Unity Instantiate é o método que cria uma cópia de um objeto durante o jogo. Você passa um prefab, uma posição e uma rotação, e ele devolve o clone já na cena: Instantiate(bulletPrefab, firePoint.position, firePoint.rotation). É assim que nascem tiros, inimigos, moedas, partículas de impacto e qualquer coisa que não estava na cena quando a fase começou.

Aqui você vai montar um spawn de tiro e um spawner de inimigos, usar as sobrecargas com posição, rotação e pai, receber o componente já tipado no retorno (sem GetComponent depois) e entender quando criar e destruir vira gargalo e é hora de trocar por object pooling. Código para a Unity 6.3 LTS.

Unity instantiate: como funciona

Instantiate é um método estático de UnityEngine.Object, por isso você chama direto de qualquer MonoBehaviour. Ele recebe um original e devolve uma cópia completa: o GameObject, todos os componentes, todos os filhos e os valores serializados de cada campo.

A referência do Unity 6.3 lista várias sobrecargas, genéricas e não genéricas. As que você vai usar quase sempre:

  • Instantiate(original): cria na posição e rotação do original, sem pai.
  • Instantiate(original, parent): cria como filho de parent.
  • Instantiate(original, parent, instantiateInWorldSpace): como filho, escolhendo se a posição do original vale como posição de mundo (true) ou local ao pai (false, o padrão da sobrecarga anterior).
  • Instantiate(original, position, rotation): cria num lugar exato, sem pai.
  • Instantiate(original, position, rotation, parent): lugar exato e já pendurado no pai.

Duas coisas acontecem na hora da chamada que vale saber. O Awake e o OnEnable dos scripts do clone rodam antes da linha seguinte do seu código. O Start só roda depois, antes do primeiro Update do clone. Isso significa que você pode configurar o objeto recém-criado (definir alvo, direção, dano) e ele já usa esses valores no Start.

O clone ganha o nome do original com "(Clone)" no final, o que ajuda a identificar na Hierarchy o que foi criado em tempo de jogo.

Criar um prefab para instanciar

Prefab é um GameObject salvo como asset, um molde. Para criar:

  1. Monte o objeto na cena. Para um tiro 2D: um GameObject com SpriteRenderer, Rigidbody2D (Gravity Scale 0), CircleCollider2D com Is Trigger marcado e o script Bullet que você vai escrever abaixo.
  2. Arraste o objeto da Hierarchy para uma pasta Prefabs na janela Project. O ícone fica azul.
  3. Apague o objeto da cena. O molde fica no Project.

Se você ainda não trabalhou com prefab, overrides e variantes, o post Prefab na Unity cobre a parte do editor. Aqui o foco é o código.

O script do tiro. Arquivo Bullet.cs:

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class Bullet : MonoBehaviour
{
    [SerializeField] private float speed = 14f;
    [SerializeField] private float lifetime = 2f;
    [SerializeField] private int damage = 1;

    private Rigidbody2D body;

    private void Awake()
    {
        body = GetComponent<Rigidbody2D>();
    }

    private void Start()
    {
        body.linearVelocity = transform.right * speed;
        Destroy(gameObject, lifetime);
    }

    public void SetDamage(int value)
    {
        damage = value;
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Enemy"))
        {
            Debug.Log("Acertou " + other.name + " com " + damage + " de dano.");
            Destroy(gameObject);
        }
    }
}

O tiro anda na direção do próprio eixo X local (transform.right), então quem decide a direção é a rotação com que ele nasce. O Destroy(gameObject, lifetime) garante que um tiro que errou tudo não fique voando para sempre.

Instantiate com posição, rotação e pai

Agora a arma. Crie um GameObject vazio filho do jogador, chamado FirePoint, posicionado na ponta do cano. Arquivo PlayerGun.cs:

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerGun : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;
    [SerializeField] private Transform firePoint;
    [SerializeField] private float fireRate = 6f;
    [SerializeField] private int bulletDamage = 2;

    private InputAction attackAction;
    private float nextShotTime;

    private void Awake()
    {
        attackAction = InputSystem.actions.FindAction("Attack");
    }

    private void Update()
    {
        if (attackAction.IsPressed() && Time.time >= nextShotTime)
        {
            Shoot();
            nextShotTime = Time.time + 1f / fireRate;
        }
    }

    private void Shoot()
    {
        Bullet bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
        bullet.SetDamage(bulletDamage);
    }
}

O tiro nasce na posição e na rotação do FirePoint. Se o jogador vira para a esquerda girando 180 graus no eixo Y, o FirePoint gira junto, e o transform.right do tiro aponta para a esquerda. Não precisa de if para direção. Atenção se o seu personagem vira com scale.x = -1: escala não muda a rotação, o FirePoint continua com a rotação de antes e o tiro sai para a direita. Nesse caso, gire o personagem em Y ou calcule a direção pelo sinal da escala.

Por que o tiro não recebe pai aqui? Se você passasse o jogador como pai, o tiro andaria junto com o jogador, como se estivesse grudado. Tiro, inimigo e efeito que vive no mundo nascem sem pai.

Quando usar pai: coisas que devem seguir o objeto. Um escudo que aparece em volta do personagem, um ícone de "!" sobre a cabeça do inimigo, um item de UI criado dentro de um painel. Exemplo:

using UnityEngine;

public class AlertIcon : MonoBehaviour
{
    [SerializeField] private GameObject alertPrefab;
    [SerializeField] private Vector3 offset = new Vector3(0f, 1.2f, 0f);

    public void ShowAlert()
    {
        GameObject icon = Instantiate(alertPrefab, transform);
        icon.transform.localPosition = offset;
    }
}

Com Instantiate(alertPrefab, transform), o ícone vira filho do inimigo e segue ele. A posição é ajustada em coordenadas locais logo depois.

Pegar o componente já no Instantiate

Repare que o campo do PlayerGun é Bullet bulletPrefab, não GameObject bulletPrefab. Isso é proposital. A versão genérica Instantiate<T> devolve o mesmo tipo que recebeu: se você passa um Bullet, recebe um Bullet. O C# deduz o T sozinho, então a chamada fica Instantiate(bulletPrefab, ...) sem precisar escrever <Bullet>.

Na prática, isso elimina o GetComponent depois de criar:

// Com GameObject: cria e depois procura o componente.
GameObject clone = Instantiate(bulletObjectPrefab, firePoint.position, firePoint.rotation);
Bullet bulletA = clone.GetComponent<Bullet>();

// Com o tipo do componente: já vem pronto.
Bullet bulletB = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);

No Inspector, o campo tipado como Bullet só aceita prefabs que tenham o componente Bullet. Isso impede arrastar o prefab errado, um erro que com GameObject só aparece em tempo de jogo.

Até aqui é um tiro e uma arma. Um jogo de nave completo junta isso com inimigos que atiram de volta, ondas, power-ups, pontuação e telas, e cada spawn precisa conversar com o resto sem que um sistema quebre o outro. É nesse ponto, de juntar tudo num projeto que fecha, que estudar sozinho costuma emperrar.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Spawn de inimigo a partir de pontos

O mesmo Instantiate cria inimigos. Arquivo EnemySpawner.cs:

using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private GameObject enemyPrefab;
    [SerializeField] private Transform[] spawnPoints;
    [SerializeField] private Transform enemiesParent;
    [SerializeField] private float interval = 2f;
    [SerializeField] private int maxEnemies = 8;

    private float timer;

    private void Update()
    {
        timer += Time.deltaTime;

        if (timer >= interval && enemiesParent.childCount < maxEnemies)
        {
            timer = 0f;
            SpawnOne();
        }
    }

    private void SpawnOne()
    {
        Transform point = spawnPoints[Random.Range(0, spawnPoints.Length)];
        Instantiate(enemyPrefab, point.position, Quaternion.identity, enemiesParent);
    }
}

Aqui o pai tem outro papel: organização. Todos os inimigos ficam embaixo de um GameObject vazio Enemies na Hierarchy, em vez de espalhados na raiz da cena. Fica fácil contar quantos existem (childCount) e a Hierarchy não vira uma lista de 200 "Enemy(Clone)". Como enemiesParent fica parado na origem, pendurar os inimigos nele não muda a posição de ninguém.

Random.Range(0, spawnPoints.Length) com int exclui o valor máximo, então sorteia um índice válido do array. Quaternion.identity é "sem rotação".

Erros comuns: instanciar o objeto da cena, perder a referência

Arrastar o objeto da cena em vez do prefab. O campo aceita os dois. Se você arrasta o inimigo que está na Hierarchy, o Instantiate funciona, até o dia em que aquele inimigo morre. Aí o original foi destruído, o campo passa a apontar para um objeto que não existe mais e o Instantiate para com o erro "The Object you want to instantiate is null". Sempre arraste da janela Project (ícone azul), nunca da Hierarchy.

Configurar o prefab em vez do clone. bulletPrefab.SetDamage(5) muda o asset, não o tiro que acabou de nascer. Em Play Mode, essa mudança pode até persistir no asset. Configure sempre o retorno: Bullet bullet = Instantiate(...); bullet.SetDamage(5);.

Perder a referência do que foi criado. Se você precisa destruir, contar ou mudar os inimigos depois, guarde o retorno numa List<GameObject> ou use um pai como no spawner. Procurar depois com Find é lento e frágil.

Tiro nascendo dentro do próprio jogador. O collider do tiro encosta no collider do jogador e dispara a colisão no mesmo frame. Afaste o FirePoint ou use layers e a matriz de colisão (Project Settings > Physics 2D > Layer Collision Matrix) para jogador e tiros do jogador não colidirem.

Rotação esquecida. Instantiate(prefab, position, Quaternion.identity) num tiro faz todos irem para a direita, porque a direção vem da rotação. Para tiro, use a rotação do FirePoint.

Quando trocar por object pooling

Instantiate e Destroy têm custo: alocar memória, inicializar componentes, e depois o coletor de lixo limpar. Para um inimigo a cada dois segundos, irrelevante. Para uma metralhadora a 15 tiros por segundo com partícula de impacto em cada acerto, o custo começa a aparecer como engasgos no Profiler.

A solução é criar um estoque de objetos uma vez e reaproveitar: ativa quando precisa, desativa quando "morre". A Unity tem isso pronto em UnityEngine.Pool.ObjectPool<T>:

using UnityEngine;
using UnityEngine.Pool;

public class PooledGun : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;
    [SerializeField] private Transform firePoint;

    private ObjectPool<Bullet> pool;

    private void Awake()
    {
        pool = new ObjectPool<Bullet>(
            createFunc: () => Instantiate(bulletPrefab),
            actionOnGet: bullet => bullet.gameObject.SetActive(true),
            actionOnRelease: bullet => bullet.gameObject.SetActive(false),
            actionOnDestroy: bullet => Destroy(bullet.gameObject),
            collectionCheck: false,
            defaultCapacity: 20,
            maxSize: 100);
    }

    public Bullet Fire()
    {
        Bullet bullet = pool.Get();
        bullet.transform.SetPositionAndRotation(firePoint.position, firePoint.rotation);
        return bullet;
    }

    public void Return(Bullet bullet)
    {
        pool.Release(bullet);
    }
}

Os parâmetros, na ordem: createFunc é o único lugar onde Instantiate ainda aparece, chamado só quando o estoque está vazio. actionOnGet e actionOnRelease ligam e desligam o objeto ao sair e voltar. actionOnDestroy roda quando o pool já tem maxSize objetos guardados e recebe mais um, que então é destruído de verdade. defaultCapacity é o tamanho inicial da lista interna, e collectionCheck em true avisa se você devolver o mesmo objeto duas vezes, uma checagem útil enquanto desenvolve.

Com pool, o tiro não pode mais chamar Destroy em si mesmo (nem o Destroy(gameObject, lifetime) do Start): ele precisa devolver ao pool. E a inicialização sai do Start, que roda uma vez só. Cuidado com a tentação de mover para o OnEnable: ele roda dentro do pool.Get(), antes de o Fire posicionar e girar o tiro, então a velocidade sairia com a rotação antiga. O caminho seguro é um método público, como Launch(), chamado depois do SetPositionAndRotation. O conceito completo, com os cuidados de reset, está em object pooling.

Para objetos grandes, a Unity 6 também tem Object.InstantiateAsync, que clona sem travar o frame e devolve um AsyncInstantiateOperation<T>. É útil para carregar um pedaço pesado de cenário no meio da fase, não para tiros.

Próximo passo

Monte a cena com o PlayerGun, o Bullet e o EnemySpawner, e confira na Hierarchy os clones nascendo e sumindo. Depois troque o campo do spawner de GameObject para o tipo do script do inimigo e configure cada inimigo logo após o Instantiate (velocidade aleatória, por exemplo). Quando quiser organizar esse código em sistemas que não dependem uns dos outros, o post padrões de projeto em jogos mostra Observer e Component aplicados a esse tipo de spawn.

Perguntas frequentes

Como instanciar um prefab na Unity por código?

Crie um campo [SerializeField] private GameObject prefab, arraste o prefab da janela Project para ele no Inspector e chame Instantiate(prefab, posicao, rotacao). O método devolve a cópia criada, que você pode guardar numa variável para configurar.

Como instanciar um objeto como filho de outro na Unity?

Use a sobrecarga que recebe um Transform pai: Instantiate(prefab, pai) cria a cópia como filha mantendo a posição local do prefab. Instantiate(prefab, posicao, rotacao, pai) cria na posição e rotação de mundo informadas e já pendura no pai.

Instantiate é lento na Unity?

Uma chamada ocasional não é problema. Criar e destruir dezenas de objetos por segundo, como balas de uma metralhadora, gera custo de CPU e lixo para o coletor de memória. Nesses casos, reaproveite objetos com object pooling, que a Unity oferece pronto em UnityEngine.Pool.ObjectPool.

O que é InstantiateAsync na Unity 6?

É uma versão assíncrona que clona o objeto sem travar o frame e devolve um AsyncInstantiateOperation. Serve para criar algo grande, como um pedaço de cenário cheio de objetos, sem engasgo. O Awake dos objetos criados ainda roda na thread principal.