Criando um Jogo FPS com Unreal Engine: Do Projeto Novo aos Inimigos com IA
Este artigo guia você na criação de um shooter em primeira pessoa (FPS) com o Unreal Engine 5, desde o primeiro clique de menu no Epic Games Launcher até inimigos que veem o jogador e o perseguem. Cada parte são passos que você segue um de cada vez. O artigo mostra onde cada arquivo de código deve ficar, e o diagrama mostra o caminho completo.
O que você precisa
Você precisa de um computador com Windows 10 ou 11, uma placa de vídeo com suporte a DirectX 12, pelo menos 100 GB de espaço livre em disco e uma conta gratuita da Epic Games. Instale o Visual Studio 2022 com a carga de trabalho Desenvolvimento de jogos com C++ para compilar o código C++. Se quiser usar apenas Blueprint, pode começar sem C++. Nota: o código aqui foi escrito com base na documentação do Unreal Engine 5, mas ainda não foi compilado nem testado na máquina usada para escrever este artigo. Os resultados indicados são o que você deve ver se seguir os passos corretamente.
Ilustração
1. Instale e crie o projeto FPS
Primeiro instale o Unreal Engine pelo Epic Games Launcher e depois crie um projeto a partir de um modelo pronto.
1. Abra o Epic Games Launcher e entre com sua conta Epic Games
2. No menu da esquerda, abra a seção Unreal Engine e escolha a aba Library
3. Clique em + Add Version, escolha a versão mais recente do Unreal Engine 5 e clique em Install
4. Depois que a instalação terminar, clique em Launch nessa versão
5. No Unreal Project Browser, escolha Games no painel da esquerda
6. Escolha o modelo First Person
7. Em Project Defaults, escolha C++ se quiser escrever código, ou Blueprint se quiser começar visualmente
8. Marque Starter Content para obter modelos e materiais básicos
9. Dê um nome ao projeto, por exemplo FpsDemo, e clique em Create
O que você deve ver: o Editor abre com um nível de exemplo e um personagem em primeira pessoa. Ao pressionar Play, você consegue andar e girar a câmera.
2. Estrutura do projeto e onde ficam os scripts
Entenda a função de cada parte do projeto. O código C++ fica na pasta Source, enquanto os Blueprints e os modelos ficam em Content.
Atenção: todos os scripts C++ ficam em Source/FpsDemo/. Os Blueprints são criados e editados dentro do editor. Não edite arquivos de Content/ manualmente.
Código: project-tree.txt
FpsDemo/
├── FpsDemo.uproject arquivo do projeto, clique duas vezes para abrir no editor
├── Config/ valores iniciais do jogo, como teclas de controle
├── Content/ arquivos do jogo criados no editor, como Blueprints, modelos, níveis, sons
├── Source/
│ ├── FpsDemo/ código C++ do jogo, escrito em arquivos .h e .cpp
│ │ └── FpsDemo.Build.cs define quais bibliotecas este módulo usa, por exemplo AIModule
│ ├── FpsDemo.Target.cs
│ └── FpsDemoEditor.Target.cs
└── Binaries/, Intermediate/, Saved/ arquivos gerados pelo próprio UE, não precisam ser alterados
3. Importe modelos 3D para o projeto
A maioria dos modelos é salva como arquivos FBX. Obtenha-os no Fab ou em outra fonte para a qual você tenha permissão de uso, e confira a licença antes de usar no seu jogo.
1. No Content Browser, na parte inferior, clique com o botão direito na pasta desejada, por exemplo Content/Models
2. Escolha Import to /Game/Models e selecione o arquivo .fbx
3. Na janela FBX Import Options, escolha Skeletal Mesh se o modelo tiver esqueleto, ou Static Mesh se o objeto for fixo
4. Marque Import Materials and Textures e clique em Import
5. Arraste o modelo do Content Browser para o nível para testá-lo
O que você deve ver: o modelo aparece no Content Browser, e ao arrastá-lo para o nível ele fica na cena.
4. Configure os controles com Enhanced Input
O Unreal Engine 5 usa o sistema Enhanced Input para separar as teclas pressionadas das ações, assim você pode mudar as teclas sem editar código.
1. No Content Browser, abra a pasta Input que vem com o modelo
2. Abra IA_Fire para confirmar que ele é um Input Action Digital (bool) para o botão de disparo
3. Abra IMC_Default e confirme que o clique esquerdo do mouse está associado a IA_Fire
4. Para mudar a tecla, edite a associação em IMC_Default e clique em Save
5. Pressione Play e confirme que o clique esquerdo dispara a ação de tiro
O que você deve ver: ao pressionar a tecla associada, a função de tiro do personagem é chamada toda vez.
5. Atirando com line trace
A maioria das armas de FPS emite uma linha reta invisível da câmera para a frente e verifica o que ela atinge. Este exemplo chama LineTraceSingleByChannel e aplica dano ao ator atingido.
1. Abra Source/FpsDemo/FpsCharacter.h e adicione as variáveis Range e Damage, além da declaração de Fire, como no código
2. Abra Source/FpsDemo/FpsCharacter.cpp e adicione a função Fire
3. Em FpsCharacter.cpp, encontre a função OnFire criada pelo modelo e chame Fire() no lugar do código que cria o projétil
4. Pressione Ctrl+Alt+F11 ou clique em Compile no editor para compilar o código
Código: FpsCharacter_Fire.cpp
// Arquivo: Source/FpsDemo/FpsCharacter.h (adicione à classe do personagem do jogador criada pelo modelo)
// UPROPERTY(EditAnywhere, Category = "Combat")
// float Range = 10000.f;
//
// UPROPERTY(EditAnywhere, Category = "Combat")
// float Damage = 25.f;
//
// void Fire();
//
// Arquivo: Source/FpsDemo/FpsCharacter.cpp (função Fire abaixo)
#include "FpsCharacter.h"
#include "Camera/CameraComponent.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
void AFpsCharacter::Fire()
{
if (!FirstPersonCameraComponent)
{
return;
}
// O ponto inicial é a câmera, e o ponto final é a distância Range na direção para onde a câmera aponta
const FVector Start = FirstPersonCameraComponent->GetComponentLocation();
const FVector End = Start + FirstPersonCameraComponent->GetForwardVector() * Range;
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this); // para que o tiro não atinja o próprio personagem
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params))
{
if (AActor* Target = Hit.GetActor())
{
UGameplayStatics::ApplyDamage(Target, Damage, GetController(), this, UDamageType::StaticClass());
}
}
}
O que você deve ver: clicar com o botão esquerdo sobre um objeto causa dano a ele. O Output Log não mostra erros ao atirar.
6. O inimigo recebe dano e morre
Crie a classe do inimigo a partir de Character. Ele tem vida, recebe dano pelo TakeDamage e é removido quando a vida chega a zero.
1. No editor, abra File > New C++ Class, escolha Character como classe base e dê o nome EnemyCharacter
2. Abra EnemyCharacter.h e adicione as declarações como no código
3. Coloque o código de EnemyCharacter.cpp no arquivo
4. Compile e depois crie um Blueprint a partir de EnemyCharacter para colocar no nível
Código: EnemyCharacter.cpp
// Arquivo: Source/FpsDemo/EnemyCharacter.h
// UCLASS()
// class FPSDEMO_API AEnemyCharacter : public ACharacter
// {
// GENERATED_BODY()
// public:
// AEnemyCharacter();
// virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
// AController* EventInstigator, AActor* DamageCauser) override;
// protected:
// UPROPERTY(EditAnywhere, Category = "Combat")
// float Health = 100.f;
// };
//
// Arquivo: Source/FpsDemo/EnemyCharacter.cpp
#include "EnemyCharacter.h"
#include "EnemyAIController.h"
AEnemyCharacter::AEnemyCharacter()
{
// Faz o AI Controller do inimigo assumir automaticamente quando ele é colocado na cena ou criado
AIControllerClass = AEnemyAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
float AEnemyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
AController* EventInstigator, AActor* DamageCauser)
{
const float Applied = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Health -= Applied;
if (Health <= 0.f)
{
Destroy(); // quando a vida acaba, o inimigo sai da cena
}
return Applied;
}
O que você deve ver: ao atirar no inimigo até a vida chegar a zero, ele desaparece do nível.
7. Adicione o nível, propriedades e NavMesh
O inimigo precisa andar pelo chão, então o nível precisa de um NavMesh, que é o mapa das áreas por onde ele pode passar. Adicione também alguns objetos que servem de cobertura.
1. Procure propriedades adicionais em uma fonte para a qual você tenha permissão de uso, como o Fab, e escolha Add to Project ou baixe e importe
2. Arraste os objetos do Content Browser para o nível e ajuste a posição com as ferramentas Move e Scale
3. Em Place Actors, procure Nav Mesh Bounds Volume e arraste-o para o nível
4. Ajuste o tamanho do volume para cobrir a área por onde é possível andar
5. Pressione P no editor para exibir o NavMesh
O que você deve ver: na visualização com P, o chão que pode ser percorrido aparece em verde, e a área embaixo dos objetos não fica verde.
8. O AI Controller que detecta o jogador
O AI Controller é o cérebro do inimigo. Ele usa o AI Perception para verificar se o jogador está visível e grava o resultado no Blackboard.
1. Crie uma classe C++ a partir de AIController com o nome EnemyAIController
2. Abra FpsDemo.Build.cs e adicione "AIModule" em PublicDependencyModuleNames
3. Coloque os arquivos .h e .cpp no projeto e compile
4. O construtor do inimigo em EnemyCharacter define o AI Controller, então o controlador assume quando o inimigo é colocado ou criado
Código: EnemyAIController.cpp
// Arquivo: Source/FpsDemo/EnemyAIController.h
// UCLASS()
// class FPSDEMO_API AEnemyAIController : public AAIController
// {
// GENERATED_BODY()
// public:
// AEnemyAIController();
// protected:
// virtual void BeginPlay() override;
// UFUNCTION()
// void OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
// UPROPERTY(VisibleAnywhere)
// UAIPerceptionComponent* Perception;
// UPROPERTY()
// UAISenseConfig_Sight* Sight;
// UPROPERTY(EditAnywhere, Category = "AI")
// UBehaviorTree* BehaviorTreeAsset;
// };
//
// Arquivo: Source/FpsDemo/EnemyAIController.cpp
// É preciso adicionar "AIModule" em PublicDependencyModuleNames em FpsDemo.Build.cs antes
#include "EnemyAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AIPerceptionTypes.h"
AEnemyAIController::AEnemyAIController()
{
Perception = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("Perception"));
SetPerceptionComponent(*Perception);
Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("Sight"));
Sight->SightRadius = 1500.f; // distância máxima em que o inimigo ainda consegue ver
Sight->LoseSightRadius = 1800.f; // precisa ser maior que isso para a visão se perder
Sight->PeripheralVisionAngleDegrees = 90.f;
Sight->DetectionByAffiliation.bDetectEnemies = true;
Sight->DetectionByAffiliation.bDetectNeutrals = true;
Sight->DetectionByAffiliation.bDetectFriendlies = true;
Perception->ConfigureSense(*Sight);
Perception->SetDominantSense(Sight->GetSenseImplementation());
Perception->OnTargetPerceptionUpdated.AddDynamic(this, &AEnemyAIController::OnTargetPerceptionUpdated);
}
void AEnemyAIController::BeginPlay()
{
Super::BeginPlay();
if (BehaviorTreeAsset)
{
RunBehaviorTree(BehaviorTreeAsset);
}
}
void AEnemyAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
// quando o jogador é visto, grava na chave TargetActor do Blackboard usada pelo Behavior Tree
if (UBlackboardComponent* BB = GetBlackboardComponent())
{
BB->SetValueAsObject(TEXT("TargetActor"), Stimulus.WasSuccessfullySensed() ? Actor : nullptr);
}
}
O que você deve ver: quando o jogador entra na distância e no ângulo de visão do inimigo, a chave TargetActor do Blackboard passa a apontar para o jogador.
9. Behavior Tree e Blackboard
O Behavior Tree é um diagrama de decisões, por exemplo: se há um alvo, vá até ele e atire; se não há, patrulhe. O Blackboard guarda os dados que a árvore usa para decidir.
1. Clique com o botão direito no Content Browser, escolha Artificial Intelligence > Blackboard e dê o nome BB_Enemy
2. Abra BB_Enemy e adicione uma chave chamada TargetActor do tipo Object
3. Clique com o botão direito de novo, escolha Artificial Intelligence > Behavior Tree e dê o nome BT_Enemy
4. Abra BT_Enemy e defina o Blackboard Asset como BB_Enemy
5. Adicione um Selector na raiz, depois uma Sequence com uma condição de Blackboard que verifica se TargetActor está preenchido, seguida de uma tarefa MoveTo que usa TargetActor
6. Nos detalhes do Blueprint do inimigo, defina o Behavior Tree como BT_Enemy no campo Behavior Tree do AI Controller
O que você deve ver: quando o inimigo vê o jogador, ele se move em direção a ele. Quando o jogador sai do campo de visão, ele para de segui-lo.
10. Compile e teste
Confirme que tudo funciona em conjunto e corrija os problemas mais comuns.
1. Pressione Compile ou Ctrl+Alt+F11 para compilar o código C++
2. Pressione Play no editor e ande em direção ao inimigo
3. Atire no inimigo até ele desaparecer e confirme que o dano corresponde ao valor que você definiu
4. Abra Window > Developer Tools > Output Log para ler avisos e erros
O que você deve ver: o jogo roda. O inimigo se vira para o jogador quando o vê, a vida diminui quando ele é atingido, e ele desaparece quando a vida chega a zero.
Próximos passos
Depois, adicione som de tiro, um sistema de pontuação, um menu inicial e animações para o inimigo. Use o Unreal Engine Learning Portal e a documentação do Unreal Engine 5 em dev.epicgames.com/documentation como referências principais.
Resumo
Crie um projeto FPS a partir do modelo no Epic Games Launcher, importe modelos, escreva a função de tiro com line trace e dano para o inimigo, configure o AI Controller para detectar o jogador e conecte-o a um Behavior Tree e a um Blackboard. Cada passo é um clique de menu ou a colagem de código na ordem indicada.