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 deparent.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:
- Monte o objeto na cena. Para um tiro 2D: um GameObject com
SpriteRenderer,Rigidbody2D(Gravity Scale 0),CircleCollider2Dcom Is Trigger marcado e o scriptBulletque você vai escrever abaixo. - Arraste o objeto da Hierarchy para uma pasta
Prefabsna janela Project. O ícone fica azul. - 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.
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.



