SerializeField vs public na Unity: Variáveis no Inspector
[SerializeField] na Unity é o atributo que faz um campo private aparecer no Inspector e ser salvo na cena ou no prefab. Ele dá ao campo o mesmo efeito visual de public, mas sem abrir a variável para qualquer outro script mexer. Por isso a regra da casa é simples: configurável no editor, [SerializeField] private; public só quando outro script realmente precisa daquilo.
Aqui você vai ver o porquê dessa regra, os atributos que deixam o Inspector organizado (Header, Tooltip, Range, Space), o bug do valor que você muda no código e não muda no jogo, e o que fazer quando um campo simplesmente não aparece. Tudo testado contra as regras de serialização da Unity 6.3 LTS.
SerializeField na Unity: o que faz
Serializar é transformar dados em algo que pode ser salvo e recarregado. Quando você ajusta a velocidade de um inimigo no Inspector e salva a cena, a Unity grava aquele número no arquivo da cena ou do prefab. Ao abrir o projeto de novo, ela lê o arquivo e devolve o valor ao campo.
A regra do manual ("Serialization rules") diz que um campo é serializado quando é public ou tem [SerializeField], e desde que não seja static, const nem readonly. Além disso, o tipo precisa ser suportado: primitivos (int, float, bool, string), enums, tipos da Unity (Vector2, Color, AnimationCurve), referências a objetos da Unity (GameObject, Transform, componentes, assets), structs e classes marcadas com [Serializable], e arrays ou List<T> desses tipos.
Campo serializado aparece no Inspector. Campo não serializado não aparece e não é salvo. É essa a relação que você precisa ter na cabeça.
public vs private com SerializeField
Veja os dois jeitos lado a lado. Arquivo EnemyStats.cs:
using UnityEngine;
public class EnemyStats : MonoBehaviour
{
// Jeito que aparece em tutorial antigo: qualquer script pode mudar.
public float speedPublic = 3f;
// Jeito recomendado: aparece no Inspector, mas só esta classe muda.
[SerializeField] private float speed = 3f;
[SerializeField] private int maxHealth = 4;
private int currentHealth;
public float Speed => speed;
public bool IsDead => currentHealth <= 0;
private void Awake()
{
currentHealth = maxHealth;
}
public void TakeDamage(int amount)
{
currentHealth = Mathf.Max(currentHealth - amount, 0);
}
}
No Inspector, speedPublic e speed ficam idênticos. A diferença está no resto do código. Com public, qualquer script pode escrever enemy.speedPublic = 0; e o inimigo para, sem deixar rastro. Quando o bug aparecer ("o inimigo congela às vezes"), você vai ter que procurar em todos os scripts do projeto quem mexe nesse campo.
Com [SerializeField] private, a única forma de mudar speed em jogo é por um método da própria classe. Outros scripts leem pela propriedade Speed, que é só leitura. Se a vida precisa mudar, passa por TakeDamage, que é um ponto só, onde você pode botar um log, tocar um som ou disparar um evento.
Existe ainda um ganho de leitura. Quando você abre um script e vê [SerializeField] private, sabe que aquele número é ajuste de design, feito no editor. Quando vê public, precisa descobrir se é ajuste de design, se é dado que outro sistema escreve, ou as duas coisas ao mesmo tempo. Isso também protege você de você mesmo. Num projeto pequeno parece exagero; com 40 scripts, saber que só uma classe mexe em cada valor é o que mantém o código legível.
O valor do Inspector ganha do código
Este é o bug que mais confunde quem está começando. Você escreve [SerializeField] private float speed = 3f;, adiciona o script num inimigo, testa, acha lento, volta no código, troca para 6f, salva, dá Play. O inimigo continua lento.
O motivo: o 3f do código é só o valor inicial, usado no momento em que o componente é criado. A partir daí, o valor que vale é o que está salvo na cena ou no prefab. Ao mudar para 6f no código, você mudou o padrão para componentes novos, não os que já existem.
Ordem real dos acontecimentos quando o jogo carrega um objeto:
- A Unity cria a instância do script; os inicializadores do código rodam (
speed = 6f). - A Unity aplica os dados serializados da cena por cima (
speed = 3f, o valor salvo). - O
Awakeroda, já com3f.
Como resolver, conforme o caso:
- Quer o valor novo só neste objeto: mude no Inspector.
- Quer voltar ao valor do código: clique nos três pontos do componente no Inspector e escolha Reset. Isso reaplica todos os valores iniciais do código, então confira os outros campos depois.
- Quer mudar em todos os inimigos: mude no prefab, não nas cópias da cena. Cópias sem override herdam do prefab.
- O valor nunca deveria ser configurável: tire o
[SerializeField]e deixeprivate. Aí o código manda sempre.
Outro sintoma do mesmo mecanismo: mudar um valor no Inspector durante o Play e perder a mudança ao parar. Mudança em Play Mode não é salva. Copie o componente (três pontos, Copy Component), pare o jogo e use Paste Component Values.
Header, Tooltip, Range e Space
Um script com 15 campos soltos vira uma parede no Inspector. Quatro atributos resolvem a maior parte disso. Arquivo PlayerConfig.cs:
using UnityEngine;
public class PlayerConfig : MonoBehaviour
{
[Header("Movimento")]
[Tooltip("Velocidade horizontal em unidades por segundo.")]
[SerializeField, Range(1f, 15f)] private float moveSpeed = 6f;
[Tooltip("Velocidade vertical aplicada no início do pulo.")]
[SerializeField, Min(0f)] private float jumpSpeed = 12f;
[Space(12)]
[Header("Combate")]
[SerializeField, Range(1, 10)] private int maxHealth = 5;
[Tooltip("Segundos de invencibilidade depois de tomar dano.")]
[SerializeField, Range(0f, 3f)] private float invincibleTime = 1f;
[Header("Referências")]
[SerializeField] private Rigidbody2D body;
[SerializeField] private Animator animator;
[Header("Texto")]
[SerializeField, TextArea(2, 5)] private string deathMessage = "Você caiu.";
}
[Header("...")]escreve um título em negrito acima do campo.[Tooltip("...")]mostra o texto quando o mouse para sobre o nome do campo. Use para dizer a unidade (segundos, graus, unidades por segundo), que é o que mais se esquece.[Range(min, max)]troca a caixa de número por um slider e impede valores fora do intervalo pelo Inspector. Funciona comfloateint.[Space(pixels)]adiciona um respiro vertical.[Min(valor)]e[TextArea]são dois extras úteis: um impede número abaixo do mínimo sem slider, o outro deixa um campo de texto com várias linhas.
Os atributos podem ir em colchetes separados ou juntos, separados por vírgula, como em [SerializeField, Range(1f, 15f)]. O resultado é o mesmo.
Um Inspector bem organizado é o que permite você ajustar o jogo sem abrir código. Isso fica crítico quando o projeto tem jogador, inimigos, armas e fases inteiras para balancear, e é exatamente nessa hora, de fazer muita coisa funcionar junto, que quem estuda sozinho costuma travar.
Serializar classes e listas próprias
Você pode agrupar dados numa classe sua e mostrar no Inspector, desde que ela tenha [System.Serializable]. Arquivo WaveSpawner.cs:
using System;
using System.Collections.Generic;
using UnityEngine;
public class WaveSpawner : MonoBehaviour
{
[Serializable]
private class Wave
{
public GameObject enemyPrefab;
[Range(1, 30)] public int count = 5;
[Min(0.1f)] public float interval = 1f;
}
[SerializeField] private List<Wave> waves = new List<Wave>();
public int WaveCount => waves.Count;
}
No Inspector aparece uma lista que você expande, e cada item tem os três campos. Os campos da classe Wave são public porque ela é um pacote de dados interno, private dentro do WaveSpawner. Ninguém de fora acessa.
Três regras do manual que valem aqui:
[Serializable]não é herdado. SeBossWaveherda deWave, ela também precisa do atributo.- Sem coleção dentro de coleção.
List<List<int>>,int[,]eDictionarynão são serializados. Para uma grade, faça umaListde uma classeRowque contém umaList<int>. - Records não. O manual avisa que a serialização da Unity não suporta records do C#.
Quando os mesmos dados aparecem em vários objetos (stats de cada tipo de inimigo, por exemplo), o passo seguinte é tirar esses dados do componente e colocar num ScriptableObject, um asset que vários prefabs referenciam. Está explicado em ScriptableObject na Unity.
Erros comuns: campo que não aparece no Inspector
Quando um campo some do Inspector, a causa quase sempre está nesta lista:
- Erro de compilação no projeto. Se qualquer script tem erro, a Unity não recompila, e o Inspector mostra a versão antiga. Olhe o Console antes de tudo.
- Campo
static,constoureadonly. Esses nunca são serializados. - É uma propriedade.
public float Speed { get; set; }não aparece. Veja a próxima seção. - Tipo não suportado.
Dictionary,List<List<T>>, array multidimensional, interface,Action. - Classe própria sem
[Serializable]. O campo do tipoWavesome inteiro. [HideInInspector]ou[NonSerialized]. O primeiro esconde mas continua salvando; o segundo nem salva. Às vezes vêm de código copiado.- Nome da classe diferente do arquivo. O componente fica com aviso de script ausente e nenhum campo aparece.
Quando usar propriedades
Propriedade é o jeito do C# de expor um valor com controle. A Unity não serializa propriedades, mas tem duas formas de combinar as duas coisas.
A forma clássica, que funciona com qualquer atributo de Inspector:
using UnityEngine;
public class Weapon : MonoBehaviour
{
[SerializeField, Range(1, 20)] private int damage = 3;
public int Damage => damage;
}
A forma curta, com [field: SerializeField], que o manual documenta para serializar o campo gerado de uma auto-propriedade:
using UnityEngine;
public class Pickup : MonoBehaviour
{
[field: SerializeField] public int Value { get; private set; } = 1;
}
O Value aparece no Inspector, outros scripts leem, só a própria classe escreve. O cuidado é que o nome salvo no arquivo é o do campo gerado pelo compilador; se você trocar de auto-propriedade para campo comum depois, o valor salvo se perde. Para dados que vão mudar de forma, prefira a versão clássica.
Próximo passo
Pegue um script seu que tenha campos public, troque todos por [SerializeField] private, compile e veja quais outros scripts quebram. Cada erro de compilação é um lugar que mexia no campo sem você saber: decida se ele precisa de um método ou se nem deveria estar mexendo. Depois organize o Inspector com Header e Range. Se a dúvida for de C# básico, o post qual linguagem a Unity usa dá o contexto, e o padrão de manter dados e comportamento separados aparece em padrões de projeto em jogos.
Perguntas frequentes
Qual a diferença entre public e [SerializeField] private na Unity?
Os dois fazem o campo aparecer no Inspector e ser salvo na cena ou no prefab. A diferença é que public também deixa qualquer outro script ler e alterar o valor, enquanto [SerializeField] private mantém o campo fechado para o código de fora. Para o Inspector o resultado é o mesmo; para o projeto, private é mais seguro.
Por que mudei o valor no script e nada mudou no jogo?
Porque o valor salvo no Inspector tem prioridade sobre o valor inicial escrito no código. O número do código só vale quando o componente é adicionado ou resetado. Mude no Inspector, ou use Reset no menu de três pontos do componente para voltar aos valores do código.
Como mostrar uma propriedade C# no Inspector da Unity?
A Unity não serializa propriedades. Para uma auto-propriedade, use [field: SerializeField], que serializa o campo gerado pelo compilador. A alternativa clássica é um campo [SerializeField] private com uma propriedade pública só de leitura apontando para ele.
Dictionary aparece no Inspector da Unity?
Não. O sistema de serialização da Unity não suporta Dictionary, arrays multidimensionais nem listas de listas. O padrão comum é serializar uma List de uma classe [Serializable] com chave e valor e montar o Dictionary no Awake.



