Neste episódio da série Mini Joguinhos, mostro como criei um jogo de corrida de kart na Unity usando inteligência artificial para acelerar o desenvolvimento — do projeto vazio a uma corrida completa e jogável em aproximadamente duas horas.
O jogo foi desenvolvido na Unity 6 com URP e conta com um kart controlado pelo jogador, dois adversários guiados por IA e um circuito fechado. O sistema de direção usa física com Rigidbody, controles WASD, câmera em terceira pessoa e ajustes de aderência para deixar as curvas mais estáveis e divertidas.
📦 Projeto no GitHub: [ProjetoGitHub]
📌 Prompt completo:
Prompt — Jogo de Kart (Unity 6, URP)
Crie um jogo de corrida de Kart completo e jogável neste projeto Unity, do zero, sem sobrar nada para
eu consertar depois.
Este prompt já traz as medidas reais dos modelos e as armadilhas conhecidas (cada uma custou
horas de diagnóstico numa execução anterior). Use os números daqui em vez de redescobri-los, mas
confirme as medidas na Fase 0 — se algo não bater, pare e me avise em vez de improvisar.
Execute na ordem das fases. Cada fase tem um portão de validação numérico: só avance depois de
o portão passar. Se falhar, conserte antes de seguir. Ao concluir cada fase, diga em uma linha o que
ficou pronto.
Contexto do projeto
— use UnityEngine.InputSystem (ex.: Keyboard.current.wKey.isPressed).
- Cena:
Assets/Scenes/SampleScene.unity(trabalhe nela; deve estar no Build Settings). - Prefabs em
Assets/Prefabs/:PistaReta,PistaCurva,Kart1,kart2,kart3
(k minúsculo em kart2/kart3). São variants de FBX (Assets/Models/) e contêm só malha — sem
Rigidbody nem Collider. - TextMeshPro NÃO está importado. Use
UnityEngine.UI.Textcom a fonteLegacyRuntime.ttf
(Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");Arial.ttfé inválido no Unity 6). - Os FBX de pista já trazem empties de encaixe nomeados:
Encaixe_Entrada/Encaixe_Saida(na curva,
com prefixoCurva_). O eixo +Y local desses empties aponta no sentido do percurso. Use-os.
Decisões de projeto (fixas — não mude)
1. Escala da pista = 190
Os prefabs PistaReta e PistaCurva vêm com escala 106 (pequena demais para 3 karts). Passe a
escala raiz dos DOIS prefabs para 190 editando o asset (PrefabUtility.LoadPrefabContents →transform.localScale = Vector3.one * 190f → SaveAsPrefabAsset → UnloadPrefabContents) e confirme
lendo o asset de volta. Tudo abaixo já está calculado para 190.
2. Tamanho dos karts (visual)
O kart é um GameObject raiz de física (Kart1, Kart2, Kart3; forward = +Z) com um filho Visual
(instância do prefab, sem colliders). Os modelos apontam no eixo X, então o filho é girado em Y:
| Raiz | Prefab do Visual | Visual.localRotation (Y) | Visual.localScale |
|---|---|---|---|
Kart1 (jogador) | Kart1 | −90° | 3,09 |
Kart2 (IA) | kart2 | +90° | 2,6 |
Kart3 (IA) | kart3 | +90° | 2,4 |
O jogador é um pouco maior de propósito (fica mais fácil de ver). BoxCollider proporcional à escala do
visual s (bounds do modelo: comprimento 0,98 no eixo X do modelo, largura 0,50, altura 0,49):size = (0,50·s·0,95 ; 0,49·s·0,96 ; 0,98·s·0,97), center = (0, size.y/2, 0) → base do collider em y=0.
Isto dá Kart1 1,47×1,45×2,94, Kart2 1,24×1,22×2,47, Kart3 1,14×1,13×2,28. (Validado: as IAs
completam a corrida normalmente com estes colliders.)
3. Chão = UM collider só; sem MeshCollider no asfalto
Não coloque MeshCollider nas peças de pista. Cada laje com seu próprio collider tem faces verticais
nas pontas e o kart desacelera/engancha na junção (bug real já reportado). A pista é perfeitamente plana:
a superfície de rolagem é um único BoxCollider (ver Fase 2). As paredes invisíveis cuidam da
contenção lateral.
4. Peças sobrepostas em 0,3% — aplicada por ÚLTIMO
Como último passo da Fase 2 (depois de gerar linha de centro, paredes, linha de chegada e waypoints, que
usam os encaixes na escala 190 exata), ponha localScale = 190 × 1,003 = 190,57 em cada peça. O pivô não muda,
então não acumula erro no fechamento. Cada peça invade ~3,4 cm da vizinha → nenhum vão, nem milimétrico,
entre as malhas. Não causa z-fighting visível. (O topo visual do asfalto fica ~0,6 mm abaixo de y=0; quem
define o y=0 real é o ChaoDaPista, então é inofensivo.)
5. Física arcade: atrito do PhysX zerado
Crie Assets/Materials/SemAtrito.physicMaterial (extensão .physicMaterial, sem “s” —.physicsMaterial gera um arquivo que não carrega): dynamicFriction=0, staticFriction=0,bounciness=0, frictionCombine=Minimum, bounceCombine=Minimum. Aplique em: chão, paredes e os
3 karts. Com o atrito padrão (0,6) a força de aceleração é anulada e o kart não sai do lugar, mesmo
com input, raycast e Rigidbody aparentemente corretos. A aderência passa a ser 100% do script.
Defina os valores e depois SaveAssets; confirme lendo o .physicMaterial em disco.
FASE 0 — Confirmar as medidas (não chute; meça uma vez)
Instancie cada peça de pista com rotação limpa Euler(-90,0,0), root em (0,0,0), e meça pelos
transforms dos empties de encaixe e por Renderer.bounds. Já sabemos (escala 190):
| PistaReta | PistaCurva | |
|---|---|---|
| Comprimento útil | 22,800 | arco de 30° (não 90°!), comprimento de arco 22,800 |
Largura do asfalto (Superficie_Asfalto) | 10,2974 | idem |
| Face interna das muretas (a partir do eixo) | ±5,0158 | idem |
| Raio da linha de centro | — | 43,54619 |
Encaixe_Entrada (local) | (0, −0,19475, +11,400) | (−2,53512, −0,19475, +12,31120) |
Encaixe_Saida (local) | (0, −0,19475, −11,400) | (+3,298775, −0,19475, −9,461199) |
| Heading entrada → saída | −Z → −Z (Δ 0°) | −Z → girado −30° (curva à esquerda) |
Consequências: 3 peças PistaCurva = 1 curva de 90°; só há curva à esquerda → circuito anti-horário.
Portão 0: informe suas medidas lado a lado com esta tabela. Se qualquer valor divergir mais de 1%,
pare e me avise (os modelos podem ser outros). Se bater, siga.
FASE 1 — Montar o circuito
Encadeie 30 peças pelos frames de encaixe (nomes Reta_00 … Curva_29, sob o pai Pista em (0,0,0)).
Sequência: 6 retas · 3 curvas · 3 retas · 3 curvas · 6 retas · 3 curvas · 3 retas · 3 curvas.
Por que 6/3/6/3 e não 4 retas diferentes: com 4 curvas de 90° idênticas, o deslocamento das curvas
se cancela, então o fechamento obriga retaA = retaC e retaB = retaD. Não dá para fechar com as 4
retas de comprimentos diferentes. (Isso já foi verificado.)
Algoritmo (cursor = posição P do encaixe atual + heading H em graus; começa em P=(0,0,0), H=180):
θ = H − 180
ry = Quaternion.Euler(0, θ, 0)
peça.localRotation = ry * Quaternion.Euler(-90, 0, 0)
peça.localPosition = P − ry * pEntrada // encaixa a entrada exatamente no cursor
P = peça.localPosition + ry * pSaida
H = H + Δ(peça) // reta 0 ; curva −30
Não some offset de altura no pai Pista. O termo P − ry*pEntrada já sobe cada peça de 0,19475 e
põe o encaixe em y=0 (erro clássico: somar de novo deixa o asfalto acima de y=0).
Portão 1 (numérico; nesta fase as peças ainda têm escala 190 exata — a sobreposição só entra no fim da Fase 2):
- erro de fechamento do cursor (posição final − inicial) < 1e-3 e ângulo final ≡ 180°;
- as 30 junções (
Encaixe_Saidada peça i vsEncaixe_Entradada i+1, incluindo a última→primeira),
medidas pelos transforms reais, com folga < 1e-3 e desalinhamento < 0,01°; - menor distância entre centros de asfalto de peças não-vizinhas > largura da pista (10,3) → sem
sobreposição acidental. Esperado: volta da linha de centro ≈ 684,0; bounds de todos os renderers daPista≈ 178 × 247.
Não aplique ainda o 190,57 (Decisão 4): a Fase 2 amostra a linha de centro pelos encaixes e eles precisam
estar na escala 190 exata.
FASE 2 — Física da pista
- Amostre a linha de centro por comprimento de arco (24 amostras por peça = 720; comprimento total
≈ 683,998): reta = linha; curva = arco de raio 43,54619 em torno deE + esquerda·R, girando −30°. Pista/ChaoDaPista: umBoxCollider(sem renderer),size = (bounds.x+20, 4, bounds.z+20)
dos renderers do asfalto (≈ 197,2 × 4 × 265,6), centro nos bounds em x/z, topo em y = 0
(position.y = −2), materialSemAtrito. Nenhum MeshCollider no asfalto.Pista/ParedesInvisiveis: 120 segmentos × 2 lados = 240 BoxColliders sem renderer, um por passo
de arco (≈ 5,70). Cada um emp ± esquerda·(5,0158 + 0,35),y = 1,25,rotation = LookRotation(tangente),size = (0,7 ; 2,5 ; passo + 0,3), materialSemAtrito. NomesParedeEsq_000…/ParedeDir_000….LinhaDeChegada(raiz da cena) em s = 68,40 ao longo da linha de centro (meio da reta principal,
3 peças depois do início daReta_00),rotation = LookRotation(tangente)(+Z = sentido do percurso):BoxCollidertriggersize (10,2974 ; 2,5 ; 3,0),center (0 ; 1,25 ; 0), +LapTrigger.
Faixa quadriculada visível (filhoFaixa): 20 colunas × 2 fileiras de cubos sem collider
(largura 10,2974/20, profundidade 1,0, altura 0,06, y=0,03), materiais URP/LitLinhaBranca/LinhaPreta
emAssets/Materials/, alternando ((f+c) % 2).Waypoints(raiz): 60 emptiesWP_00 … WP_59em s = 68,40 + (k + 0,5)·11,40 (oWP_00fica logo
depois da linha),rotation = LookRotation(tangente), cada um comBoxCollidertriggersize (10,2974 ; 2,5 ; 3,0)center (0;1,25;0)e o componenteCheckpoint.- Por último, aplique a sobreposição (Decisão 4):
localScale = 190,57nas 30 peças. Os pivôs não mudam:
confira que a maior folga entre encaixes virou uma sobreposição de ~7 cm e que as 30 peças continuam
nos mesmos lugares. Recalcule osizedoChaoDaPistapelos bounds finais (+20) se quiser.
Portão 2 (com a sobreposição já aplicada): em Play, solte cubos com Rigidbody (a 6 un. de altura) numa reta, no meio de uma curva e
encostado na borda: devem pousar em y = 0,500 e ficar. Dispare cubos a 30 u/s contra as paredes nos dois
lados, numa reta e numa curva: nenhum pode sair da pista. Waypoints: distância entre consecutivos
≈ 11,37–11,40 (inclusive WP_59→WP_00), giro total = −360°, nenhum “próximo atrás do atual”, todos com asfalto
embaixo (raycast). Varra a reta principal com raycast (passo 0,05) e confirme degrau = 0 e zero pontos sem chão.
FASE 3 — Kart do jogador + câmera
Monte Kart1 conforme a Decisão 2. Rigidbody: massa 150, linearDamping 0,5, angularDamping 5,interpolation Interpolate, collisionDetectionMode ContinuousDynamic,constraints = FreezeRotationX | FreezeRotationZ (a pista é plana: o kart nunca capota),centerOfMass = (0, −0,35, 0) no Awake.
KartController (input em Update, física em FixedUpdate; campos [SerializeField] para eu ajustar;
Input System novo: Keyboard.current; aceita setas também):
Wacelera ·Sfreia se está indo para frente e só vira ré depois de parar ·A/Dviram.- Raycast de chão de
transform.position + 0,3·up, alcance 1,0; sem chão → sem tração. - Aceleração via
AddForce(ForceMode.Acceleration)só enquantovelocidadeFrente < velocidadeMaxima. - Direção só responde em movimento:
fator = Clamp01(|vFrente| / velocidadeParaCurvaPlena)e inverte na ré
(sentido = vFrente ≥ 0 ? 1 : −1). - Suavize o “volante”:
direcaoSuave = MoveTowards(direcaoSuave, entrada, suavizacaoDirecao·dt)(teclado é liga/desliga). - Em alta a direção afrouxa:
fator *= Lerp(1, fatorCurvaAltaVelocidade, InverseLerp(velCurvaPlena, velMax, |vFrente|)). - Trava anti-rodada:
derrapagem = Angle(forward·sentido, velocidade);autoridade = Clamp01(1 − InverseLerp(anguloDeDerrapagemLimite, 90, derrapagem))(aplicada só com|v| > 0,5). corpo.angularVelocity = Vector3.zerono início deAplicarDirecao(essencial). O script é o dono
exclusivo do rumo: sem isso, uma raspada na parede injeta velocidade angular que briga com oMoveRotation
e o kart vira pião (medido: 7 travamentos e 102° fora do eixo em 3 voltas → 0 travamentos e 23° com a linha).- Giro:
MoveRotation(rotation * Euler(0, direcaoSuave·forcaCurva·fator·sentido·autoridade·dt, 0)). - Aderência lateral:
AddForce(−componenteLateral · aderenciaLateral, Acceleration); eforcaParaBaixo(gravidade extra)
quando no chão. - Trava total (velocidades zero todo
FixedUpdate) enquanto!gerente.PodeMover; métodoReposicionar(pos, rot)
que resolve o Rigidbody se ainda for nulo (é chamado antes doAwakedele).
Valores finais (Kart1, já calibrados para a pista 190 — K = 190/106 = 1,7925):
| Campo | Valor | Campo | Valor |
|---|---|---|---|
velocidadeMaxima | 46,60 | forcaCurva (°/s) | 80 |
velocidadeMaximaRe | 16,13 | velocidadeParaCurvaPlena | 14,34 |
aceleracao | 39,43 | suavizacaoDirecao | 5 |
forcaFreio | 60,94 | anguloDeDerrapagemLimite | 40 |
desaceleracaoLivre | 7 | fatorCurvaAltaVelocidade | 0,6 |
aderenciaLateral | 30 | forcaParaBaixo | 12 |
Regra de reescala (se um dia mudar a pista): comprimentos, velocidades e acelerações escalam com K;
taxas angulares (°/s) e amortecimentos (1/s) NÃO escalam —forcaCurva,suavizacaoDirecao,aderenciaLateral,desaceleracaoLivre,forcaParaBaixoficam iguais.
CameraFollow (em LateUpdate, na Main Camera): terceira pessoa usando só o yaw do kart (senão a câmera
balança com qualquer solavanco). deslocamento (0 ; 3,8 ; −8,5); posição por SmoothDamp comsuavizacaoPosicao = 0,08 (a 46 u/s um valor maior deixa a câmera 6+ un. atrasada); rotação porSlerp(1 − exp(−8·dt)); mira em posição + up·1,1 + frente·12,55; método PosicionarImediatamente().
Portão 3 (obrigatório): dirija o Kart1 pelo circuito inteiro, sem capotar, sem atravessar parede, sem
travar, com a câmera acompanhando. Atenção — como testar sem teclado físico: o Input System descarta
eventos de teclado quando a Game view não tem foco. Use um script piloto temporário que injetaInputSystem.QueueStateEvent(Keyboard.current, keyboardState) (com Key.W/A/S/D) e traga o Editor para
o foco (editor_focus); histerese na direção (aperta a 3,5°, solta a 1,2°) para não oscilar. Critérios:
3 voltas seguidas, 0 travamentos, inclinação 0°, 0 frames sem asfalto, desvio lateral < 2,8. Apague o
piloto e todo script de teste ao terminar (remova o componente da cena antes de apagar o .cs).
Se você não conseguir injetar teclado, valide com as IAs (Fase 4) e diga explicitamente que o
teste com WASD real fica por minha conta.
FASE 4 — Karts de IA
Kart2 e Kart3 = mesma física do jogador (Rigidbody + BoxCollider + SemAtrito, Decisão 2), semtransform.position teleportando. KartAI reaproveita exatamente a mesma lógica de direção
(suavização não é necessária: o alvo já é contínuo), com angularVelocity = 0, trava anti-rodada,
afrouxamento em alta e aderência lateral idênticos. O “teclado” é trocado por:
- Alvo:
AvancarAlvoavança enquanto o waypoint atual estiver a< raioDeTrocaou já ficou para
trás (Dot(até, forward) < 0e a< 2,2·raioDeTroca).PontoDeMira= primeiro dos próximos 6
waypoints a≥ distanciaAntecipacao. Direção =Clamp(SignedAngle(forward, até)/anguloDeDirecaoTotal, −1, 1). - Velocidade nas curvas: olha o waypoint
+4à frente;curvatura = |SignedAngle|;alvo = Lerp(velocidadeMaxima, velocidadeMinimaEmCurva, Clamp01(curvatura/42));
acelera sev < alvo − 0,4, freia sev > alvo + 1,5. - Destravamento:
|v| < 1,2por 1,5 s → 0,8 s de ré com direção invertida. Reposicionar(pos, rot)(também resolve o Rigidbody se nulo) edesvioLateral(empurra o traçado ao longo dewaypoint.right, para os karts não andarem empilhados).- Trava total enquanto
!gerente.PodeMover.
Valores finais:
| Campo | Kart2 | Kart3 |
|---|---|---|
velocidadeMaxima | 42,12 | 39,43 |
velocidadeMinimaEmCurva | 24,20 | 22,41 |
desvioLateral | +2,2 | −2,2 |
aceleracao / forcaFreio | 37,64 / 46,60 | 37,64 / 46,60 |
distanciaAntecipacao / raioDeTroca | 19,72 / 12,55 | 19,72 / 12,55 |
waypointsDeAntecipacaoDeCurva | 4 | 4 |
forcaCurva / velocidadeParaCurvaPlena / anguloDeDirecaoTotal | 80 / 14,34 / 28 | 80 / 14,34 / 28 |
anguloDeDerrapagemLimite / fatorCurvaAltaVelocidade | 40 / 0,6 | 40 / 0,6 |
aderenciaLateral / forcaParaBaixo | 30 / 12 | 30 / 12 |
Portão 4: com cada IA sozinha (desative as outras para isolar), 2 voltas completas em Play com:
0 travamentos, 0 frames na contramão, 0 frames sem asfalto, desalinhamento máximo < ~25° e passagens
em ordem por todos os waypoints. (Referência: ~2 voltas em 43–57 s; velocidades diferentes entre as duas.)
FASE 5 — Regras da corrida
Grid (referencial da LinhaDeChegada: −forward = atrás, right = lateral; todos com rotation da
linha; Rigidbody e transform posicionados juntos):
| Kart | Atrás da linha | Lateral (right) |
|---|---|---|
Kart2 (pole) | 9,30 | +3,2 |
Kart1 (jogador) | 15,00 | −3,2 |
Kart3 | 20,70 | 0,0 |
RaceManager (participantes Voce/Kart 2/Kart 3 configurados no Inspector; memoriza a pose de largada de cada um):
- ⚠
PrepararLargada()roda noStart, NÃO noAwake. Ela reposiciona os karts e oAwakedeles ainda
pode não ter rodado (NullReference que aborta a largada).Awakesó carrega waypoints e monta os corredores. - Contagem regressiva 3 → 2 → 1 → GO! (
3 s+GO!por 1 s) com os karts travados;PodeMoversó étrue
emCorrendo. Estados:Contagem → Correndo → Encerrada. - 2 voltas. Termina quando o primeiro completa (
Encerradapara todos os karts). - Volta à prova de trapaça: cada
Checkpointsó é aceito seindice == proximoCheckpointdaquele kart
(checkpointsNaVolta++).LapTriggerinforma se a passagem foi para frente (Dot(velocidade, linha.forward) > 0,5);
a volta só conta separaFrente && checkpointsNaVolta >= 60. Ré por cima da linha: ignora e não reseta nada.
Toda passagem válida para frente zeracheckpointsNaVolta/proximoCheckpoint. - Posição em tempo real: ordena por (terminou → ordem de chegada) → voltas →
checkpointsNaVolta→ menor distância
até o próximo waypoint. - Tempo total por kart (acumula em
UpdateenquantoCorrendoe não terminou). ReiniciarCorrida()=PrepararLargada()(zera voltas, tempos, posições, poses, câmera).Checkpoint.Configurar(indice, gerente)eLapTrigger.Configurar(gerente)para ligar por script na criação da cena.
Portão 5 (roda uma corrida inteira, com números):
- Em
Contagem, os 3 karts comvel = 0e a ordem do grid correta; - as 2 voltas contam certo para os 3, a corrida termina quando o primeiro completa;
- teste de trapaça em 3 casos (cobaia = um kart com a IA desligada, em Play): A 60/60 checkpoints
cruzando de ré → voltas 0; B 30/60 cruzando para frente → voltas 0; C 60/60 para frente → voltas 1.
(Se a corrida já tiver encerrado, reinicie antes de cada caso.)
FASE 6 — UI (uGUI, sem TextMeshPro)
CanvasCorrida(ScreenSpaceOverlay) comCanvasScalerScale With Screen Size, referência
1920×1080,matchWidthOrHeight = 0,5, +GraphicRaycastere o componenteRaceUI.- EventSystem com
InputSystemUIInputModule(AssignDefaultActions()seactionsAssetfor nulo).
Não deixe oStandaloneInputModule— com o Input System exclusivo ele quebra e o botão não responde. - Canto superior direito (
PainelInfo, âncora/pivô (1,1), pos (−30,−30), 360×150, fundo preto α 0,45):TextoPosicao(64 pt, negrito, branco, alinhado à direita) →"2º / 3";TextoVolta(40 pt, amarelo) →"Volta 1/2"
(Clamp(voltas+1, 1, total)). - Centro:
TextoContagem(180 pt, negrito, comOutlinepreto), mostra3/2/1/GO!; desabilite o
Text quando não houver nada a mostrar. - Painel final (
PainelFinal, tela cheia, fundo preto α 0,72, começa inativo) com umaCaixa900×580
(fundo#171A24):TextoResultado(58 pt) →"VOCE VENCEU!"(dourado) ou"VOCE FICOU EM 3º LUGAR";
cabeçalho# / KART / TEMPO / VOLTAS; 4Textde coluna separados (ColunaPosicao/Nome/Tempo/Voltas,
âncoras x = 0,06 / 0,20 / 0,56 / 0,82,anchoredPosition (0;−196),sizeDelta (300;240), 34 pt,supportRichText = true) — a fonte não é monoespaçada, então uma string só desalinha; linhas coloridas por
rich text: 1º dourado#FFD633, quem não terminou cinza#8C93A3, demais branco; o jogador marcado com"(voce)". BotãoREINICIAR(340×78, azul) chamaRaceManager.ReiniciarCorrida(). RaceUI.Updateatualiza HUD/contagem; ao passar deEncerradapara outro estado esconde o painel.
Portão 6: amostre HUD + ordem durante uma corrida e veja a posição mudar (ex.: 2º/3 → 3º/3);
a volta vira Volta 2/2; o placar aparece ao terminar; dispare o clique do botão pelo EventSystem
(ExecuteEvents.Execute(...pointerClickHandler), após confirmar por RaycastAll de UI que o botão está sob o ponto)
e confirme: estado Contagem, painel escondido, todos com voltas 0, tempo 0, cp 0 e de volta ao grid
(9,30 / 15,00 / 20,70 atrás da linha), depois uma nova corrida completa.
Armadilhas conhecidas (cada uma já custou tempo — respeite)
| Sintoma | Causa | Regra |
|---|---|---|
| Kart não sai do lugar (input OK, chão OK) | Atrito padrão 0,6 do PhysX anula a força | SemAtrito.physicMaterial em chão, paredes e karts |
| Kart vira pião nas paredes | Raspada injeta velocidade angular | angularVelocity = 0 em AplicarDirecao (jogador e IA) |
| Kart desacelera na junção das peças | 30 MeshColliders → faces verticais nas pontas | 1 BoxCollider de chão + peças sobrepostas 0,3% |
| Asfalto acima de y=0 | Offset somado no pai Pista além do por peça | Não desloque o pai Pista |
| Peças encolhem para ~1/190 | localScale é absoluto, não multiplica o do prefab | localScale = 190 × 1,003 |
NullReferenceException na largada | RaceManager.Awake reposiciona antes do Awake dos karts | PrepararLargada() no Start + guarda if (corpo == null) em Reposicionar |
Asset .physicsMaterial não carrega / valores voltam ao padrão | Extensão errada | .physicMaterial (sem “s”); confira em disco |
| Botão Reiniciar não responde | StandaloneInputModule com Input System exclusivo | InputSystemUIInputModule |
| Colunas do placar desalinhadas | Fonte não monoespaçada | Um Text por coluna |
| Play congela quando o Editor perde o foco | runInBackground = false | PlayerSettings.runInBackground = true e confirme runInBackground: 1 em ProjectSettings.asset |
| Teclado injetado não chega ao kart | Input System descarta eventos sem foco na Game view | editor_focus antes; senão valide com IA |
| Raycast dá resultado velho | Colliders movidos sem sincronizar | Physics.SyncTransforms() |
GameObject.Find devolve nulo / erro de NRE em script de cena | Find ignora objetos inativos | Ache pelas raízes (GetRootGameObjects) ou reative antes |
eval: 'Object' is an ambiguous reference | System.Object × UnityEngine.Object | Escreva UnityEngine.Object |
FindFirstObjectByType obsoleto | API descontinuada | FindAnyObjectByType |
| Captura de tela mostra pista “fantasma”/UI em blocos | Frame antigo do Editor | Capture duas vezes e confie em raycast/números, não só na imagem |
| Corpo de teste “não anda” | Rigidbody dormindo | sleepThreshold = 0 / WakeUp() no teste |
Padrões de código
- Scripts em
Assets/Scripts/, um arquivo por classe:KartController,KartAI,RaceManager,LapTrigger,Checkpoint,RaceUI,CameraFollow. Só estes 7 ao final — nada de scripts de teste. - Física em
FixedUpdate, input emUpdate. Nada deInput.GetAxis/GetKey. - Comentários em português explicando o que não é óbvio (o porquê: atrito,
angularVelocity,StartvsAwake). - Crie os scripts, espere a recompilação terminar (
recompile_status = completed, sem erros) e só então anexe
os componentes. Ligue as referências viaSerializedObjecte confira que nenhum campo ficou vazio. - Deixe a cena salva, com tudo ligado no Inspector, pronta para apertar Play. Ao final: 0 scripts quebrados
(Missing) na cena, 0 erros no console, 0 arquivos de teste (_PilotoDeTeste,_Monitor…,Assets/Temp,Assets/_shot), e recarregue a cena do disco e rode uma corrida completa como teste de fumaça.
Entrega final
Diga: o que foi criado; os números medidos em cada portão; o que não pôde ser testado (por exemplo, dirigir
com WASD real); quais valores ajustar no Inspector para deixar mais rápido/mais difícil
(velocidadeMaxima, aceleracao, forcaCurva, aderenciaLateral, suavizacaoDirecao,fatorCurvaAltaVelocidade — este último é o que obriga a frear para a curva); e como testar.
