Antes de começar
Certifique-se de ter concluído os seguintes objetivos da seção anterior, Como criar um personagem jogável com ações de entrada:
A classe C++ de personagem criada
Aprendeu como as ações de entrada e os contextos de mapeamento de entrada funcionam.
Aprenda a vincular entrada e movimento
Explore um exemplo de Blueprint de Personagem para aprender como ações de entrada, contextos de mapeamento de entrada e código se combinam para produzir movimento. Em seguida, aprenda a replicar essa funcionalidade em código.
Visualize as entradas em Blueprints
A classe BP_FirstPersonCharacter que acompanha o modelo Primeira Pessoa é um ótimo exemplo de como os Blueprints e as ações de entrada interagem.
Na árvore de ativos do Navegador de Conteúdo, acesse Conteúdo > FirstPerson > Blueprints. Clique duas vezes na classe BP_FirstPersonCharacter para abri-la no Editor de Blueprint.
O Event Graph do Blueprint fica no meio do Editor de Blueprint. O EventGraph é um grafo de nós que usa eventos e chamadas de função para executar uma série ordenada de ações em resposta à jogabilidade. Neste grafo, há grupos de nós para Entrada de Câmera, Entrada de Movimento e Entrada de Salto.
Entenda a lógica da entrada de salto
Aumente o zoom no grupo Entrada de Salto. O nó EnhancedInputAction IA_Jump representa o ativo da ação de entrada IA_Jump que você explorou na última etapa.
Quando a ação de entrada é acionada, ela aciona os eventos Iniciado e Acionado. O evento Iniciado do nó leva a um nó de função chamado Salto. A classe de personagem pai desse Blueprint tem uma funcionalidade de salto integrada chamada sempre que IA_Jump é acionado por um aperto de botão.
Quando o salto termina, o nó aciona um evento Concluído. Esse evento leva a outro nó de função, Interromper Salto, que também herda da classe de personagem.
A lógica de entrada de salto também adiciona controles de toque, mas eles não são abordados neste tutorial.
Entenda a lógica da entrada de movimento
Em seguida, verifique o grupo Entrada de Movimento. Esse grupo também começa com um nó correspondente a uma ação de entrada, IA_Move.
O nó IA_Move tem um evento Acionado que é disparado quando quaisquer botões vinculados a IA_Move são pressionados.
IA_Move também contém Action Value X e Action Value Y, que são os valores dos movimentos em X e Y produzidos pela entrada do jogador. Como os valores de X e Y são separados, você precisa aplicá-los ao personagem individualmente.
O nó Mover é um nó de função personalizado que aplica movimento ao personagem. Você pode ver no nó e em seu painel Detalhes que ele recebe duas entradas denominadas Esquerda/Direita e Avançar/Recuar, e os valores de movimento X e Y do IA_Move são transmitidos para essas entradas.
Clique duas vezes no nó Mover ou clique na guia Mover acima do gráfico para visualizar a lógica dentro da função.
A função começa com um nó de entrada de função com seus valores de entrada.
O grupo de nós Esquerda/Direita contém um nó de função Adicionar entrada de movimento que adiciona movimento ao personagem com base em dois valores: Direção do mundo e Valor de escala.
Direção no Mundo é a direção em que o personagem está voltado no mundo, e Valor de Escala é a quantidade de movimento a ser aplicada. Como esse nó controla o movimento Esquerda/Direita, ele usa o nó de função Obter vetor direito do ator para primeiro obter o vetor correto da posição do personagem no mundo e, em seguida, usa a entrada Esquerda/Direita (ou o valor X da ação de entrada) como o valor de escala para aplicar o movimento ao longo desse vetor.
Se Left / Right for positivo, o personagem se moverá para cima no eixo X, ou seja, para a direita. Se Left / Right for negativo, o personagem se moverá para baixo no eixo X, ou seja, para a esquerda.
O grupo Avançar/Recuar tem a mesma configuração que o grupo Esquerda/Direita, mas em vez disso usa a entrada Avançar/Recuar (o valor Y da ação de entrada) para determinar o Valor da escala ao longo do Vetor Avançar do ator.
Replicar esses nós em código exige um pouco mais de esforço, mas oferece um controle preciso sobre como e para onde o personagem se move.
Atribuir entrada a um jogador com um PlayerController
O Contexto de Mapeamento de Entrada mapeia a entrada do jogador para Ações de Entrada, mas você também precisa conectar esse contexto de entrada ao jogador. O jogador padrão faz isso com a classe PlayerController e o subsistema de entrada.
O ativo PlayerController atua como uma ponte entre o jogador humano e os Pawns no jogo que ele controla. Ele recebe e processa a entrada do jogador e traduz essa entrada em comandos. O Pawn recebe esses comandos e descobre como realizar o movimento no mundo do jogo. Você pode usar o mesmo PlayerController para controlar diferentes Pawns.
O PlayerController também pode:
Desabilitar entrada durante cutscenes ou menus.
Rastrear pontuações ou outros dados do jogador.
Gerar ou ocultar elementos da interface
A separação entre PlayerController e o personagem permite flexibilidade e persistência de dados. Por exemplo, ser capaz de trocar de personagem (como quando um jogador morre) sem perder dados do jogador ou lógica de gerenciamento de entrada, pois isso está dentro do PlayerController.
Para explorar como definir isso no Blueprints, volte à pasta Blueprints no Navegador de Conteúdo, abra o Blueprint BP_FirstPersonPlayerController.
As classes PlayerController possuem um Subsistema de Jogador Local de Entrada Aprimorada. É um subsistema anexado a um jogador local específico e gerencia o contexto de entrada e os mapeamentos desse jogador no tempo de execução. Use-o para gerenciar quais entradas estão ativas e trocar entre contextos de entrada no tempo de execução. Para saber mais sobre os subsistemas da UE, consulte Programming Subsystems.
Quando o jogo começa, se o Subsistema de Jogador Local de Entrada Aprimorada for válido, ele fará uma chamada Adicionar contexto de mapeamento para vincular o contexto de mapeamento de entrada de IMC_Default ao subsistema de entrada do jogador. Em outras palavras, esse grupo de nós ativa esse conjunto de entradas para o jogador.
Embora a lógica do PlayerController esteja em um Blueprint separado da outra lógica de movimento, em C++, você implementará tudo isso na classe de personagem. Portanto, uma segunda classe C++ não será necessária.
O gráfico de eventos de
BP_FirstPersonCharactermostra outra maneira de aplicar contextos de mapeamento de entrada que envolve esperar até que o pawn seja possuído. Essa abordagem não é abordada neste tutorial, mas você pode explorá-la por conta própria.
Configure a classe de personagem
Agora que você viu como o movimento funciona em Blueprints, é hora de fazer isso em código e, em seguida, testá-lo movendo seu personagem pelo nível! Você começará adicionando todos os módulos e instruções #include necessários e, em seguida, declarará as classes, funções e propriedades necessárias para implementar o movimento do personagem.
Os exemplos de código neste tutorial usam um projeto chamado AdventureGame e uma classe de personagem chamada AdventureCharacter.
Adicione o Sistema de Entrada Aprimorada
Você já habilitou o Sistema de Entrada Aprimorada no Unreal Editor, mas também precisará declará-lo manualmente no Build.cs do seu projeto e adicionar componentes à classe de personagem.
Para usar o Sistema de Entrada Aprimorada no projeto C++, siga estas etapas:
Abra o projeto no Visual Studio e abra
[ProjectName].Build.cs(localizado na pastaSourcecom os outros arquivos de classe do projeto).Esse arquivo informa à Unreal Engine quais módulos você precisa para criar seu projeto.
Na chamada da função
PublicDependencyModuleNames, adicione“EnhancedInput”à lista de módulos:C++PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });Salve e feche o arquivo
Build.cs.
Para adicionar componentes do Sistema de Entrada Aprimorada à classe de personagem, siga estas etapas:
Abra o arquivo
.hdo personagem. Perto do topo do arquivo, adicione as seguintes instruções "include":#include “EnhancedInputComponent.h”adiciona o módulo de componente Entrada Aprimorada.#include “InputActionValue.h”habilita o acesso aos valores de ação de entrada gerados pelas ações de entrada.#include “EnhancedInputSubsystems.h”habilita o acesso ao subsistema de jogador local.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "InputActionValue.h" #include "AdventureCharacter.generated.h"Certifique-se de que todas as instruções
#includeadicionadas venham antes da instruçãoAdventureCharacter.generated.h. Para que o código funcione adequadamente, essa instrução deve ser a última na lista de entradas.Após as instruções
#include, declare três novas classes:UInputMappingContextUInputActionUInputComponent
Essas classes já existem no módulo Entrada Aprimorada. Essa declaração de um objeto existente é chamada de declaração de encaminhamento e informa ao compilador que a classe existe e que você a usará.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "InputActionValue.h" #include "AdventureCharacter.generated.h"
Declare um ponteiro InputMappingContext
Na seção protected do arquivo .h do personagem, use TObjectPtr para adicionar um novo ponteiro para UInputMappingContext chamado FirstPersonContext. É um ponteiro direcionado ao contexto de mapeamento de entrada que vincula as ações de entrada ao pressionar o botão.
TObjectPtr é um agrupador de ponteiro inteligente na Unreal Engine que é uma maneira mais segura de referenciar tipos derivados de UObject. Ele é um substituto para ponteiros UObject brutos que reconhece o editor e pode ser usado na coleta de lixo. É uma referência fixa, então mantém o objeto carregado no tempo de execução. Recomendamos declarar ponteiros dessa forma ao programar com a Unreal Engine.
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputMappingContext> FirstPersonContext;O prefixo U identifica o InputMappingContext como um UObject.
O macro UPROPERTY() que vem antes de uma declaração de variável informa à Unreal Engine sobre essa variável. A Unreal Header Tool usa o macro para processar informações sobre seu código e controla onde a variável pode ser acessada, como ela aparece no editor, entre outros.
Esse ponteiro tem os seguintes valores de UPROPERTY:
EditAnywhere: expõe a propriedade ao Unreal Editor no painel Detalhes da classe.BlueprintReadOnly: os Blueprints podem acessar essa propriedade, mas não editá-la.Category = Input: a propriedade aparecerá em uma seção chamada Entrada no painel Detalhes da classe. As categorias são úteis para organizar o código e podem facilitar muito a navegação no editor.
Declare ponteiros InputAction para salto e movimento
Ainda na seção protected, adicione dois ponteiros para UInputAction chamados MoveAction e JumpAction. Eles são ponteiros para as ações de entrada IA_Jump e IA_Move.
Forneça o mesmo macro UPROPERTY() de UInputMappingContext.
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputMappingContext> FirstPersonContext;
// Move Input Actions
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputAction> MoveAction;
Declare a função Move()
As ações de entrada geram valores de ação de entrada, e você passará esses valores para uma nova função que os usará para aplicar movimento ao personagem.
Na seção public do arquivo, declare uma nova função chamada Move() que usa uma referência constante FInputActionValue chamada Value.
// Handles 2D Movement Input
UFUNCTION()
void Move(const FInputActionValue& Value);O macro UFUNCTION() que vem antes da declaração da função torna a Unreal Header Tool ciente da função.
Salve o arquivo. O arquivo de cabeçalho do personagem deve estar parecido com este:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
#include "AdventureCharacter.generated.h"
class UInputMappingContext;
Implemente funções de movimento
Agora que você declarou as propriedades necessárias para o movimento do personagem, no arquivo .cpp, você designará suas funções para simular a funcionalidade vista no Blueprint de Personagem padrão.
Configure a função Move()
Abra a classe .cpp do personagem e adicione uma nova definição de função para Move() para implementar a que você declarou no arquivo .h..
void AAdventureCharacter::Move(const FInputActionValue& Value)
{
) Ao explorar as ações de entrada do personagem padrão, você viu que IA_Move tem um tipo de valor de Axis2D (Vector2D), logo, retorna um valor FVector2D quando é acionado.
Obtenha o valor de FInputActionValue em Move() e armazene-o em um novo FVector2D chamado MovementValue:
void AAdventureCharacter::Move(const FInputActionValue& Value)
{
// 2D Vector of movement values returned from the input action
const FVector2D MovementValue = Value.Get<FVector2D>();
}Em seguida, adicione uma instrução if para verificar se o Controle é válido. Controle é um ponteiro para o controle que possui esse ator e deve ser válido para que o movimento funcione.
void AAdventureCharacter::Move(const FInputActionValue& Value)
{
// 2D Vector of movement values returned from the input action
const FVector2D MovementValue = Value.Get<FVector2D>();
// Check if the controller possessing this Actor is valid
if (Controller)
{
}
Adicionar entrada de movimento 2D com Move()
Para produzir os movimentos para a esquerda, para a direita, para a frente e para trás no Blueprint de Personagem, o Event Graph adicionou uma entrada de movimento combinando Action Value X e Action Value Y de IA_Move com o vetor direito e o vetor para a frente do ator. Você implementará isso em código dentro da função Move().
Dentro da instrução if, chame GetActorRightVector() para armazenar o vetor da direita do ator em um novo FVector chamado Right.
const FVector Right = GetActorRightVector();Em seguida, chame AddMovementInput() para adicionar movimento ao personagem, passando Right e MovementValue.X.
AddMovementInput(Right, MovementValue.X);Repita o processo para movimentos para a frente e para trás usando GetActorForwardVector(), desta vez passando MovementValue.Y.
A função"Move() completa deve ser semelhante a esta:
void AAdventureCharacter::Move(const FInputActionValue& Value)
{
// 2D Vector of movement values returned from the input action
const FVector2D MovementValue = Value.Get<FVector2D>();
// Check if the controller posessing this Actor is valid
if (Controller)
{
// Add left and right movement
const FVector Right = GetActorRightVector();
Vincule o movimento à entrada com SetupPlayerInputComponent
Em seguida, vincule a função Move ao contexto de mapeamento de entrada FirstPersonContext que você declarou antes.
A função para isso, SetupPlayerInputComponent(), já está definida no arquivo .cpp do personagem, pois ele é herdado de ACharacter. Essa função recebe um UInputComponent para configurar uma entrada de jogador.
Verifique se há um componente de entrada aprimorada
Por padrão, essa função começa com uma chamada da função SetupPlayerInputComponent() de ACharacter, que verifica a existência de um componente de entrada no personagem.
// Called to bind functionality to input
void AAdventure::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}Isso verifica apenas se um componente de entrada regular existe no personagem. Como você precisa verificar se há um componente de entrada aprimorada, exclua essa chamada à função SetupPlayerInputComponent() da classe pai.
Em vez disso, em uma instrução if, declare um novo ponteiro para UEnhancedInputComponent chamado EnhancedInputComponent. Defina-o como igual ao resultado da chamada de CastChecked() no PlayerInputComponent passado para essa função ao chamá-la em UEnhancedInputComponent.
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
}Vincule ações de movimento
Dentro da instrução if, chame a função BindAction() a partir de EnhancedInputComponent.
Passe os seguintes argumentos para a função:
MoveAction: a ação de entrada a ser vinculada (declarada no arquivo.hdo personagem).Evento
Triggereda partir deETriggeredEvent: o tipo de gatilho do evento.this: o personagem ao qual vincular.Move(): uma referência à função que você deseja vincular.
if (TObjectPtr<UEnhancedInputComponent> EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
// Bind Movement Actions
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAdventureCharacter::Move);
}Agora, quando IA_Move é acionado, ele chama a função Move() para adicionar movimento ao personagem.
Vincule ações de salto
Em seguida, adicione duas vinculações a IA_Jump: uma para iniciar o salto e outra para interrompê-lo.
Você usará os seguintes argumentos:
JumpAction, o ponteiro da ação de entrada para IA_Jump que você declarou no arquivo.h.Eventos de gatilho
StartedeCompleted.Funções
JumpeStopJumpingherdadas da classe pai ACharacter e definidas nela.
// Bind Jump Actions
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);Sua função SetupPlayerInputComponent() deve ficar assim:
// Called to bind functionality to input
void AAdventureCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// Check the UInputComponent passed to this function and cast it to an UEnhancedInputComponent
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
// Bind Movement Actions
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAdventureCharacter::Move);
// Bind Jump Actions
Vincule o mapeamento de entrada ao personagem
Você vinculou as entradas às funções, mas ainda precisa vincular o contexto de mapeamento de entrada ao personagem. Você fará isso na função BeginPlay() do personagem para que a entrada esteja configurada quando o jogo inciar.
BeginPlay() é uma função virtual na classe pai AActor chamada quando o jogo começa ou quando um ator é gerado e inicializado no mundo. Use-a para a lógica que deve ser executada uma vez para esse ator no início da jogabilidade.
Em BeginPlay(), verifique se o ponteiro global da engine é nulo antes de continuar.
check(GEngine != nullptr);Em uma instrução if, crie um novo ponteiro APlayerController chamado PlayerController. Defina isso como o resultado da conversão de Controller para APlayerController.
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
}A instrução if garante que a execução só prosseguirá se o ponteiro não for nulo.
Agora você precisa obter o Subsistema de Jogador Local de Entrada Aprimorada e adicionar o contexto de mapeamento de entrada FirstPersonContext (declarado no seu arquivo .h) ao subsistema.
Em outra instrução if, crie um novo ponteiro para UEnhancedInputLocalPlayerSubsystem denominado Subsystem, chamando ULocalPlayer::GetSubsystem() e passando o jogador atual. Encontre o jogador atual chamando PlayerController->GetLocalPlayer().
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
}
}Adicione o contexto de mapeamento ao subsistema chamando AddMappingContext(), passando o contexto de mapeamento e uma prioridade de 0 para definir esse contexto de mapeamento como o de prioridade mais alta.
// Get the enhanced input local player subsystem and add a new input mapping context to it
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(FirstPersonContext, 0);
}Por fim, adicione uma nova messagem de depuração para verificar se sua classe de personagem personalizada está sendo usada.
Sua função BeginPlay() deve ficar assim:
// Called when the game starts or when spawned
void AAdventureCharacter::BeginPlay()
{
Super::BeginPlay();
check(GEngine != nullptr);
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
Salve seu cabeçalho .h e arquivos de implementação .cpp no Visual Studio e clique em Compilar para compilar o projeto.
Defina variáveis no Blueprint de Personagem
Para concluir a configuração desses controles de movimento, use o seu Blueprint de Personagem para atribuir ativos às variáveis que você declarou no código.
Para preencher as novas propriedades do seu personagem com ativos, siga estas etapas:
No Unreal Editor, se ainda não estiver aberto, abra seu Blueprint de Personagem no Editor de Blueprint.
No painel Detalhes, em Entrada, defina as seguintes propriedades:
Defina o Contexto em primeira pessoa como
IMC_Adventure.Defina Ação de Movimento como
IA_Move.Defina Ação de Salto como
IA_Jump.
Salve o Blueprint e clicar em Compilar para compilar.
Teste o movimento do personagem
Pressione Jogar na Barra de Ferramentas do Editor de Níveis para iniciar o modo Jogar no Editor. Quando o jogo começar, você verá a mensagem “Hello World!” e “We are using AdventureCharacter” na tela. Você deve conseguir se mover usando WASD ou as teclas de seta e saltar usando a barra de espaço!
Próxima
Agora você tem um personagem que se move, mas ainda falta uma malha e câmera adequadas. Na próxima seção, você aprenderá a criar um componente de câmera, vinculá-lo ao personagem e adicionar malhas esqueléticas para obter uma ponto de vista real em primeira pessoa!
Como adicionar câmera, malha e animação em primeira pessoa
Saiba como usar C++ para anexar componentes de câmera e malha em um personagem em primeira pessoa.
Código completo
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
#include "AdventureCharacter.generated.h"
class UInputMappingContext;
#include "AdventureCharacter.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class AdventureGame : ModuleRules
{
public AdventureGame(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;