Antes de empezar
Asegúrate de haber completado los siguientes objetivos de la sección anterior, Crear un personaje jugable con acciones de entrada:
Se ha creado una clase C++ para el personaje.
Has aprendido cómo funcionan las acciones de entrada y los contextos de asignación de entradas.
Más información sobre cómo vincular la entrada y el movimiento
Explora un blueprint de personaje de muestra para descubrir cómo se combinan las acciones de entrada, los contextos de asignación de entrada y el código para producir movimiento. Después, aprende a replicar esa función en código.
Visualización de entradas en blueprints
La clase BP_FirstPersonCharacter que viene con la plantilla First Person es un buen ejemplo de cómo interactúan los blueprints y las acciones de entrada.
En el árbol de recursos del explorador de contenido , dirígete a Contenido > FirstPerson > Blueprints. Haz doble clic en la clase BP_FirstPersonCharacter para abrirla en el editor de blueprints.
El grafo de eventos del blueprint está en el centro del editor de blueprints. El EventGraph es un grafo de nodos que usa eventos y llamadas a función para realizar una serie ordenada de acciones en respuesta al juego. En este grafo hay grupos de nodo para Camera Input (entrada de cámara), Move Input (entrada de movimiento) y Jump Input (entrada de salto).
Comprender la lógica de las entradas de salto
Haz zoom sobre el grupo Jump Input. El nodo EnhancedInputAction IA_Jump representa el recurso de acción de entrada IA_Jump que exploraste en el último paso.
Cuando se activa la acción de entrada, se activan los eventos Started y Triggered. El evento Started del nodo dirige un nodo de función llamado Jump. La clase de personaje padre de este blueprint tiene una función de salto integrada. Se llama a esta función cada vez que se activa IA_Jump al pulsar un botón.
Cuando finaliza el salto, el nodo activa un evento Completed. Este evento conduce a otro nodo de función, Stop Jumping (dejar de saltar), que también hereda de la clase del personaje.
La lógica de entrada Jump también añade controles táctiles, pero este tutorial no los trata.
Comprender la lógica de las entradas de movimiento
A continuación, fíjate en el grupo Movement Input. Este grupo también comienza con un nodo correspondiente a una acción de entrada, IA_Move.
El nodo IA_Move tiene un evento Triggered que se activa cuando se pulsa cualquier botón vinculado a IA_Move.
IA_Move también contiene Action Value X y Action Value Y, que son los valores de los movimientos en los ejes X e Y producidos por la entrada del jugador. Dado que los valores X e Y son independientes, tendrás que aplicarlos individualmente al personaje.
El nodo Mover es un nodo de función personalizado que aplica movimiento al personaje. Puedes ver en el nodo y en su panel de detalles que toma dos entradas llamadas Left/Right y Forward/Backward, a las que se pasan los valores de movimiento X e Y de IA_Move.
Haz doble clic en el nodo Mover o en la pestaña Mover que hay encima del grafo para ver la lógica de la función.
La función comienza con un nodo de entrada de función con sus valores de entrada.
El grupo de nodo Left/Right contiene un nodo de función Añadir entrada de movimiento que añade movimiento al personaje en función de dos valores: Dirección del mundo y Valor de escala.
World Direction es la dirección hacia la que mira el personaje en el mundo y Scale Value, la cantidad de movimiento que se aplicará. Dado que este nodo se encarga del movimiento Left/Right, usa el nodo de función Conseguir actor Vector derecho para obtener primero el vector derecho de la posición del personaje en el mundo, y a continuación, usa la entrada izquierda/derecha (o el valor x de la entrada acción) como el valor de escala para aplicar el movimiento a lo largo de ese vector.
Si Left/Right es positivo, el personaje se mueve hacia arriba en el eje X o hacia la derecha. Si Left/Right es negativo, el personaje se mueve hacia abajo sobre el eje X o hacia la izquierda.
El grupo Forward/Backward tiene la misma configuración que el grupo Left/Right, pero en su lugar usa la entrada Forward / Backward (el valor Y de la acción en el juego) para determinar el valor de escala a lo largo del vector de avance del actor.
Replicar estos nodos en código requiere un poco más esfuerzo, pero proporciona un control preciso sobre cómo y dónde se mueve el personaje.
Asignación de entradas a jugadores con PlayerController
El contexto de asignación de entrada mapea la entrada del jugador a acciones de entrada, pero también es necesario conectar ese contexto de entrada al jugador. El jugador por defecto hace esto con la clase PlayerController y el subsistema de entrada.
El recurso PlayerController actúa como puente entre el jugador humano y cualquier peón en el juego al que esté controlando. Recibe y procesa la entrada del jugador y la traslada en comandos. Luego, el peón recibe esos comandos y decide cómo realizar ese movimiento en el mundo del juego. Puedes usar el mismo PlayerController para controlar distintos peones.
El PlayerController también puede:
Desactivar la entrada durante las cinemáticas o los menús.
Realizar un seguimiento de las puntuaciones u otros datos del jugador.
Generar u ocultar elementos de la IU.
La separación entre PlayerController y el personaje permite cierta flexibilidad y persistencia de los datos. Por ejemplo, poder cambiar de personaje (como cuando un jugador muere) sin perder los datos del jugador ni la lógica de gestión de entrada, ya que reside en PlayerController.
Para saber cómo establecer esto en blueprints, vuelve a la carpeta blueprints del explorador de contenido y abre el blueprint BP_FirstPersonPlayerController.
Las clases PlayerController cuentan con un Enhanced Input Local Player Subsystem (subsistema de entrada de jugador local). Se trata de un subsistema adjunto a un jugador local específico que gestiona el contexto de entrada y las asignaciones de dicho jugador en tiempo de ejecución. Se usa para gestionar qué entradas están activas y cambiar entre contextos de entrada en tiempo de ejecución. Si quieres más información sobre los subsistemas de UE, consulta Programming Subsystems.
Cuando comienza el juego, si el Enhanced Input Local Player Subsystem es válido, llama a Add Mapping Context para vincular el contexto de asignación de entrada IMC_Default al subsistema de entrada del jugador. En otras palabras, este grupo de nodos activa este conjunto de entradas para el jugador.
Aunque esta lógica de PlayerController está en un blueprint separado de la otra lógica de movimiento, en C++, implementarás todo esto dentro de la clase de personaje para que no sea necesaria una segunda clase de C++.
El grafo de evento de
BP_FirstPersonCharacter muestra otra forma de aplicar contextos de asignación de entradas que consiste en esperar hasta que el peón esté poseído.Este enfoque no se trata en este tutorial, pero puedes explorarlo por tu cuenta.
Configuración de la clase de personaje
Ahora que has visto cómo funciona el movimiento en blueprints, es el momento de compilarlo en código y, a continuación, probar a mover a tu personaje por el nivel. Comenzarás añadiendo todos los módulos necesarios y las instrucciones #include. Después, declararás las clases, funciones y propiedades que necesitarás para implementar el movimiento del personaje.
Los ejemplos de código de este tutorial usan un proyecto llamado AdventureGame y una clase de personaje llamada AdventureCharacter.
Añadir el sistema de sistema de entradas mejorado
Ya te has asegurado que el sistema de entradas mejorado esté activado en Unreal Editor, pero también tendrás que declararlo manualmente en el archivo Build.cs de tu proyecto y añadir ciertos componentes a tu clase de personaje.
Para usar el sistema de entradas mejorado en tu proyecto de C++, sigue estos pasos:
Abre tu proyecto en Visual Studio y abre
[ProjectName].Build.cs(se encuentra en la carpetaSourcecon los demás archivos de clase de tu proyecto).Este archivo indica a Unreal Engine qué módulos necesitas para compilar tu proyecto.
En la llamada de función
PublicDependencyModuleNames, añade“EnhancedInput”a la lista de módulos:C++PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });Guarda y cierra el archivo
Build.cs.
Para añadir componentes del sistema de entradas mejorado a tu clase de personaje, sigue estos pasos:
Abre el archivo
.hde tu personaje. Cerca de la parte superior del archivo, añade las siguientes instrucciones:#include “EnhancedInputComponent.h”añade el módulo de componente de Enhanced Imput.#include “InputActionValue.h”habilita el acceso a los valores de acción de entrada producidos por tus acciones de entrada.#include “EnhancedInputSubsystems.h”habilita el acceso al subsistema del jugador 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"Asegúrate de que cualquier instrucción
#includeque añadas vaya antes de la instrucciónAdventureCharacter.generated.h. Para que tu código funcione correctamente, esta instrucción debe ser la última en la lista de entradas.Después de las instrucciones
#include, declara tres nuevas clases:UInputMappingContextUInputActionUInputComponent
Estas clases ya existen en el módulo Enhanced Imput. Declarar un objeto existente como este se denomina declaración directa e indica al compilador que la clase existe y que la usarás.
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"
Declarar un puntero InputMappingContext
En la sección protected del archivo .h de tu personaje, utiliza TObjectPtr para añadir un nuevo puntero UInputMappingContext denominado FirstPersonContext. Se trata de un puntero al contexto de asignación de entrada que vincula tus acciones de entrada con las pulsaciones de botones.
TObjectPtr es un envoltorio de puntero inteligente de Unreal Engine que supone una forma más segura de hacer referencia a tipos derivados de UObject. Se trata de un reemplazo compatible con el editor y seguro frente a la recolección de basura para los punteros UObject sin procesar. Es una referencia dura, por lo que mantiene el objeto cargado en tiempo de ejecución. Recomendamos declarar punteros de esta manera al programar con Unreal Engine.
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputMappingContext> FirstPersonContext;El prefijo U identifica el InputMappingContext como un UObject.
La macro UPROPERTY() que precede a la declaración de una variable informa a Unreal Engine sobre esa variable. La herramienta Unreal Header Tool utiliza la macro para procesar información sobre tu código y controla dónde se puede acceder a la variable, cómo aparece en el editor y otras cosas.
Este puntero tiene los siguientes valores de UPROPERTY:
EditAnywhere: Expone la propiedad a Unreal Editor en el panel de detalles de la clase.BlueprintReadOnly: Los blueprints pueden acceder a esta propiedad, pero no editarla.Category = Input: La propiedad aparecerá en una sección llamada Input en el panel de detalles de la clase. Las categorías son útiles para organizar tu código y pueden facilitar mucho la navegación por el editor.
Declaración de los punteros InputAction saltar y mover
También en la sección protected, añade dos punteros UInputAction llamados MoveAction y JumpAction. Son punteros a las acciones de entrada IA_Jump e IA_Move.
Asigna a estas la misma macro UPROPERTY() que a 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;
Declaración de la función Move()
Tus acciones de entrada producen valores de acciones de entrada, y pasarás estos valores a una nueva función que los utilice para aplicar movimiento a tu personaje.
En la sección public del archivo, declara una nueva función llamada Move() que tome una referencia constante FInputActionValue llamada Value.
// Handles 2D Movement Input
UFUNCTION()
void Move(const FInputActionValue& Value);La macro UFUNCTION() que precede a la declaración de la función hace que Unreal Header Tool reconozca la función.
Guarda el archivo. El archivo de cabecera de tu personaje ahora debería tener el siguiente aspecto:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
#include "AdventureCharacter.generated.h"
class UInputMappingContext;
Implementar funciones de movimiento
Ahora que has declarado las propiedades necesarias para el movimiento del personaje, en el archivo .cpp de tu personaje diseñarás las funciones para imitar la función que viste en el blueprint del personaje por defecto.
Configuración de la función Move()
Abre el archivo .cpp de tu personaje y añade una nueva definición de función para Move() para implementar la que declaraste en tu archivo .h.
void AAdventureCharacter::Move(const FInputActionValue& Value)
{
) Al explorar las acciones de entrada predeterminadas del personaje, has visto que IA_Move tiene un tipo de valor de Axis2D (Vector2D), por lo que devuelve un valor FVector2D cuando se activa.
Dentro de Move(), obtén el valor de FInputActionValue y guárdalo dentro de un nuevo FVector2D llamado MovementValue:
void AAdventureCharacter::Move(const FInputActionValue& Value)
{
// 2D Vector of movement values returned from the input action
const FVector2D MovementValue = Value.Get<FVector2D>();
}A continuación, añade una instrucción if para comprobar si el controlador es válido. Controller es un puntero al controlador que posee este actor y debe ser válido para que funcione el movimiento.
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)
{
}
Añadir una entrada de movimiento 2D con Move()
Para producir un movimiento hacia la izquierda, hacia la derecha, hacia delante y hacia atrás en el blueprint de personaje, el grafo de eventos añadió una entrada de movimiento combinando Action Value X del IA_Move y Action Value Y con los vectores hacia la derecha y hacia delante del actor. Esto se implementará en el código en la función Move().
Dentro de la instrucción if, llama a GetActorRightVector() para almacenar el vector derecho del actor en un nuevo FVector llamado Right.
const FVector Right = GetActorRightVector();Después, llama a AddMovementInput() para añadir movimiento al personaje, pasando Right y MovementValue.X.
AddMovementInput(Right, MovementValue.X);Repite este proceso para avanzar e ir hacia atrás usando GetActorForwardVector(), esta vez pasando el MovementValue.Y.
La función Move() al completo debería parecerse a esto:
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();
Vincular el movimiento a una entrada con SetupPlayerInputComponent
A continuación, vincula tu función Move al contexto de asignación de entrada FirstPersonContext que declaraste antes.
La función para ello, SetupPlayerInputComponent(), ya está definida en el archivo .cpp de tu personaje, puesto que se hereda de ACharacter. Esta función toma un componente UInputComponent y lo utiliza para configurar la entrada de jugador.
Comprobación de que hay un componente Enhanced Input
Por defecto, esta función empieza con una llamada a la función SetupPlayerInputComponent() de ACharacter, la cual comprueba si existe un componente de entrada en el personaje.
// Called to bind functionality to input
void AAdventure::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}Esto solo comprueba si existe un componente de entrada normal en el personaje, y tú tienes que comprobar si hay un componente Enhanced Input en su lugar, así que elimina esta llamada a la función SetupPlayerInputComponent() de la clase padre.
En su lugar, en una instrucción if, declara un nuevo puntero UEnhancedInputComponent llamado EnhancedInputComponent. Iguala esto con el resultado de llamar a CastChecked() en el PlayerInputComponent que se pasa a esta función mientras se convierte a UEnhancedInputComponent.
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
}Vincular acciones de movimiento
Dentro de la instrucción if, llama a la función BindAction() desde EnhancedInputComponent.
Pasa los siguientes argumentos a la función:
MoveAction: la acción de entrada para vincular (declarado en el archivo.hdel personaje).Evento
TriggereddeETriggeredEvent: el tipo de activador del evento.this: el personaje al que vincularse.Move(): una referencia a la función que quieres realizar la vinculación.
if (TObjectPtr<UEnhancedInputComponent> EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
// Bind Movement Actions
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAdventureCharacter::Move);
}Ahora, cuando se activa IA_Move, llama a la función Move() para añadir movimiento a tu personaje.
Vincular acciones de salto
A continuación, añade dos vinculaciones a IA_Jump, una para que empiece a saltar y otra para que deje de saltar.
Vas a utilizar los siguientes argumentos:
JumpAction, la acción de entrada que apunta a IA_Jump, que declaraste en el archivo.h.Eventos de activación
StartedyCompleted.Funciones
JumpyStopJumpingheredadas y definidas en la clase padre ACharacter.
// Bind Jump Actions
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);Tu función SetupPlayerInputComponent() debería tener este aspecto:
// 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
Vinculación de la asignación de entrada al personaje
Has vinculado tus entradas a tus funciones, pero aún tienes que vincular el contexto de asignación de entrada a tu personaje. Esto se hará en la función BeginPlay() de tu personaje, de modo que la entrada se configurará cuando se inicie el juego.
BeginPlay() es una función virtual de la clase padre AActor, a la que se llama cuando se inicia el juego o cuando se genera e inicializa por completo un actor en el mundo. Utiliza esto para la lógica que debería ejecutarse una vez para ese actor al principio del juego.
En BeginPlay(), comprueba si el puntero global del motor es nulo antes de continuar.
check(GEngine != nullptr);En una instrucción if, crea un nuevo puntero APlayerController nombrado PlayerController. Establece esto en el resultado de convertir Controller en APlayerController.
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
}La instrucción if garantiza que la ejecución solo se lleve a cabo si el puntero no es un valor nulo.
Ahora necesitas obtener el subsistema del jugador local Enhanced Input y añadir el contexto de asignación de entrada FirstPersonContext (declarado en tu archivo .h) al subsistema.
En otra instrucción if, crea un nuevo puntero UEnhancedInputLocalPlayerSubsystem nombrado Subsystem llamando a ULocalPlayer::GetSubsystem() y pasando el jugador actual. Puedes obtener el jugador actual haciendo una llamada a PlayerController->GetLocalPlayer().
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
}
}Añade el contexto de asignación al subsistema haciendo una llamada a AddMappingContext(), pasando el contexto de asignación y una prioridad de 0 para establecer este contexto de asignación como el de mayor prioridad.
// 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 último, añade un nuevo mensaje de depuración para verificar que se está usando tu clase de personaje personalizada.
Tu función BeginPlay() debería tener un aspecto similar al siguiente:
// 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))
{
Guarda tus archivo de encabezado .h y tus archivos de implementación .cpp en Visual Studio y haz clic en Compilar para compilar tu proyecto.
Establecer variables en el blueprint de personaje
Para terminar de ajustar estos controles de movimiento, usa el blueprint de tu personaje para asignar recursos a las variables que declaraste en el código.
Para propagar las nuevas propiedades de tu personaje con recursos, sigue estos pasos:
Si no lo has hecho ya en Unreal Editor, abre tu blueprint de personaje en el editor de blueprints.
En el panel de detalles , en Entrada, establece las siguientes propiedades:
Establece el contexto de primera persona en
IMC_Adventure.Establece Move Action como
IA_Move.Establece Jump Action como
IA_Jump.
Guarda tu blueprint y haz clic en Compilar para compilarlo.
Probar el movimiento del personaje
Pulsa el botón de reproducción en la barra de herramientas del editor de niveles para iniciar el modo Reproducir en el editor. Cuando empiece el juego, debería aparecer el mensaje “Hello World!” y “We are using AdventureCharacter” impresos en la pantalla. Deberías poder moverte con WASD o las teclas de flecha y saltar con la barra espaciadora.
Siguiente
Tienes un personaje en movimiento, pero aún le faltan la malla y la cámara adecuadas. En la siguiente sección, hay más información acerca de cómo crear un componente de cámara, vincularlo a tu personaje y añadir mallas esqueléticas para conseguir un punto de vista real en primera persona.
Añadir una cámara en primera persona, una malla y una animación
Más información sobre cómo utilizar C++ para vincular componentes de malla y de cámara a un personaje en primera persona.
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;