Voltar para o Blog
Quest Log

Como Criar um Jogo de Terror na Unity: o Básico

Corredor escuro em primeira pessoa iluminado só pelo cone de uma lanterna, com névoa e uma porta entreaberta no fim

Como criar um jogo de terror na Unity: lanterna com bateria, escuro legível no URP, som 3D, porta que range e susto por trigger, com código C# do Unity 6.3.

Ferramenta:UnityC#

Como Criar um Jogo de Terror na Unity: o Básico

Para criar um jogo de terror na Unity você precisa de cinco peças: controle em primeira pessoa, uma lanterna com bateria, um cenário escuro que continua legível, som 3D que diz de onde vem o perigo e eventos disparados por trigger, como a porta que range ou o susto no fim do corredor. Este guia monta cada uma em C# para o Unity 6.3 LTS com a URP, e no fim explica a diferença entre susto barato e tensão de verdade.

A parte técnica é pequena. O difícil num jogo de terror é a dosagem: escuro demais vira frustração, susto demais vira previsível. Por isso cada seção traz os números de partida que eu uso e o motivo deles.

O mínimo de um jogo de terror

Um protótipo de terror que assusta cabe numa casa com três cômodos e um corredor. A lista mínima:

  • Jogador em primeira pessoa que anda devagar (correr tira a tensão).
  • Lanterna que o jogador controla e que pode acabar.
  • Ambiente escuro com névoa curta e pós-processamento discreto.
  • Som ambiente constante e sons pontuais posicionados no espaço.
  • Uma porta que abre com interação.
  • Um evento de susto disparado quando o jogador cruza um ponto.

O que fica de fora: inimigo com IA, inventário, quebra-cabeças. Essas peças vêm depois que o clima funciona. Se você quer entender o lado de design, o que de fato dá medo e como o ritmo funciona, o post como fazer um jogo de terror indie trata disso sem depender de engine. Aqui o foco é a implementação na Unity.

Crie o projeto com o template Universal 3D. Ele já vem com a URP configurada, um Volume global na cena de exemplo e o Input System ativo.

Controle em primeira pessoa

O controle usa CharacterController, que resolve colisão com paredes e degraus sem física de corpo rígido. A câmera é filha do jogador, na altura dos olhos (y = 1,6). As ações Move e Look já existem no mapa Player do asset padrão de input do Unity 6.

Arquivo FirstPersonController.cs:

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))]
public class FirstPersonController : MonoBehaviour
{
    [SerializeField] private Transform cameraPivot;
    [SerializeField] private float walkSpeed = 2.2f;
    [SerializeField] private float lookSensitivity = 0.12f;
    [SerializeField] private float gravity = -15f;

    private CharacterController controller;
    private InputAction moveAction;
    private InputAction lookAction;
    private float pitch;
    private float verticalVelocity;

    private void Awake()
    {
        controller = GetComponent<CharacterController>();
        moveAction = InputSystem.actions.FindAction("Player/Move");
        lookAction = InputSystem.actions.FindAction("Player/Look");
        Cursor.lockState = CursorLockMode.Locked;
    }

    private void Update()
    {
        Vector2 look = lookAction.ReadValue<Vector2>() * lookSensitivity;
        transform.Rotate(Vector3.up, look.x);
        pitch = Mathf.Clamp(pitch - look.y, -80f, 80f);
        cameraPivot.localRotation = Quaternion.Euler(pitch, 0f, 0f);

        Vector2 input = moveAction.ReadValue<Vector2>();
        Vector3 move = transform.right * input.x + transform.forward * input.y;

        if (controller.isGrounded && verticalVelocity < 0f)
        {
            verticalVelocity = -2f;
        }
        verticalVelocity += gravity * Time.deltaTime;

        Vector3 velocity = move * walkSpeed + Vector3.up * verticalVelocity;
        controller.Move(velocity * Time.deltaTime);
    }
}

A velocidade de 2,2 m/s é de propósito: é uma caminhada cautelosa. Se o jogador consegue atravessar a casa em 10 segundos, nenhum corredor dá medo. Para um controle com corrida, pulo e agachar, veja o FPS controller em primeira pessoa na Unity; aqui ele foi reduzido ao que o terror pede.

Jogo de terror na Unity: lanterna com Spot Light

A lanterna é uma Spot Light filha da câmera, posicionada um pouco à direita e abaixo (x = 0,25, y = -0,2), como se estivesse na mão. Valores de partida no Inspector:

  • Range: 18
  • Spot Angle (Outer): 45, Inner: 25
  • Intensity: 8 (com a unidade padrão da URP)
  • Shadow Type: Soft Shadows
  • Color: levemente amarelada, algo como RGB 255, 240, 210

O cone com borda suave e sombra é o que transforma a lanterna em ferramenta de medo: cada objeto que passa na frente projeta uma sombra que se mexe.

Arquivo Flashlight.cs:

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(Light))]
public class Flashlight : MonoBehaviour
{
    [SerializeField] private float maxBattery = 120f;
    [SerializeField] private float drainPerSecond = 1f;
    [SerializeField] private float maxIntensity = 8f;
    [SerializeField] private float lowBatteryThreshold = 0.2f;
    [SerializeField] private AudioSource clickSound;

    private Light spot;
    private float battery;
    private bool isOn = true;

    public float Battery01 => battery / maxBattery;

    private void Awake()
    {
        spot = GetComponent<Light>();
        battery = maxBattery;
    }

    private void Update()
    {
        if (Keyboard.current != null && Keyboard.current.fKey.wasPressedThisFrame)
        {
            isOn = !isOn;
            if (clickSound != null) clickSound.Play();
        }

        if (isOn && battery > 0f)
        {
            battery = Mathf.Max(0f, battery - drainPerSecond * Time.deltaTime);
        }

        spot.enabled = isOn && battery > 0f;
        spot.intensity = CalculateIntensity();
    }

    private float CalculateIntensity()
    {
        float charge = Battery01;
        if (charge > lowBatteryThreshold) return maxIntensity;

        float dim = Mathf.Lerp(0.3f, 1f, charge / lowBatteryThreshold);
        float flicker = Mathf.PerlinNoise(Time.time * 8f, 0f) > 0.25f ? 1f : 0.2f;
        return maxIntensity * dim * flicker;
    }

    public void Recharge(float amount)
    {
        battery = Mathf.Min(maxBattery, battery + amount);
    }
}

Dois minutos de bateria com a lanterna sempre acesa, e pilhas espalhadas pelo mapa chamando Recharge. Abaixo de 20% a luz enfraquece e começa a piscar com Mathf.PerlinNoise, que gera um tremor irregular e não um pisca-pisca de metrônomo. A bateria é a primeira decisão real que o jogador toma: economizar luz ou enxergar.

Iluminação escura: post processing e névoa no URP

O erro mais comum em terror é deixar a cena preta. Preto absoluto não dá medo, dá vontade de aumentar o brilho do monitor. A meta é: o jogador enxerga o contorno do cômodo sem a lanterna, mas não os detalhes.

Luz ambiente. Abra Window > Rendering > Lighting, aba Environment. Em Environment Lighting, troque Source para Color e use um azul bem escuro (algo como RGB 12, 14, 22). O azul frio no escuro e o amarelo quente da lanterna criam contraste de cor, que lê melhor que contraste de brilho.

Névoa. Na mesma aba, em Other Settings, marque Fog, modo Exponential Squared, cor igual à da luz ambiente e densidade entre 0,06 e 0,1. A névoa esconde o fim dos corredores e faz a lanterna "furar" o escuro. A névoa da URP é a névoa clássica por distância; névoa volumétrica nativa, com raios de luz visíveis no ar, é recurso da HDRP. Na URP, quem quer esse efeito recorre a pacotes da Asset Store ou a soluções da comunidade.

Pós-processamento. A URP usa o sistema de Volumes. Se a cena não tem um, crie em GameObject > Volume > Global Volume, crie um perfil em New e adicione os overrides em Add Override > Post-processing:

  • Vignette: intensidade 0,35, escurece as bordas e puxa o olho para o centro.
  • Film Grain: intensidade 0,2 a 0,3, esconde o banding do escuro e dá textura.
  • Color Adjustments: Post Exposure entre -0,5 e 0, Saturation -20.
  • Tonemapping: modo ACES ou Neutral, para o escuro não virar cinza lavado.

Confira se a câmera tem Post Processing marcado no componente Camera; sem isso, nenhum override aparece. Teste sempre com o jogo em tela cheia e com o brilho do monitor no padrão. Se puder, ofereça um ajuste de brilho no menu ligado ao Post Exposure.

Som 3D e ambiente

Som é metade do terror. Duas camadas:

Ambiente: um AudioSource na cena com Spatial Blend: 0 (2D), Loop marcado, volume baixo, tocando um drone grave ou vento. Ele nunca para, e é o silêncio dele (quando você desliga de propósito) que avisa que algo vai acontecer.

Sons posicionados: passos no andar de cima, uma torneira pingando, o rangido da porta. Cada um num AudioSource com Spatial Blend: 1 (3D), Min Distance 1 e Max Distance entre 10 e 20, Volume Rolloff: Logarithmic. O jogador ouve de qual lado vem o som porque o Audio Listener está na câmera. Use fone ao testar.

O post sobre áudio e sound design para jogos aprofunda camadas, mixagem e onde achar sons livres. Para o protótipo, 5 ou 6 sons bem escolhidos fazem mais que 50 genéricos.

Com lanterna, escuro e som funcionando, você já tem clima. O que vem a seguir é onde muita gente para: amarrar eventos, ritmo, inimigo, menu e salvar o progresso num jogo que alguém termina, e ajustar tudo a partir do teste com outras pessoas. Fazer isso sozinho, sem um roteiro de produção, costuma terminar num corredor bonito e nenhum jogo.

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

Portas, interação e eventos por trigger

A interação usa um raycast curto saindo da câmera. Quando o jogador aperta a tecla de interagir olhando para algo com o componente Interactable, o método é chamado.

Arquivo Interactor.cs (na câmera):

using UnityEngine;
using UnityEngine.InputSystem;

public class Interactor : MonoBehaviour
{
    [SerializeField] private float reach = 2.2f;
    [SerializeField] private LayerMask interactMask;

    private InputAction interactAction;

    private void Awake()
    {
        interactAction = InputSystem.actions.FindAction("Player/Interact");
    }

    private void Update()
    {
        if (!interactAction.WasPressedThisFrame()) return;

        Ray ray = new Ray(transform.position, transform.forward);
        if (Physics.Raycast(ray, out RaycastHit hit, reach, interactMask))
        {
            Door door = hit.collider.GetComponentInParent<Door>();
            if (door != null)
            {
                door.Toggle();
            }
        }
    }
}

Arquivo Door.cs (num objeto vazio no eixo da dobradiça, com a malha da porta como filha):

using System.Collections;
using UnityEngine;

public class Door : MonoBehaviour
{
    [SerializeField] private float openAngle = 95f;
    [SerializeField] private float openDuration = 1.6f;
    [SerializeField] private AudioSource creak;

    private bool isOpen;
    private bool isMoving;
    private Quaternion closedRotation;

    private void Awake()
    {
        closedRotation = transform.localRotation;
    }

    public void Toggle()
    {
        if (isMoving) return;
        StartCoroutine(Swing(!isOpen));
    }

    private IEnumerator Swing(bool open)
    {
        isMoving = true;
        if (creak != null)
        {
            creak.pitch = Random.Range(0.9f, 1.1f);
            creak.Play();
        }

        Quaternion from = transform.localRotation;
        Quaternion to = open ? closedRotation * Quaternion.Euler(0f, openAngle, 0f) : closedRotation;
        float t = 0f;
        while (t < 1f)
        {
            t += Time.deltaTime / openDuration;
            float eased = Mathf.SmoothStep(0f, 1f, t);
            transform.localRotation = Quaternion.Slerp(from, to, eased);
            yield return null;
        }

        isOpen = open;
        isMoving = false;
    }
}

A porta leva 1,6 segundo para abrir, não 0,3. Porta lenta com rangido é suspense; porta rápida é só porta. O pitch aleatório evita que o jogador decore o som.

Para o susto, um trigger: um BoxCollider com Is Trigger no meio do corredor. O jogador precisa de um Rigidbody ou CharacterController para disparar eventos de trigger; o CharacterController resolve.

Arquivo ScareTrigger.cs:

using UnityEngine;
using UnityEngine.Events;

[RequireComponent(typeof(Collider))]
public class ScareTrigger : MonoBehaviour
{
    [SerializeField] private UnityEvent onTriggered;
    [SerializeField] private float delay = 0f;
    [SerializeField] private bool oneShot = true;

    private bool fired;

    private void OnTriggerEnter(Collider other)
    {
        if (fired && oneShot) return;
        if (!other.CompareTag("Player")) return;

        fired = true;
        Invoke(nameof(Fire), delay);
    }

    private void Fire()
    {
        onTriggered.Invoke();
    }
}

O UnityEvent deixa ligar qualquer coisa no Inspector: tocar um som 3D atrás do jogador, apagar a luz do cômodo, bater uma porta, ativar uma silhueta no fim do corredor que some 2 segundos depois. Um mesmo script cobre todos os eventos do jogo.

Tensão vs jump scare

Jump scare é o som alto com imagem súbita. Funciona uma vez e cobra caro: depois do terceiro, o jogador espera o próximo e o medo vira irritação. Tensão é a expectativa de que algo vai acontecer, e ela se constrói com as ferramentas que você já montou:

  • Silêncio: desligue o som ambiente 5 segundos antes de um evento. O jogador percebe a ausência.
  • Pista sonora: passos no andar de cima antes de o jogador subir. Ele sobe sabendo que algo está lá.
  • Recurso escasso: bateria em 15% no corredor mais longo.
  • Falso alarme: um gato derrubando uma lata. Alivia, e o próximo evento pega o jogador relaxado.
  • Evento que não ataca: a silhueta que aparece e some. Nada aconteceu, e o jogador passa a olhar para todo canto.

Uma regra prática para o protótipo: no máximo um jump scare a cada 10 minutos de jogo, e sempre depois de um trecho de tensão, nunca do nada.

Erros comuns: escuro demais, susto previsível

  • Escuro demais. Se o jogador não acha a porta sem a lanterna, o ambiente está escuro demais. Suba a luz ambiente até os contornos aparecerem.
  • Névoa densa demais. Densidade acima de 0,15 esconde o próprio cômodo. A névoa deve engolir o fim do corredor, não a parede ao lado.
  • Susto no mesmo ritmo. Se cada sala tem um evento na entrada, o jogador aprende o padrão. Varie posição, intervalo e tipo.
  • Trigger que dispara de novo. Sem oneShot, voltar pelo corredor repete o susto e quebra o efeito.
  • Som 2D em evento posicionado. Rangido com Spatial Blend 0 toca "dentro da cabeça" e o jogador não sabe para onde olhar.
  • Pós-processamento sem Post Processing na câmera. O Volume existe, mas nada muda. Confira a caixa na câmera.

Próximo passo

Monte a casa com três cômodos usando cubos (greybox), coloque a lanterna, a névoa, dois sons posicionados, uma porta e um único evento de susto. Peça para alguém jogar sem você explicar nada e observe onde a pessoa hesita: é ali que o próximo evento deve morar. Depois, leia o guia de design de terror indie para planejar o ritmo da fase inteira e o de áudio para jogos para refinar a mixagem.

Perguntas frequentes

Qual render pipeline usar num jogo de terror na Unity?

A URP atende bem a maioria dos jogos de terror indie: tem luzes em tempo real, sombras, névoa por distância e pós-processamento com Vignette e Film Grain, e roda em máquinas modestas. A HDRP traz névoa volumétrica nativa e iluminação mais realista, mas pede hardware mais forte e mais tempo de ajuste.

Como fazer uma lanterna na Unity?

Crie uma Spot Light como filha da câmera do jogador, ajuste o ângulo do cone e o alcance, e ligue as sombras. Um script lê uma tecla para ligar e desligar e desconta bateria enquanto ela está acesa, reduzindo a intensidade quando a carga fica baixa.

Como fazer som 3D na Unity?

No AudioSource, coloque Spatial Blend em 1 (3D) e ajuste Min Distance e Max Distance. O som passa a mudar de volume e de lado conforme a posição do Audio Listener, que fica na câmera. Para passos e rangidos, é isso que faz o jogador saber de onde vem o perigo.

Jogo de terror é um bom primeiro projeto 3D?

É um dos melhores, porque o escopo é controlável: uma casa, uma lanterna, alguns sons e um evento bem colocado já assustam. O terror depende mais de ritmo, luz e som do que de muitos sistemas, então você aprende 3D sem precisar de combate, inventário ou IA complexa logo de cara.