Antes de empezar
Asegúrate de haber completado los siguientes objetivos de la sección anterior, Administrar elementos y datos:
Configura una estructura de datos de elemento, clase
UDataAsset, una instancia de recurso de datos de tipo consumible llamadaDA_Pickup_001y una tabla de datos.
Crear una nueva clase de recogida
Hasta ahora, has aprendido a definir y almacenar la estructura y los datos de un elemento. En esta sección, aprenderás a convertir estos datos en una "recogida" en el juego, una representación concreta de los datos de la tabla con la que el jugador puede interactuar y obtener un efecto. Una recogida puede ser un artilugio que se puede equipar, una caja que proporciona materiales o un potenciador que proporciona una mejora temporal.
Para empezar a configurar una clase de recogida con declaraciones iniciales, sigue los siguientes pasos:
En Unreal Editor, ve a Herramientas > Nueva clase de C++. Selecciona Actor como clase padre y asigna a la clase el nombre
PickupBase. Haz clic en Crear clase.En Visual Studio, abre
PickupBase.hy añade las siguientes instrucciones al principio del archivo:#include ”Components/SphereComponent.h”. Añadirás un componente de esfera al punto de recogida para detectar colisiones entre el jugador y la recogida.#include ”AdventureCharacter.h”. Añade una referencia a tu clase de personaje en primera persona para que puedas comprobar si hay solapamientos entre el punto de recogida y los personajes de esta clase. (En este tutorial se usaAdventureCharacter).Una declaración directa para
UItemDefinition. Este es el elemento del recurso de datos asociado al que hace referencia cada punto de recogida.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Components/SphereComponent.h" #include "CoreMinimal.h" #include "AdventureCharacter.h" #include "GameFramework/Actor.h" #include "PickupBase.generated.h"En la macro
UCLASS()que hay encima de la definición de claseAPickupBase, añade los especificadoresBlueprintTypeyBlueprintablepara que quede como clase base a la hora de crear blueprints.C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API APickupBase : public AActor {En
PickupBase.cpp, añade un#includeparaItemDefinition.h.C++// Copyright Epic Games, Inc. All Rights Reserved. #include "PickupBase.h" #include "ItemDefinition.h"
Inicializar la recogida con los datos de la tabla
Tu punto de recogida no es más que un actor en blanco, así que cuando empiece el juego, tienes que darle los datos que necesita para funcionar correctamente. La recogida debe extraer una fila de valores de la tabla de datos y guardar esos valores en un recurso de datos ItemDefinition (el “elemento de referencia”).
Extraer datos de una tabla de datos
En PickupBase.h, en la sección pública, declara una nueva función void InitializePickup(). Utilizarás esta función para inicializar la recogida con valores de la tabla de datos.
// Initializes this pickup with values from the data table.
void InitializePickup();Para extraer datos de la tabla, el blueprint de recogida necesita dos propiedades: el recurso de tabla de datos y el nombre de la fila (que habrás configurado para que coincida con el ID del elemento).
En la sección protegida, declara una propiedad FName llamada PickupItemID. Asígnale los especificadores EditInstanceOnly y Category = “Pickup | Item Table”. Este es el ID de esta recogida en la tabla de datos asociada.
// The ID of this pickup in the associated data table.
UPROPERTY(EditInstanceOnly, Category = "Pickup | Item Table")
FName PickupItemID;Las recogidas no deberían tener un ID de elemento por defecto, por lo que el especificador EditInstanceOnly permite editar esta propiedad en instancias de recogidas en el mundo, pero no en el arquetipo (o clase por defecto).
En el texto Categoría, la barra vertical (|) crea una subsección anidada. En este ejemplo, Unreal Engine crea una sección Recogida con una subsección llamada Tabla de elemento en el panel Detalles del recurso.
A continuación, declara un TSoftObjectPtr para una UDataTable denominada PickupDataTable. Asígnale los mismos especificadores que al PickupItemID. Esta es la tabla de datos que la recogida utiliza para obtener sus datos.
Es posible que la tabla de datos no se cargue en tiempo de ejecución, así que utiliza un TSoftObjectPtr aquí para poder cargarla de forma asíncrona.
Guarda el archivo de cabecera y cambia a PickupBase.cpp para implementar InitializePickup().
Dentro de la función, en una instrucción if, comprueba si la PickupDataTable proporcionada es válida y si PickupItemID tiene un valor.
/**
* Initializes the pickup with default values by retrieving them from the associated data table.
*/
void APickupBase::InitializePickup()
{
if (PickupDataTable && !PickupItemID.IsNone())
{
}
}En la instrucción if, añade el código para recuperar la fila de valores de la tabla de datos. Declara un puntero const FItemData con el nombre ItemDataRow y configúralo como el resultado de llamar a FindRow() en tu PickupDataTable. Especifica FItemData como el tipo de fila a buscar.
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>();FindRow() recibe dos argumentos:
Un nombre de fila
FNameque quieres buscar. PasaPickupItemIDcomo nombre de fila.Una cadena de contexto de tipo
FStringque puedes utilizar para la depuración si no se encuentra la fila. Puedes utilizarText(“Mi contexto aquí.”)para añadir una cadena de contexto o utilizarToString()para convertir el ID del elemento en una cadena de contexto.
if (PickupDataTable && !PickupItemID.IsNone())
{
// Retrieve the item data associated with this pickup from the Data Table
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString());
}Crear un elemento de referencia
Una vez que hayas recuperado los datos de la fila de recogida, crea e inicializa un ReferenceItem de tipo de recurso de datos para almacenar esa información.
Al guardar los datos en un elemento de referencia como este, Unreal Engine puede hacer referencia fácilmente a esos datos cuando necesite información sobre el elemento en lugar de realizar más búsquedas de datos en tablas, lo cual es menos eficiente.
En PickupBase.h, en la sección protegida, declara un TObjectPtr para una UItemDefinition llamada ReferenceItem. Se trata de un recurso de datos que almacena los datos de recogida. Asígnale los especificadores VisibleAnywhere y Category = “Pickup | Reference Item”.
// Data asset associated with this item.
UPROPERTY(VisibleAnywhere, Category = "Pickup | Reference Item")
TObjectPtr<UItemDefinition> ReferenceItem;Guarda el archivo de cabecera y vuelve a PickupBase.cpp.
En InitializePickup(), después de la llamada a FindRow(), establece ReferenceItem como un NewObject de tipo UItemDefinition.
En Unreal Engine, NewObject<T>() es una función de plantilla para crear dinámicamente instancias derivadas de UObject en tiempo de ejecución. Devuelve un puntero al nuevo objeto. Suele tener la siguiente sintaxis:
T* Object = NewObject<T>(Outer, Class);
Donde T es el tipo de UObject que estás creando, Outer es el propietario del objeto y Class es la clase del objeto que estás creando. El argumento Class suele ser T::StaticClass(), que proporciona un puntero UClass que representa el tipo de clase de T. Sin embargo, a menudo puedes omitir ambos argumentos, ya que UE asume que Outer es la clase actual y utiliza T para inferir la UClass.
Pasa esto como clase externa y UItemDefinition::StaticClass() como tipo de clase para crear una base UItemDefinition.
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());Para copiar la información de recogida en ReferenceItem, establece que cada campo de ReferenceItem sea el campo asociado de ItemDataRow. Para WorldMesh, extrae la propiedad WorldMesh del ItemBase al que se hace referencia en ItemDataRow.
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());
ReferenceItem->ID = ItemDataRow->ID;
ReferenceItem->ItemType = ItemDataRow->ItemType;
ReferenceItem->ItemText = ItemDataRow->ItemText;
ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;Llamar a InitializePickup()
En BeginPlay(), llama a InitializePickup() para inicializar la recogida cuando empiece el juego.
// Called when the game starts or when spawned
void APickupBase::BeginPlay()
{
Super::BeginPlay();
// Initialize this pickup with default values
InitializePickup();
}Guarda el archivo. PickupBase.cpp debería tener ahora el siguiente aspecto:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
}
Crear función en el juego
Tu recogida tiene los datos del elemento que necesita, pero aún necesita saber cómo aparecer y operar en el mundo del juego. Necesita una malla para que el jugador la vea, un volumen de colisión para determinar cuándo la toca el jugador y cierta lógica para hacer que la recogida desaparezca (como si el jugador la hubiese recogido) y reaparezca después de un tiempo determinado.
Añadir un componente de malla
Del mismo modo que hiciste cuando configuraste el personaje jugable en Añadir una cámara en primera persona, una malla y una animación, utilizarás la función de plantilla CreateDefaultSubobject para crear un objeto de malla estático como componente hijo de tu clase de recogida y, a continuación, aplicar la malla del objeto a este componente.
En PickupBase.h, en la sección protected, declara un TObjectPtr a un UStaticMeshComponent con el nombre PickupMeshComponent. Esta es la malla que representará la recogida en el mundo.
Utilizarás código para asignar la malla del recurso de datos a esta propiedad, así que asígnale los especificadores VisibleDefaultsOnly y Category = “Pickup | Mesh” para que sea visible, pero no editable, en Unreal Editor.
// The mesh component to represent this pickup in the world.
UPROPERTY(VisibleDefaultsOnly, Category = "Pickup | Mesh")
TObjectPtr<UStaticMeshComponent> PickupMeshComponent;Guarda el archivo de cabecera y cambia a PickupBase.cpp.
En la función de construcción APickupBase, establece el puntero PickupMeshComponent en el resultado de una llamada a CreateDefaultSubobject() de tipo UStaticMeshComponent. En el argumento Text, asigna al objeto el nombre PickupMesh.
A continuación, para asegurarte de que la malla se ha instanciado correctamente, comprueba que PickupMeshComponent no sea nulo.
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create this pickup's mesh component
PickupMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PickupMesh"));
check(PickupMeshComponent != nullptr);
}Ve a la implementación de InitializePickup().
Antes de aplicar WorldMesh al componente de malla de recogida, tendrás que comprobar que la malla está cargada, ya que la has definido con un TSoftObjectPtr.
En primer lugar, declara un nuevo puntero UItemDefinition con el nombre TempItemDefinition y establécelo como el resultado de llamar a ItemDataRow->ItemBase.Get().
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();A continuación, con una instrucción if, comprueba si WorldMesh está cargada actualmente llamando a WorldMesh.IsValid().
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
}En caso afirmativo, establece el PickupMeshComponent llamando a SetStaticMesh(), y recupera el WorldMesh mediante Get():
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
// Set the pickup's mesh to the associated item's mesh
PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());
}Si la malla no está cargada, fuerza su carga llamando a LoadSynchronous() en la malla. Esta función carga y devuelve un puntero de recurso a ese objeto.
Después de la instrucción if, en una instrucción else, declara un nuevo puntero UStaticMesh con el nombre WorldMesh y establécelo llamando a WorldMesh.LoadSynchronous().
A continuación, establece el componente PickupMeshComponent utilizando SetStaticMesh().
else {
// If the mesh isn't loaded, load it by calling LoadSynchronous().
UStaticMesh* WorldMesh = TempItemDefinition->WorldMesh.LoadSynchronous();
PickupMeshComponent->SetStaticMesh(WorldMesh);
}Después de la instrucción else, haz que PickupMeshComponent sea visible mediante SetVisiblity(true):
// Set the mesh to visible.
PickupMeshComponent->SetVisibility(true);Añadir una forma de colisión
Añade un componente de esfera para que actúe como volumen de colisión y activa las consultas de colisión en dicho componente.
En PickupBase.h, en la sección protected, declara un TObjectPtr a un USphereComponent con el nombre SphereComponent. Este es el componente de esfera utilizado para la detección de colisiones. Asígnale los especificadores EditAnywhere, BlueprintReadOnly y Category = “Pickup | Components”.
// Sphere Component that defines the collision radius of this pickup for interaction purposes.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Components")
TObjectPtr<USphereComponent> SphereComponent;Guarda el archivo de cabecera y cambia a PickupBase.cpp.
En la función de construcción APickupBase, después de definir el PickupMeshComponent, establece que SphereComponent sea el resultado de llamar a CreateDefaultSubobject con USphereComponent como tipo y “SphereComponent” como nombre. Añade una comprobación de null después.
// Create this pickup's sphere component
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
check(SphereComponent != nullptr);Ahora que tienes ambos componentes, utiliza SetupAttachment() para adjuntar PickupMeshComponent a SphereComponent:
// Attach the sphere component to the mesh component
SphereComponent->SetupAttachment(PickupMeshComponent);Después de adjuntar el SphereComponent al MeshComponent, establece el de tamaño de la esfera con SetSphereRadius(). Este valor debería hacer que tu colisionador de recogida sea lo suficientemente grande como para colisionar con él, pero no tanto como para que el personaje choque contra él sin querer.
// Set the sphere's collision radius
SphereComponent->SetSphereRadius(32.f);En InitializePickup(), después de la línea SetVisibility(true), haz una llamada a SetCollisionEnabled() para hacer que SphereComponent sea colisionable.
Esta función recibe un enum (ECollisionEnabled) que le dice al motor el tipo de colisión a utilizar. Lo que quieres es que el personaje sea capaz de colisionar y activar consultas de colisión con la recogida, pero la recogida no debe tener ninguna física que la haga rebotar cuando recibe un impacto, así que pasa la opción ECollisionEnabled::QueryOnly.
// Set the mesh to visible and collidable.
PickupMeshComponent->SetVisibility(true);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly); PickupBase.cpp ahora debería verse así:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Simular colisiones de recogida
Ahora que la recogida tiene una forma de colisión, añade lógica para detectar una colisión entre la recogida y el jugador y hacer que la recogida desaparezca cuando se colisione con ella.
Configurar el evento de colisión
En PickupBase.h, en la sección protected, declara una función void denominada OnSphereBeginOverlap().
Cualquier componente que herede de UPrimitiveComponent, como USphereComponent, puede implementar esta función para ejecutar código cuando el componente se solape con otro actor. Esta función utiliza varios parámetros que no usarás; solo pasarás lo siguiente:
UPrimitiveComponent* OverlappedComponent: El componente que se ha solapado.AActor* OtherActor: el actor que se solapa con el componente.UPrimitiveComponent* OtherComp: el componente del actor que se solapa.int32 OtherBodyIndex: el índice del componente solapado.bool bFromSweep, const FHitResult& SweepResult: información sobre la colisión, como dónde ocurrió y con qué ángulo.
// Code for when something overlaps the SphereComponent.
UFUNCTION()
void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);Guarda el archivo de cabecera y cambia a PickupBase.cpp.
Los eventos de colisión de Unreal Engine se implementan mediante delegados dinámico de multidifusión. En UE, este sistema de delegados permite que un objeto llame a varias funciones a la vez, algo así como enviar un mensaje a una lista de distribución donde los suscriptores son estas otras funciones. Cuando vinculamos funciones al delegado, es como si las suscribiéramos a nuestra lista de distribución. El “delegado” es nuestro evento; en este caso, una colisión entre el jugador y la recogida. Cuando se produce el evento, Unreal Engine llama a todas las funciones vinculadas a ese evento.
Unreal Engine incluye un par de funciones de vinculaciones más, pero te recomendamos que uses AddDynamic(), ya que tu delegado, OnComponentBeginOverlap, es un delegado dinámico. Además, estás vinculando una UFUNCTION en una clase UObject, lo que requiere AddDynamic() para la compatibilidad con los reflejo. Para obtener más información sobre los delegados dinámicos multidifusión, consulta los delegados multidifusiónMulti-cast Delegates.
En PickupBase.cpp, en InitializePickup(), usa la macro AddDynamic para vincular OnSphereBeginOverlap() al evento OnComponentBeginOverlap del componente esférico.
// Register the Overlap Event
SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &APickupBase::OnSphereBeginOverlap);Guarda el trabajo. Ahora, OnSphereBeginOverlap() se ejecuta cuando un personaje colisiona con el componente esférico del captador.
Ocultar la recogida después de una colisión
En PickupBase.cpp, implementa OnSphereBeginOverlap() para hacer que desaparezca el objeto para que parezca que lo ha hecho el jugador.
Empieza añadiendo un mensaje de depuración para indicar cuándo se activa esta función.
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Attempting a pickup collision"));
}Dado que esta función se ejecuta cada vez que otro actor colisiona con la recogida, debes asegurarte de que sea tu personaje en primera persona el que colisiona.
Declara un nuevo puntero AAdventureCharacter llamadoPersonaje y establécelo convirtiendo OtherActor a tu clase personaje (este tutorial usa AAdventureCharacter).
// Checking if it's an AdventureCharacter overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);En una declaración if, comprueba si Personaje no es nulo. Null indica que la conversión ha fallado y que OtherActor no era un tipo de AAdventureCharacter.
Dentro de la instrucción if, anula el registro de OnComponentBeginOverlap de esta función llamando a RemoveAll() para que no se active repetidamente. De este modo se pone fin a la colisión.
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
}A continuación, haz que PickupMeshComponent sea invisible con SetVisibility(false) y establece que la malla receptora y el componente de esfera no colisionen con SetCollisionEnabled(), pasando la opción NoCollision.
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
// Set this pickup to be invisible and disable collision
PickupMeshComponent->SetVisibility(false);
PickupMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}Guarda PickupBase.cpp.
Hacer que la recogida reaparezca
Ahora que el personaje no puede interactuar con la recogida, haz que reaparezca después de un tiempo determinado.
En PickupBase.h, en la sección protegida, declara un booleano llamado bShouldRespawn. Puedes utilizarlo para activar o desactivar las reapariciones.
Declara un float denominado RespawnTime inicializado en 4.0f. Este es el tiempo que hay que esperar hasta que la recogida debería reaparecer.
Dale a ambas propiedades EditAnywhere, BlueprintReadOnly y Category = «Pickup | Respawn».
// Whether this pickup should respawn after being picked up.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
bool bShouldRespawn;
// The time in seconds to wait before respawning this pickup.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
float RespawnTime = 4.0f;Declara un FTimerHandle llamado RespawnTimerHandle.
// Timer handle to distinguish the respawn timer.
FTimerHandle RespawnTimerHandle;En Unreal Engine, los temporizadores del jugabilidad los gestiona FTimerManager. Esta clase incluye la función SetTimer(), que llama a una función o delegado tras un retraso definido. Las funciones de FTimerManager usan un FTimerHandle para iniciar, pausar, reanudar o realizar un bucle infinito de la función. Usarás RespawnTimerHandle para indicar cuándo reaparecer el objeto. Para obtener más sobre cómo usar el gestor de temporizadores, consulta la sección Temporizadores del juego.
Guarda el archivo de cabecera y cambia a PickupBase.cpp.
Para implementar la reaparición al recoger objetos, usa el gestor de temporizadores para establecer un temporizador que llame a InitializePickup() tras una breve espera.
Solo querrás que reaparezca el objeto si las reapariciones están activadas; Por lo tanto, en la parte inferior de OnSphereBeginOverlap, añade una declaración if que compruebe si bShouldRespawn es verdadero.
if (bShouldRespawn)
{
}En la declaración if, haz que el gestor de temporizadores use GetWorldTimerManager() y llama a SetTimer() en el gestor de temporizadores. Esta función tiene la siguiente sintaxis:
SetTimer(InOutHandle, Object, InFuncName, InRate, bLoop, InFirstDelay);
Donde:
InOutHandlees elFTimerHandleque controla el temporizador (tuRespawnTimerHandle).Objetoes elUObjectpropietario de la función a la que llamas. Utiliza esto.InFuncNamees un puntero a la función a la que quieres llamar (en este caso,InitializePickup()).InRatees un valor Float que especifica el tiempo en segundos que se esperará antes de llamar a tu función.bLoophace que el temporizador se repita cadaTimesegundos (true) o solo se active una vez (false).InFirstDelay(opcional) es un delay de tiempo inicial antes de la primera llamada a función en un temporizador en bucle. Si no se especifica, UE usaInRatecomo retraso.
Si solo quieres hacer una llamada a InitializePickup() una vez para reemplazar la pastilla, establece que bLoop tenga el valor false.
Establece el tiempo de reaparición que desees; este tutorial establece que la recogida reaparezca a los cuatro segundos sin retraso inicial.
// If the pickup should respawn, wait an fRespawnTime amount of seconds before calling InitializePickup() to respawn it
if (bShouldRespawn)
{
GetWorldTimerManager().SetTimer(RespawnTimerHandle, this, &APickupBase::InitializePickup, RespawnTime, false, 0);
}Tu función OnSphereBeginOverlap() completa debería tener este aspecto:
/**
* Broadcasts an event when a character overlaps this pickup's SphereComponent. Sets the pickup to invisible and uninteractable, and respawns it after a set time.
* @param OverlappedComponent - the component that was overlapped.
* @param OtherActor - the Actor overlapping this component.
* @param OtherComp - the Actor's component that overlapped this component.
* @param OtherBodyIndex - the index of the overlapped component.
* @param bFromSweep - whether the overlap was generated from a sweep.
* @param SweepResult - contains info about the overlap such as surface normals and faces.
*/
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
Guarda tu código y compila desde Visual Studio.
Implementar recogidas en tu nivel
Ahora que ya has definido el código que conforma las recogidas, ¡ha llegado el momento de probarlas en tu juego!
Para añadir recogidas a tu nivel, sigue los siguientes pasos:
En Unreal Editor, en el árbol de recursos del explorador de contenido, ve a Content > FirstPerson > Blueprints.
En la carpeta Blueprints crea una nueva carpeta secundaria llamada Recogidas para almacenar tus clases de recogidas.
En el árbol de recursos, ve a tu carpeta Clases C++. Haz clic derecho en tu clase PickupBase para crear un blueprint a partir de ella.
Llámalo
BP_PickupBase.En Ruta, selecciona Content/FirstPerson/Blueprints/Pickups y haz clic en Crear clase base de recogida.
Vuelve a la carpeta Blueprints > Recogidas. Arrastra tu blueprint
BP_PickupBaseal nivel. Una instancia de PickupBase aparece en tu nivel y se selecciona automáticamente en el panel esquematizador. Sin embargo, todavía no tiene malla.Con el actor
BP_PickupBaseaún seleccionado, en el panel de detalles , establece las siguientes propiedades:Establece Pickup Item ID en
pickup_001.Establece Pickup Data Table como
DT_PickupData.Establece Devería reaparecer como true.
Cuando haces clic en Reproducir para probar el juego, tu recogida usa el ID del elemento de recogida para consultar la tabla de datos y recuperar datos asociados con pickup_001. La recogida usa los datos de la tabla y la referencia a tu recurso de datos DA_Pickup_001 para inicializar un elemento de referencia y cargar su malla estática.
Al pasar sobre la recogida, deberías ver cómo desaparece y reaparece cuatro segundos después.
Cargar una recogida diferente
Si estableces el ID del elemento de recogida en un valor diferente, tu recogida recuperará los datos de esa fila de la tabla.
Para experimentar cambiando el Pickup Item ID, sigue estos pasos:
Crea un nuevo recurso de datos llamado DA_Pickup_002. Establece las siguientes propiedades en este recurso:
ID: pickup_002
Item Tipo de elemento: consumible
Nombre: nombre de prueba 2
Descripción: descripción de la prueba 2
Malla del mundo:
SM_ChamferCube
Añade una nueva fila a la tabla
DT_PickupDatae introduce la información del recurso de datos en los campos de la nueva fila.En el actor
BP_PickupBase, cambia el ID de Pickup Item apickup_002.
Cuando hagas clic en Play para probar el juego, ¡el recolector debería aparecer con los valores de DA_Pickup_002!
Actualizar actores de recogida en el editor
Tus recogidas funcionan en el juego, pero puede resultar difícil visualizarlas en el editor, ya que no tienen una malla por defecto.
Para solucionar esto, usa la función PostEditChangeProperty(). Esta es una función del editor que Unreal Engine llama cuando el editor cambia una propiedad, por lo que puedes usarla para mantener a tus actores actualizados en el visor a medida que cambian sus propiedades. Por ejemplo, actualizando un elemento de la IU al cambiar la salud por defecto de un jugador o cambiando la escala de una esfera al acercarla o alejarla del origen.
En este proyecto, lo usarás para aplicar la nueva malla estática del recolector cada vez que cambie el ID del elemento del recolector. De esta forma, podrás cambiar el tipo de recogida y ver cómo se actualiza inmediatamente en el visor sin necesidad de hacer clic en reproducir.
Para que los cambios en tus recogidas aparezcan inmediatamente en el editor, sigue estos pasos:
En
PickupBase.h, en la secciónprotegida, declara una macro#if WITH_EDITOR. Esta macro indica a la herramienta UE que todo lo que contenga debe empaquetarse únicamente para las compilaciones del editor y no compilarse para las versiones de lanzamiento del juego. Termina esta macro con una declaración#endif.C++#if WITH_EDITOR #endifDentro de la macro, declara una anular de función virtual void para
PostEditChangeProperty(). Esta función toma una referencia alevento FPropertyChangedEvent, que incluye información sobre la propiedad que ha cambiado, el tipo de cambio y más. Guarda el archivo de cabecera.C++#if WITH_EDITOR // Runs whenever a property on this object is changed in the editor virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endifEn
PickupBase.cpp, implementa la funciónPostEditChangeProperty(). Empieza llamando a la funciónSuper::PostEditChangeProperty()para que se encargue de los cambios en las propiedad de la clase principal.C++/** * Updates this pickup whenever a property is changed. * @param PropertyChangedEvent - contains info about the property that was changed. */ void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { // Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); }Crea una nueva variable constante
FName llamadaChangedPropertypara almacenar el nombre de la propiedad modificada.C++// Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); const FName ChangedPropertyName;Para verificar que el
PropertyChangedEventincluye unapropiedady guardar esa propiedad, usa un operador ternario conPropertyChangedEvent.Propertycomo condición. SeleccionaPropertyChangedEvent.Property->GetFName()paraChangedPropertyNamesi la condición es verdadera yNAME_Nonesi es false.C++// If a property was changed, get the name of the changed property. Otherwise use none. const FName ChangedPropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;NAME_Nonees una constante global de Unreal Engine de tipoFNameque significa «nombre no válido» o «nombre nulo».Ahora que ya conoces el nombre de la propiedad, puedes hacer que Unreal Engine actualice la malla si detecta que ha cambiado el ID.
En una declaración
if, comprueba queChangePropertyNamees igual al resultado de llamar aGET_MEMBER_NAME_CHECKED(), pasando esta claseAPickupBaseyPickupItemID. Esta macro realiza una comprobación en tiempo de compilación para asegurarse de que la propiedad que pasas existe en la clase pasada.También obtendrás valores de la tabla de datos, así que comprueba también que la tabla sea válida antes de introducir la instrucción
if.C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { }Dentro de la declaración
if, recupera la fila de datos asociada a este repunte tal y como hiciste enInitializePickup(), llamando aFindRow.Esta vez, para asegurarte de que
PickupItemIDestá en la tabla antes de continuar, coloca la líneaFindRowen una declaraciónifanidada.C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { // Retrieve the associated ItemData for this pickup. if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { } }Si UE encuentra los datos de la fila, crea una variable
TempItemDefinitionpara almacenar el recurso de datos (que contiene la nueva malla) al que se hace referencia enItemDataRow.C++if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase;Para actualizar la malla, usa
SetStaticMeshen elPickupMeshComponenty pasa laWorldMeshdel recurso de datos temporal.C++// Set the pickup's mesh to the associated item's mesh PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());Establece el radio de colisión del Componente de esfera con
SetSphereRadius(32.f).C++// Set the sphere's collision radius SphereComponent->SetSphereRadius(32.f);Guarda tu código y compílalo desde Visual Studio.
Tu función completa de PostEditChangeProperty() debería tener este aspecto:
/**
* Updates this pickup whenever a property is changed.
* @param PropertyChangedEvent - contains info about the property that was changed.
*/
void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Handle parent class property changes
Super::PostEditChangeProperty(PropertyChangedEvent);
// If a property was changed, get the name of the changed property. Otherwise use none.
De vuelta en el editor, en el esquematizador, asegúrate de que tu actor BP_PickupBase esté seleccionado. Cambia el ID del elemento de recogida de pickup_001 a pickup_002 y vuelve a cambiarlo. A medida que cambias el ID, la malla del fonocaptor se actualiza en el visor.
Si vas a experimentar con otras mallas, puede que tengas que reproducir al juego una vez para forzar que una nueva malla se cargue por completo antes de poder verla en el visor del editor.
Siguiente
A continuación, ampliarás tu clase de recogida para crear un artilugio personalizado y equiparlo a tu personaje.
Equipa a tu personaje
Más información para usar C++ para crear objetos equipables personalizado y adjuntarlos a tu personaje.
Código completo
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/SphereComponent.h"
#include "CoreMinimal.h"
#include "AdventureCharacter.h"
#include "GameFramework/Actor.h"
#include "PickupBase.generated.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;