Voltar para o Blog
Quest Log

C# para Unity: o Essencial para Começar a Programar Jogos

Janela de código C# ao lado de um personagem de jogo com setas ligando variáveis às partes do personagem

C# para Unity sem estudar a linguagem inteira: tipos, if, loops, métodos, classes, listas, null e eventos, cada um dentro de um MonoBehaviour no Unity 6.

Ferramenta:UnityC#

C# para Unity: o Essencial para Começar a Programar Jogos

C# para Unity é um recorte pequeno de uma linguagem grande: no primeiro mês de projeto você usa tipos, condições, loops, métodos, classes, listas, null e eventos, e quase nada além disso. Este guia mostra cada um desses pedaços dentro de um script de jogo de verdade, um MonoBehaviour que você cola na Unity 6.3 LTS e roda, e diz com todas as letras o que pode ficar para depois.

Se você ainda está decidindo se a Unity é a engine certa ou qual linguagem ela usa, o post Qual linguagem a Unity usa resolve essa parte. Aqui a pergunta é outra: o que estudar de C# para conseguir escrever o código do seu primeiro jogo sem travar a cada linha.

C# para Unity: quanto da linguagem você precisa

C# é usado em backend, aplicativo de desktop, ferramenta corporativa e jogo. Livro de C# cobre tudo isso, e é por isso que tanta gente desiste no capítulo de interfaces genéricas sem nunca ter movido um personagem. Script de jogo usa uma fatia bem menor.

A Unity 6.3 LTS compila seus scripts com o compilador Roslyn e suporta C# 9.0, segundo o manual oficial (página "C# compiler", conferida em outubro de 2026). Algumas partes do C# 9 não funcionam na engine, como init-only setters e covariant return types, e o manual avisa que records não funcionam com a serialização da Unity. Nada disso aparece no código de quem está começando.

O runtime dos scripts é o Mono. A Unity vem publicando atualizações sobre a migração para o CoreCLR (o runtime moderno do .NET), com o alvo declarado em fórum oficial de um Editor completo em CoreCLR no Unity 6.8. Para você, hoje, isso não muda uma linha do que está neste post: tipos, if, for, classes e listas são iguais em qualquer runtime.

A lista do primeiro mês:

  • variáveis e tipos (int, float, bool, string, Vector2, Vector3);
  • if, else, switch, for e foreach;
  • métodos com parâmetro e retorno;
  • classes, objetos e o MonoBehaviour;
  • List<T>, arrays e Dictionary<TKey, TValue>;
  • null e referências;
  • eventos com Action.

Variáveis e tipos que você mais vai usar

Num jogo, quase toda variável representa uma coisa que o jogador vê ou sente: vida, velocidade, nome, se está no chão. Arquivo PlayerStats.cs:

using UnityEngine;

public class PlayerStats : MonoBehaviour
{
    [SerializeField] private int maxHealth = 5;
    [SerializeField] private float moveSpeed = 4.5f;
    [SerializeField] private string playerName = "Aventureira";

    private int currentHealth;
    private bool isGrounded;
    private Vector2 spawnPoint;

    private void Awake()
    {
        currentHealth = maxHealth;
        spawnPoint = transform.position;
        Debug.Log(playerName + " começa com " + currentHealth + " de vida.");
    }
}

Três detalhes que pegam iniciante. O f no fim de 4.5f é obrigatório: sem ele o C# lê 4.5 como double e não deixa guardar num float. int não tem casa decimal, então 7 / 2 dá 3, não 3.5. E Vector2/Vector3 são tipos da Unity (struct), não do C#: eles vêm do using UnityEngine;.

O [SerializeField] private faz o campo aparecer no Inspector sem abrir ele para qualquer outro script mexer. Esse hábito evita muito bug: se outro script precisar ler o valor, crie um método ou uma propriedade só de leitura.

Condições e loops em código de jogo

if é a forma mais comum de decisão do jogo: tomou dano, está morto, pode pular. Loop aparece quando você percorre uma coleção: todos os inimigos, todos os itens do inventário, todas as moedas.

Arquivo Health.cs:

using UnityEngine;

public class Health : MonoBehaviour
{
    [SerializeField] private int maxHealth = 5;
    private int currentHealth;

    private void Awake()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(int amount)
    {
        if (amount <= 0)
        {
            return;
        }

        currentHealth -= amount;

        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log(name + " morreu.");
        }
        else if (currentHealth == 1)
        {
            Debug.Log(name + " está por um fio.");
        }
    }

    public int CountAliveEnemies(Health[] enemies)
    {
        int alive = 0;
        foreach (Health enemy in enemies)
        {
            if (enemy != null && enemy.currentHealth > 0)
            {
                alive++;
            }
        }
        return alive;
    }
}

O return logo no começo de TakeDamage é um padrão que você vai usar o tempo todo: sai do método cedo quando a entrada não faz sentido, e o resto do código fica sem um if gigante em volta. foreach serve para ler a coleção; quando você precisa do índice (para remover item ou comparar com o anterior), use for (int i = 0; i < enemies.Length; i++).

Um cuidado que não existe em livro de C#: loop dentro de Update roda a cada frame. Percorrer 10 inimigos é nada; percorrer 5 mil objetos com GetComponent dentro do loop, 60 vezes por segundo, pesa.

Métodos e parâmetros

Método é um bloco de código com nome. Na Unity você escreve dois tipos: os que a engine chama sozinha (Awake, Start, Update, OnTriggerEnter2D) e os seus, chamados por você ou por outro script, como o TakeDamage acima.

A regra prática: se você escreveu o mesmo trecho duas vezes, ele vira método. Se o método passa de uns 30 linhas, provavelmente faz duas coisas e pode ser dividido. Arquivo Coin.cs:

using UnityEngine;

public class Coin : MonoBehaviour
{
    [SerializeField] private int value = 1;

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            int total = AddToScore(value);
            Debug.Log("Pontuação: " + total);
            Destroy(gameObject);
        }
    }

    private int AddToScore(int amount)
    {
        int current = PlayerPrefs.GetInt("score", 0);
        int updated = current + amount;
        PlayerPrefs.SetInt("score", updated);
        return updated;
    }
}

AddToScore recebe um int e devolve um int. Quando o método não devolve nada, o tipo de retorno é void. Os nomes dos métodos da Unity precisam estar escritos exatamente assim: OnTriggerEnter2D com "T" maiúsculo e o "2D" no fim. Um erro de digitação não dá erro de compilação, o método só nunca é chamado.

Classes, objetos e MonoBehaviour

Toda vez que você cria um script na Unity, cria uma classe. Quando arrasta esse script para um GameObject, a Unity cria um objeto (uma instância) daquela classe, preso àquele GameObject. Dez inimigos com o script Enemy são dez objetos Enemy, cada um com sua própria vida.

Duas regras que a Unity cobra:

  1. O nome da classe precisa ser igual ao nome do arquivo. Enemy.cs contém public class Enemy : MonoBehaviour. Se não bater, o componente não pode ser adicionado ao objeto.
  2. Classe que herda de MonoBehaviour não é criada com new. Você adiciona pelo Inspector, com AddComponent<T>() ou instanciando um prefab.

Classes que não herdam de MonoBehaviour também têm lugar: dados puros, como um item de inventário. Essas você cria com new normalmente. Arquivo InventoryItem.cs:

using System;

[Serializable]
public class InventoryItem
{
    public string id;
    public int amount;

    public InventoryItem(string id, int amount)
    {
        this.id = id;
        this.amount = amount;
    }
}

O [Serializable] permite que uma lista dessas classes apareça no Inspector de quem a usa. Aqui os campos são públicos de propósito: é um pacote de dados, sem comportamento para proteger.

Até aqui, cada conceito cabe num script. Quando o jogo cresce, a parte difícil deixa de ser a sintaxe: é fazer input, física, animação, câmera e interface conversarem sem que um script quebre o outro. É aí que quem estuda sozinho costuma parar, com cinco scripts que funcionam isolados e um jogo que não fecha.

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

List, arrays e Dictionary

Array tem tamanho fixo e é bom para coisas que não mudam: os pontos de patrulha de um inimigo, os sprites de uma animação. List<T> cresce e encolhe: inimigos vivos, itens coletados. Dictionary<TKey, TValue> busca por chave: quantidade de cada item pelo id.

Arquivo Inventory.cs:

using System.Collections.Generic;
using UnityEngine;

public class Inventory : MonoBehaviour
{
    // Array: tamanho fixo, preenchido no Inspector (aqui, ícones dos slots).
    [SerializeField] private Sprite[] slotIcons;
    // List: cresce e encolhe durante o jogo.
    [SerializeField] private List<InventoryItem> startingItems = new List<InventoryItem>();

    private readonly Dictionary<string, int> counts = new Dictionary<string, int>();

    private void Awake()
    {
        foreach (InventoryItem item in startingItems)
        {
            Add(item.id, item.amount);
        }
    }

    public void Add(string id, int amount)
    {
        if (counts.TryGetValue(id, out int current))
        {
            counts[id] = current + amount;
        }
        else
        {
            counts.Add(id, amount);
        }
    }

    public int GetCount(string id)
    {
        return counts.TryGetValue(id, out int value) ? value : 0;
    }
}

Repare que o Dictionary não tem [SerializeField]. A Unity não serializa dicionários (o manual de serialização lista isso entre os tipos não suportados), então ele não aparece no Inspector. O padrão comum é o que está no código: uma lista serializada para configurar no editor e um dicionário montado no Awake para buscar rápido em jogo.

Null, referência e o erro mais comum

Variável de classe guarda uma referência, não o objeto em si. Se ninguém atribuiu nada, ela vale null, e qualquer acesso a ela lança NullReferenceException. É o erro número um de quem começa na Unity, e quase sempre tem uma de três causas: esqueceu de arrastar no Inspector, o GetComponent não achou o componente, ou o objeto foi destruído.

Arquivo Door.cs:

using UnityEngine;

public class Door : MonoBehaviour
{
    [SerializeField] private Animator animator;

    private void Awake()
    {
        if (animator == null)
        {
            Debug.LogError("Door sem Animator em " + name, this);
        }
    }

    public void Open()
    {
        if (animator != null)
        {
            animator.SetTrigger("Open");
        }
    }
}

O Debug.LogError com this no segundo parâmetro faz um clique no Console selecionar o objeto com problema na Hierarchy. Isso economiza muito tempo quando há 40 portas na cena.

Uma pegadinha específica da Unity: os operadores ?. e ?? do C# não devem ser usados com componentes e GameObjects. A documentação de UnityEngine.Object diz que eles não são suportados porque não enxergam o objeto destruído como nulo. Para objetos da Unity, compare com == null ou use o próprio objeto como bool (if (animator)).

Eventos: avisar sem conhecer quem escuta

O último conceito do mês é o que mais organiza o projeto. Em vez do script de vida chamar a UI, o som e a câmera diretamente, ele dispara um evento e quem se interessa se inscreve. Arquivo PlayerHealth.cs:

using System;
using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
    public event Action<int> HealthChanged;
    public event Action Died;

    [SerializeField] private int maxHealth = 5;
    private int currentHealth;

    private void Awake()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(int amount)
    {
        currentHealth = Mathf.Max(currentHealth - amount, 0);
        HealthChanged?.Invoke(currentHealth);

        if (currentHealth == 0)
        {
            Died?.Invoke();
        }
    }
}

Aqui o ?. está correto: HealthChanged é um delegate do C#, não um objeto da Unity. E quem escuta, arquivo HealthLabel.cs:

using UnityEngine;

public class HealthLabel : MonoBehaviour
{
    [SerializeField] private PlayerHealth playerHealth;

    private void OnEnable()
    {
        playerHealth.HealthChanged += UpdateLabel;
    }

    private void OnDisable()
    {
        playerHealth.HealthChanged -= UpdateLabel;
    }

    private void UpdateLabel(int value)
    {
        Debug.Log("Vida na tela: " + value);
    }
}

Inscrever no OnEnable e desinscrever no OnDisable é a regra que evita o erro de um objeto destruído continuar recebendo evento. Esse é o padrão Observer, um dos poucos padrões de projeto que valem desde o primeiro jogo.

O que estudar depois

Fica para depois, sem culpa:

  • LINQ (Where, Select, OrderBy): é útil em ferramenta de editor e em código que roda uma vez, mas aloca memória e não combina com Update.
  • async/await e Awaitable: a Unity 6 tem o tipo Awaitable para código assíncrono. Antes dele, aprenda coroutines, que resolvem os mesmos timers e sequências com menos regra nova.
  • Generics próprios, interfaces e herança profunda: você vai usar List<T> e GetComponent<T>() desde o dia um, mas escrever suas classes genéricas só faz sentido quando aparecer código repetido entre tipos diferentes.
  • Delegates avançados, reflection, unsafe: fora do escopo de quem está fechando o primeiro jogo.

O próximo passo prático: pegue os scripts deste post, coloque num projeto 2D vazio e faça uma cena com um jogador que toma dano de um inimigo, coleta moedas e mostra a vida no Console. Se você travar em if, for e variável, volte um degrau com lógica de programação para jogos. Se a dúvida é se vale pagar um curso de C# separado antes da Unity, o post curso de C# para jogos vale a pena compara os dois caminhos.

Perguntas frequentes

Preciso aprender C# antes de abrir a Unity?

Não. Dá para aprender C# já dentro da Unity, escrevendo scripts pequenos que mexem em objetos de verdade. O que ajuda muito é ter lógica de programação básica (variável, condição, repetição), porque aí a única novidade é a sintaxe e a API da engine.

Qual versão do C# a Unity 6.3 usa?

A Unity 6.3 LTS compila com Roslyn e suporta C# 9.0, com algumas exceções listadas no manual, como init-only setters e covariant return types. Na prática, quase tudo que um iniciante vai escrever funciona sem pensar em versão.

Onde aprender C# para Unity de graça?

A documentação oficial da Unity (Manual e Scripting API) é gratuita e é a referência que você vai consultar a vida toda. Combine com exercícios pequenos dentro de um projeto seu: um script por conceito, testado na cena, ensina mais do que uma playlist assistida sem abrir o editor.

LINQ e async são necessários para fazer jogo na Unity?

No primeiro mês, não. LINQ gera alocação de memória e quase nunca aparece em código que roda todo frame, e async tem regras próprias na Unity (o tipo Awaitable). Aprenda primeiro tipos, condições, loops, métodos, classes e listas, e volte nesses tópicos quando um problema real pedir.