Aprendendo Unreal

DEVLOG #01

Nos últimos anos venho trabalhando em lookdev e texturas para animações e jogos. O contato mais frequente com equipes de desenvolvimento foi tornando minha curiosidade sobre desenvolver jogos mais palpável. Algo que sempre sonhei criar, mas que sempre pareceu “um bicho de sete cabeças”. Programação era algo que via como fora do meu alcance.

Então resolvi fazer este experimento, tentar sozinho desenvolver um jogo na Unreal. Já vim acumulando algum conhecimento básico da engine para auxiliar meus colegas nos projetos, e também fazendo alguns tutoriais na internet, principalmente sobre Blueprints.

Defini como objetivo criar um jogo no estilo do Traffix, um jogo mobile sobre controlar semáforos para tentar fazer com que os veículos completem seu trajeto sem colidir em um tempo viável.

Neste primeiro devlog, monto o sistema de movimento básico para o carro IA usando Blueprints, explicando a lógica de MoveForward, um macro criado dentro do evento e o fluxo geral do BP_Car_IA.

É um projeto experimental para aprender e consolidar fundamentos de um desenvolvimento de jogos, e estou fazendo ele com base no que já aprendi e com auxílio do ChatGPT.
Primeiro Actor

BP_Car_IA

O que é: Esse é o ator principal do jogo. Um veículo que deve andar por uma rota, obedecer os semáforos que encontrar, interagir com outros veículos e tentar completar sua rota sem colidir em nada. Existem vários outros veículos iguais a ele, todos tentando fazer o mesmo objetivo.

Esse Actor é regido por uma MainCollision, dentro dela temos uma StaticMesh e alguns sensores de colisão:
No Blender, criei um modelo 3D simples, que importei na Unreal como a Static Mesh desse ator:
A ideia aqui é ter um ponto de início e validar o fluxo Blender > Unreal, definindo padrões de posição XYZ, exportação em formato .fbx e escala entre os programas. Definir esse padrão agora evita imprevistos mais a frente, como modelos finais indo em escalas erradas, rotação errada e outros.
Begin Play

Event Graph

Aqui é onde toda lógica do BP_CarIA ocorre. No EventGraph desse ator criei algumas variáveis base: MaxSpeed e CurrentSpeed, e defini para que no BeginPlay o veículo sempre seja gerado já na MaxSpeed. Assim ela já entra em cena na sua velocidade máxima.
Adicionei um Set Timer by Function Name. Esse timer vai executar a função definida repetidamente (importante ativar Looping), acionado pela variável Time [setada a 0.05]. Essa é a taxa de atualização desse cálculo.

Esse Timer funciona similar a um Tick, mas sem depender da taxa de atualização dos frames do jogo (FPS). Calcular assim evita que a taxa de atualização seja vinculado a um FPS maior ou menor, evitando bugs/exploits de velocidade.

Esse timer ativa a função MoveForward. Será nela que criaremos a lógica de movimentação do veículo.
Primeira Function

Move forward

Essa função inicialmente faz uma checagem de Booleans, condições que serão definidas como true/false em outras áreas para estabelecer se o ator está apto a se movimentar ou não:

Traffic Ligh Allows – Checa se tem algum semáforo a sua frente solicitando parada.
FrontClear – Detecta se tem outro veículo a sua frente.
Is Crashed NOT – Se este ator colidiu, ignora o sistema de movimentação e ativará o de física.

Se todas condições forem aprovadas o veículo irá Acelerar. Se não, tentará Frear. Ambos resultados do boolean passarão pelo Macro_MoveOrBreak:
Macro

Move Or Break

Resultado final do Macro_MoveOrBreak..

Nesse Macro fazemos o calculo de movimento do ator. Chegar no resultado acima foi um desafio. As primeiras tentativas ficaram enormes e bagunçadas, não conseguia entender os parâmetros de movimento, muito menos chegar em uma mesma fórmula que pudesse otimizar para as duas condições.
Primeira versão do MoveForward. tinha muitas partes do código repetindo para True/False, mas não conseguia fazer funcionar nem simplificar. Depois de fazer funcionar para o True, consegui simplificar e criar o Macro para repetir o cálculo para False.
O que me levou ao resultado esperado foi utilizar um node que não conhecia:

FInterpTo (Float Interpolate To) – É um node usado no Unreal Engine para suavizar a transição de um valor float até outro valor desejado ao longo do tempo. Em vez de pular diretamente do valor atual para o valor alvo, ele cria uma interpolação gradual, muito útil para movimentos, animações e transições mais naturais.
Como ele funciona:
ParâmetroFunçãoNode
CurrentValor atual (de onde começa)CurrentSpeed
TargetValor para o qual queremos chegarTargetSpeed
Delta TimeTempo entre frames – controla suavidade independente de FPSWorldDeltaSeconds
Interp SpeedVelocidade da interpolação (quanto maior, mais rápido chega ao alvo)Acceleration ou Deceleration
Return ValueO novo valor suavizado, que deve substituir o valor atualCurrentSpeed
O que ele faz é pegar o CurrentSpeed inicial e alterá-lo gradualmente até um novo CurrentSpeed. Para o macro funcionar tanto para andar ou parar, adicionei como Inputs:

TargetSpeed – pode ser MaxSpeed ou é Zero.
Interp Speed – pode ser Acceleration ou Deceleration.

Essa taxa de interpolação que a troca do valor levará para ocorrer, é o que deixa o movimento “real”. O ator não irá parar ou ir a MaxSpeed instantaneamente.
O FInterpTo nos dá o CurrentSpeed em que iremos nos deslocar pelo caminho. Mas o veículo ainda precisa saber ONDE vai aplicar esse movimento. Por isso nosso ator é vinculado a um Spline:

Spline é um vetor que tem início e fim. Seu início tem valor 0 e seu final valor 1.

Então nosso cálculo de movimento pega o valor inicial da Spline [0] e adiciona a ele nosso CurrentSpeed. Mas ele também precisa saber com que frequência irá adicionar esse valor, por isso multiplicamos pelo WorldDeltaSeconds.
O resultado desse macro terá como saída as novas localização e rotação.

Estes dados conectados ao SetActorLocation e SetActorRotation movimentam o ator em relação a à spline a qual ele está vinculado numa taxa de atualização guiada pelo WorldDeltaSeconds.

Importante ativar “Sweep”, para que o ator faça uma transição gradual da posição ao invés de teletransportar da localização inicial para a final.
Speed Cap

Otimização

Os cálculos de movimento por FInterp To tendem nunca a chegar à valores absolutos, nunca indo exatamente a 0 ou MaxSpeed. Visualmente é imperceptível, mas criei duas checagens para fazer um “cap” da velocidade e minimizar bugs:

Se CurrentSpeed > que 0,99 da MaxSpeed,
então a velocidade é = MaxSpeed.

Se CurrentSpeed < menor que 0,01 da MaxSpeed,
então a velocidade é = 0