Antes de empezar
Asegúrate de haber completado los siguientes objetivos en las secciones anteriores de Programar un juego de aventuras en primera persona (Cómo programar un juego de aventuras en primera persona):
Crea un personaje jugador en primera persona en C++ en Crear un personaje jugable con acciones de entrada.
Configura elementos de jugabilidad basados en datos para gestionar los datos de los elementos en Administrar elementos y datos.
Has creado un objeto de recogida y lo has añadido a tu nivel en Crear un elemento de recogida que reaparece.
Creación de elementos de referencia con una nueva función CreateItemCopy
Antes de empezar a crear un nuevo objeto equipable, primero tendrás que modificar las clases ItemDefinition y PickupBase para poder capturar un objeto de referencia de entre una variedad más amplia de tipos de objeto.
En la función InitializePickup() de tu clase PickupBase, establece un ReferenceItem de tipo UItemDefinition. Esto es demasiado restrictivo; al ajustar un elemento de referencia de esta forma, no se incluirán las propiedades adicionales que añadirías a cualquier nueva clase de elemento especializada derivada de UItemDefinition.
Para solucionar este problema, crearás una nueva función virtual en ItemDefinition que cree y devuelva una copia de ese elemento. Como se trata de una función virtual, puedes anular en cualquier clase que herede de UItemDefinition. Cuando PickupBase llama a la función, el compilador determina cuál es la función a la que llamar en función de la clase desde la que se haya realizado la llamada.
Si añades esta función a la clase principal ItemDefinition, te asegurarás de que esté disponible si decides seguir ampliando tu proyecto para incluir más tipos de elementos heredados de UItemDefinition.
Si quieres definir una nueva función CreateItemCopy() para crear elementos de referencia, sigue estos pasos:
Abre
ItemDefinition.h. En la secciónpública, declara una nuevafunción const virtual llamada CreateItemCopy()que devuelve un punteroUItemDefinition.C++// Creates and returns a copy of the item. virtual UItemDefinition* CreateItemCopy() const;En
ItemDefinition.cpp, implementa la funciónCreateItemCopy(). Dentro, crea un nuevo puntero de objetoUItemDefinitionllamadoItemCopyusandoStaticClass().C++UItemDefinition* UItemDefinition::CreateItemCopy() const { UItemDefinition* ItemCopy = NewObject<UItemDefinition>(StaticClass()); }Visual Studio convierte
UItemDefinition::StaticClass()enStaticClass().Asigna cada campo de
ItemCopya los campos de esta clase y devuelveItemCopy:C++/** * Creates and returns a copy of this Item Definition. * @return a copy of the item. */ UItemDefinition* UItemDefinition::CreateItemCopy() const { UItemDefinition* ItemCopy = NewObject<UItemDefinition>(StaticClass()); ItemCopy->ID = this->ID; ItemCopy->ItemType = this->ItemType;
A continuación, refactoriza tu función InitializePickup() eliminando el código que configura manualmente ReferenceItem y reemplázalo con una llamada a CreateItemCopy().
Para actualizar InitializePickup() con tu nueva función CreateItemCopy(), sigue estos pasos:
Abre
PickupBase.cppy dirígete aInitializePickup().Elimina estas cinco líneas que definen y establecen
ReferenceItem:C++ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass()); ReferenceItem->ID = ItemDataRow->ID; ReferenceItem->ItemType = ItemDataRow->ItemType; ReferenceItem->ItemText = ItemDataRow->ItemText; ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;En
ReferenceItem, llama aTempItemDefinition->CreateItemCopy():C++// Create a copy of the item with the class type ReferenceItem = TempItemDefinition->CreateItemCopy();
Guarda PickupBase.cpp. Tu función InitializePickup() ahora debería verse de la siguiente manera:
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());
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();
// Create a copy of the item with the class type
ReferenceItem = TempItemDefinition->CreateItemCopy();
}Definir datos de herramienta equipable
En la sección anterior, has aprendido a crear en tu nivel objetos de recogida con los que se puede interactuar, que son representaciones concretas de los datos de una tabla. En esta sección, aprenderás a crear herramientas para que tu personaje se equipe.
Para configurar una nueva herramienta equipable, tendrás que crear:
EquippableToolDefinition: una clase de recurso de datos derivada deItemDefinitionque almacena los datos de la herramienta.EquippableToolBase: una clase de actor para representar la herramienta en el juego. Proporciona a tu personaje las animaciones, las asignaciones de entrada y la malla para que el personaje pueda sujetar y utilizar la herramienta.
Para que tu personaje pueda recoger y equipar herramientas, tendrás que añadir lo siguiente:
Un lugar para almacenar objetos.
Una forma de saber de qué tipo es cada objeto de su inventario.
Una forma de equipar herramientas.
Recuerda que el actor EquippableToolBase representa la herramienta que usa y sujeta tu personaje, mientras que el actor PickupBase representa el objeto que se ha de recoger en el nivel. Tu personaje tiene que colisionar con el objeto de recogida antes de poder equiparlo, por lo que también tendrás que modificar PickupBase para que otorgue el objeto al personaje después de una colisión con éxito.
Luego, combinarás tu nueva clase de herramienta con los elementos de recogida y la tabla de datos que ya has creado para crear un iniciador de dardos personalizado y vincularlo a tu personaje.
Primero, definirás los datos de tu herramienta en una nueva clase ItemDefinition.
Para crear una nueva clase EquippableToolDefinition, sigue estos pasos:
En Unreal Editor, ve a Herramientas > Nueva clase C++. Ve a All Classes, busca y selecciona ItemDefinition como clase principal y clic en Next.
Llama a la clase
EquippableToolDefinitiony clic en Crear clase.En Visual Studio, en la parte superior de
EquippableToolDefinition.h, añade una inclusión para«ItemDefinition.h», luego añade las siguientes declaraciones de tipo forward:clase UInputMappingContext: cada herramienta equipable debería contener una referencia a un contexto de asignación de entrada que aplicarás al personaje que empuñe esa herramienta.clase AEquippableToolBase: el actor que representa tus herramientas en el juego. Lo crearás en el siguiente paso.C++#pragma once #include "CoreMinimal.h" #include "ItemDefinition.h" #include "EquippableToolDefinition.generated.h" class AEquippableToolBase; class UInputMappingContext; UCLASS(BlueprintType, Blueprintable)
En la sección
pública, añade una propiedadTSubclassOfde tipoAEquippableToolBasellamadaToolAsset. Asigna una macroUPROPERTY()conEditDefaultsOnly.C++// The tool actor associated with this item UPROPERTY(EditDefaultsOnly) TSubclassOf<AEquippableToolBase> ToolAsset;TSubclassOf<AEquippableToolBase>es un contenedor de plantilla deUClassque permite hacer referencia a subclases de blueprint deAEquippableToolBasea la vez que garantiza la seguridad de tipos. Es útil en situaciones de jugabilidad en las que quieras generar distintos tipos de actores de forma dinámica.Usarás
ToolAssetpara aparecer dinámicamente un actor herramienta cuando se equipe a tu personaje.Declara una anular para la función
CreateItemCopy()que declaraste enUItemDefinition. Este anular crea y devuelve una copia de la claseUEquippableToolDefinition.Tu archivo
EquippableToolDefinition.hcompleto debería tener el siguiente aspecto:C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "ItemDefinition.h" #include "EquippableToolDefinition.generated.h" class AEquippableToolBase; class UInputMappingContext;En
EquippableToolDefinition.cpp, implementa la funciónCreateItemCopy(). Debería parecerse a la funciónCreateItemCopy()deItemDefinition.cpp, solo que ahora también copiarásToolAsset.C++// Copyright Epic Games, Inc. All Rights Reserved. #include "EquippableToolDefinition.h" UEquippableToolDefinition* UEquippableToolDefinition::CreateItemCopy() const { UEquippableToolDefinition* ItemCopy = NewObject<UEquippableToolDefinition>(StaticClass()); ItemCopy->ID = this->ID; ItemCopy->ItemType = this->ItemType;
Guarda los dos archivos de clase EquippableToolDefinition.
Configuración de un actor herramienta equipable
A continuación, empieza a construir tu actor herramienta equipable. Representación en el juego que añade las animaciones, los controles y la malla de la herramienta al personaje.
Para crear y configurar un nuevo actor de herramienta equipable base, sigue estos pasos:
En Unreal Editor, ve a Herramientas > Nueva clase C++. Selecciona Actor como clase principal y nombra la clase EquippableToolBase.
Haz clic en Crear clase. Unreal Engine abre automáticamente los archivos de tu nueva clase en VS.
En la parte superior de
EquippableToolBase.h, Declara hacia delante las clasesAAdventureCharacteryUInputAction. Es necesario saber a qué personaje está equipada la herramienta equipable para poder vincular acciones de entrada específicas de la herramienta a ese personaje.En la macro
UCLASSde la declaración de clase, añade los especificadoresBlueprintTypeyBlueprintablepara exponer esta clase a los blueprints.C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API AEquippableToolBase : public AActor
Declarar animaciones de herramientas
En EquippableToolBase.h, en la sección pública, añade dos propiedades TObjectPtr a UAnimBlueprint llamadas FirstPersonToolAnim y ThirdPersonToolAnim. Son las animaciones en primera y tercera persona que usa el personaje cuando está equipado con esta herramienta.
Asigna a estas propiedades una macro UPROPERTY() con EditAnywherey BlueprintReadOnly.
// First Person animations
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UAnimBlueprint> FirstPersonToolAnim;
// Third Person animations
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UAnimBlueprint> ThirdPersonToolAnim;Creación de la malla de la herramienta
En EquippableToolBase.h, en la sección pública, añade un TObjectPtr a un USkeletalMeshComponent llamado ToolMeshComponent. La malla esquelética de la herramienta que ve el personaje cuando está equipada. Ponle una macro UPROPERTY() con EditAnywhere y BlueprintReadOnly.
// Tool Skeletal Mesh
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<USkeletalMeshComponent> ToolMeshComponent;En EquippableToolBase.cpp, modifica la función constructora AEquippableToolBase() para crear un USkeletalMeshComponent predeterminado y asígnalo a ToolMeshComponent. Después, comprueba si el ToolMeshComponent es nulo para asegurarte de que tu herramienta tiene un modelo cuando se carga.
AEquippableToolBase::AEquippableToolBase()
{
// 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 tool's mesh component
ToolMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("ToolMesh"));
check(ToolMeshComponent != nullptr);
}
Declaración del propietario de la herramienta
En EquippableToolBase.h, en la sección pública, crea un TObjectPtr para una instancia de tu clase Personaje llamada OwningCharacter. Ponle una macro UPROPERTY() con EditAnywhere y BlueprintReadOnly.
Este es el personaje al que está equipado actualmente esta herramienta.
// The character holding this tool
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<AAdventureCharacter> OwningCharacter;Declara una entrada y una función Usar-herramienta
Tu herramienta viene con un contexto de asignación de entrada y una acción de entrada que se deben aplicar al personaje.
Para añadir el contexto de asignación de entradas, en la sección pública, declara un TObjectPtr a un UInputMappingContext nombrado ToolMappingContext. Dale una macro UPROPERTY() con EditAnywhere y BlueprintReadOnly.
// The input mapping context associated with this tool
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UInputMappingContext> ToolMappingContext;De forma similar a cuando implementaste los controles de movimiento, añadirás una función que implementa una acción de usar herramienta y una nueva función que vincula una acción de entrada a la función.
En EquippableToolBase.h, en la sección pública, declara dos funciones virtuales vacías llamadas Use() y BindInputAction().
Cuando implementaste los controles de movimiento del personaje, usaste la función BindAction() de InputComponent que requiere que pases el nombre exacto de la función objetivo. Como aún no conoces el nombre completo de la función, necesitarás una función BindInputAction() personalizada que puedas implementar en cada subclase EquippableToolBase para llamar a BindAction, pasando [ToolChildClass]::Use.
La función BindInputAction() toma un puntero const UInputAction y vincula la acción de entrada dada a la función Use() del personaje.
// Use the tool
UFUNCTION()
virtual void Use();
// Binds the Use function to the owning character
UFUNCTION()
virtual void BindInputAction(const UInputAction* ActionToBind);En EquippableToolBase.cpp, implementar las funciones Use() y BindInputAction(). Estos no harán nada en la clase principal, por lo que puedes dejarlos en blanco por ahora. Añadirás lógica a estas al crear las subclases *EquippableToolBase*; por ejemplo, la función *Use()* debería incluir acciones específicas de herramientas, como lanzar un proyectil o abrir una puerta.
void AEquippableToolBase::Use()
{
}
void AEquippableToolBase::BindInputAction(const UInputAction* ActionToBind)
{
}
Guarda el código y compílalo desde VS.
Tu archivo EquippableToolBase.h ahora debería tener el siguiente aspecto:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.generated.h"
class AAdventureCharacter;
class UInputAction;
EquippableToolBase.cpp ahora debería verse de la siguiente manera:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolBase.h"
#include "AdventureCharacter.h"
AEquippableToolBase::AEquippableToolBase()
{
// 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;
Conceder elementos a un personaje
Has definido las herramientas que tu personaje puede usar, pero aún no puede equiparlas. A continuación, añadirás un sistema de inventario para que el personaje pueda almacenar y equiparse objetos al recogerlos.
Crear un componente de inventario
El inventario de tu personaje debería añadirle función al personaje pero no existir en el mundo del juego, así que usarás una clase de Componentes del actor para definir un inventario que sepa qué objetos tiene un personaje, pueda intercambiar herramientas y pueda evitar que el personaje obtenga más de una de la misma herramienta.
En Unreal Editor, ve a Herramientas > Nueva clase C++. Selecciona Componente del actor como clase principal y nombra a la clase InventoryComponent.
Haz clic en Crear clase.
En VS, en la parte superior de InventoryComponent.h, Declara hacia adelante una UEquippableToolDefinition. Esta es la clase que almacenarás en tu inventario.
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ADVENTUREGAME_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
En la sección pública, declara un nuevo TArray de punteros UEquippableToolDefinition llamado ToolInventory. Asigna la macro UPROPERTY() con VisibleAnywhere, BlueprintReadOnly y Category = Tools.
public:
// Sets default values for this component's properties
UInventoryComponent();
// The array of tools stored in this inventory.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Tools)
TArray<UEquippableToolDefinition*> ToolInventory;Este inventario solo almacena herramientas, pero puedes ampliarlo para incluir cualquier tipo de objeto que quieras. Una implementación más genérica almacenaría solo los valores UItemDefinition o TSubclassOf<UItemDefinition> para crear un inventario más complejo con IU, iconos, efectos de sonido, coste y otras propiedades de los elementos.
Tu InventoryComponent.h completo el archivo ahora debería verse de la siguiente manera:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
Añadir declaraciones de herramientas e inventario al Personaje
Ahora que ya tienes dónde guardar tus objetos, puedes mejorar a tu personaje con lógica que le otorgue objetos.
Para empezar, en la parte superior del archivo .h de tu personaje, declara hacia adelante las clases AEquippableToolBase, UEquippableToolDefinition y UInventoryComponent.
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "AdventureCharacter.generated.h"
class AEquippableToolBase;
class UAnimBlueprint;
En la sección protegida, declara un TObjectPtr a una UInputAction llamada UseAction. Se trata de la acción de entrada «usar herramienta» que vincularás a la función Use() de la herramienta.
// Use Input Actions
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputAction> UseAction;Cree el componente de inventario del personaje
En el archivo .h del personaje, en la sección pública, declara un TObjectPtr para un UInventoryComponent llamado InventoryComponent. Dale una macro UPROPERTY() conVisibleAnywhere y Category = Inventory.
// Inventory Component
UPROPERTY(VisibleAnywhere, Category = Inventory)
TObjectPtr<UInventoryComponent> InventoryComponent;En la función de constructor de tu personaje, después de crear el subobjeto del Componente de malla, crea un subobjeto predeterminado UInventoryComponent llamado InventoryComponent. Esto garantiza que tu inventario esté configurado correctamente cuando aparezca el Personaje.
// Create an inventory component for the owning player
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));Comprobar el inventario existente
Antes de vincular la herramienta, comprueba si el jugador ya tiene la herramienta para no tener que equiparla varias veces.
En el archivo .h del personaje, en la sección pública, declara una función llamada IsToolAlreadyOwned() que tome un puntero UEquippableToolDefinition y devuelva verdadero si esa herramienta ya existe en el inventario del jugador.
// Returns whether or not the player already owns this tool
UFUNCTION()
bool IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition);En el archivo .cpp de tu personaje, AdventureCharacter.cpp, implementa la función IsToolAlreadyOwned(). Dentro, usa un bucle for para obtener todas las herramienta del inventario del personaje accediendo a la matriz InventoryComponent->ToolInventory.
bool AAdventureCharacter::IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition)
{
// Check that the character does not yet have this particular tool
for (UEquippableToolDefinition* InventoryItem : InventoryComponent->ToolInventory)
{
}
}
Luego, en una instrucción if, comprueba si el ToolDefinition->ID de la herramienta pasada a esta función coincide con el InventoryItem->ID. Si es así, devuelve verdadero, ya que el personaje ya posee esta herramienta. De lo contrario, después del loop for, return false, ya que ToolDefinition no coincide con ningún elemento del inventario existente y, por lo tanto, es un elemento nuevo.
bool AAdventureCharacter::IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition)
{
// Check that the character does not yet have this particular tool
for (UEquippableToolDefinition* InventoryItem : InventoryComponent->ToolInventory)
{
if (ToolDefinition->ID == InventoryItem->ID)
{
return true;
}
}
Adjuntar una herramienta
En la .h de tu personaje, en la sección pública, declara una función llamada AttachTool() que tome un puntero UEquippableToolDefinition. Esta función intenta equipar al jugador con la herramienta incluida en la definición de herramienta.
// Attaches and equips a tool to the player
UFUNCTION()
void AttachTool(UEquippableToolDefinition* ToolDefinition);En la sección protegida, declara un TObjectPtr a un AEquippableToolBase llamado EquippedTool. Dale los especificadores VisibleAnywhere, BlueprintReadOnly y Category = Tools UPROPERTY().
// The currently-equipped tool
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Tools)
TObjectPtr<AEquippableToolBase> EquippedTool;En el archivo .cpp de tu personaje, implementa AttachTool(). Primero, en una declaración if, comprueba si el Personaje ya tiene la herramienta llamando a IsToolAlreadyOwned().
void AAdventureCharacter::AttachTool(UEquippableToolDefinition* ToolDefinition)
{
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
}
}
Generar un objeto
La herramienta AEquippableToolBase almacenada en ToolDefinition es un actor, por lo que es posible que no se cargue cuando se llame a AttachTool(). Para solucionar este problema, vas a generar una nueva instancia de la herramienta con la función SpawnActor().
SpawnActor() forma parte del objeto UWorld, que es el objeto principal que representa el mapa y los actores que hay en él. Accede a él llamando a la función GetWorld() desde cualquier actor.
En la sentencia if, declara un nuevo puntero AEquippableToolBase llamado ToolToEquip. Iguala esto al resultado de llamar a GetWorld()->SpawnActor(), pasando ToolDefinition->ToolAsset como el actor a generar ythis->GetActorTransform() como la posición generar.
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
// Spawn a new instance of the tool to equip
AEquippableToolBase* ToolToEquip = GetWorld()->SpawnActor<AEquippableToolBase>(ToolDefinition->ToolAsset, this->GetActorTransform());
}Cuando pasas ToolDefinition->ToolAsset a SpawnActor, UE sabe que tiene que fijarse en el tipo de clase de ToolAssety aparecer ese tipo de actor. (ToolAsset es el actor EquippableToolBase asociado a esa ToolDefinition).
Adjuntar un objeto al personaje
Para vincular la herramienta generada a tu personaje, declara un nuevo FAttachementTransformRules llamado AttachementRules.
FAttachementTransformRules es una estructura que define cómo gestionar la posición, la rotación y la escala al vincular. Hace falta EAttachmentRules y un booleano InWeldSimulateBodies al final para indicar a UE si se han aplicado física. Cuando es verdadero, UE une ambos cuerpos para que puedan interactuar como uno solo cuando se mueven. Algunas reglas de vinculación populares son KeepRelative (mantiene la transformación relativa a la principal), KeepWorld (mantiene la transformación del mundo) y SnapToTarget (acoplar a la transformación de la raíz).
En tu definición de AttachmentRoles, añade EAttachmentRule::SnapToTarget y true.
// Attach the tool to the First Person Character
FAttachmentTransformRules AttachmentRules(EAttachmentRule::SnapToTarget, true);A continuación, llama a ToolToEquip->AttachToActor() para vincular la herramienta al personaje, seguido de ToolToEquip->AttachToComponent() para vincular la herramienta al socket derecho del componente de malla de primera persona.
AttachToActor vincula un actor a un actor principal objetivo y AttachToComponent vincula el componente raíz de un actor al componente objetivo. Tienen la siguiente sintaxis:
MyActor->AttachToActor(ParentActor, AttachmentRules, OptionalSocketName)
// Attach the tool to this character, and then the right hand of their first-person mesh
ToolToEquip->AttachToActor(this, AttachmentRules);
ToolToEquip->AttachToComponent(FirstPersonMeshComponent, AttachmentRules, FName(TEXT("HandGrip_R")));Añadir las animaciones del objeto al Personaje
Establece las animaciones en las mallas de primera y tercera persona con SetAnimInstanceClass(), pasando las animaciones en primera y tercera persona de la herramienta.
// Set the animations on the character's meshes.
FirstPersonMeshComponent->SetAnimInstanceClass(ToolToEquip->FirstPersonToolAnim->GeneratedClass);
GetMesh()->SetAnimInstanceClass(ToolToEquip->ThirdPersonToolAnim->GeneratedClass);SetAnimInstanceClass establece dinámicamente el blueprint de animación en tiempo de ejecución para una malla esquelética y se usa comúnmente al equipar objetos y armas con diferentes conjuntos de animaciones. GeneratedClass obtiene la clase AnimInstance real generada a partir del blueprint.
Añadir el objeto al inventario
Añadir la herramienta al inventario del personaje usando ToolInventory.Añadir().
// Add the tool to this character's inventory
InventoryComponent->ToolInventory.Add(ToolDefinition);
Ahora que la herramienta está asociada, establece que ToolToEquip->OwningCharacter sea este personaje.
ToolToEquip->OwningCharacter = this;Ya has terminado de vincular la nueva herramienta al personaje, así que configura *ToolToEquip* para EquippedTool.
EquippedTool = ToolToEquip;Añadir los Controles de un objeto al Personaje
A continuación, añade al personaje la acción de entrada y el contexto de asignación de entrada de la herramienta.
Tendrás que implementarlo de forma similar a la configuración de AAdventureCharacter::BeginPlay() en la sección Vinculación de la asignación de entrada al personaje de Configuración del movimiento del personaje: consigue el PlayerController, después el subsistema de entrada local aumentado usando las declaraciones if para comprobar que los punteros sean nulos mientras estás en marcha.
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(ToolToEquip->ToolMappingContext, 1);
}
}
Esta vez, cuando añadas el contexto de asignación de acciones de la herramienta al subsistema del jugador, fija la prioridad en 1. La prioridad del contexto de asignación principal del jugador (FirstPersonContext) es menor (0), por lo que cuando ambos contextos de asignación tienen la misma vinculación de fotogramas clave, la vinculación de entrada en ToolToEquip->ToolMappingContext tiene prioridad sobre FirstPersonContext.
Después de añadir el contexto de asignación, llama a ToolToEquip->BindInputAction() pasando UseAction para vincular la acción en las entrada del personaje a la función Use() de la herramienta.
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(ToolToEquip->ToolMappingContext, 1);
}
ToolToEquip->BindInputAction(UseAction);
}
Toda la función AttachTool() debería tener un aspecto similar al siguiente:
void AAdventureCharacter::AttachTool(UEquippableToolDefinition* ToolDefinition)
{
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
// Spawn a new instance of the tool to equip
AEquippableToolBase* ToolToEquip = GetWorld()->SpawnActor<AEquippableToolBase>(ToolDefinition->ToolAsset, this->GetActorTransform());
// Attach the tool to the First Person Character
Admitir diferentes tipos de elementos con GiveItem()
Existe una forma de vincular herramientas, pero como los elementos para recoger y sus definiciones de elemento pueden contener más que herramientas, necesitas saber con qué tipo de objeto está interactuando tu personaje antes de llamar a AttachTool().
Crear una función GiveItem() para realizar distintas acciones en función del tipo de ItemDefinition que se pase. Has declarado distintos tipos de elementos con el enum EItemType en ItemData.h y puedes usar esos datos para diferenciar entre distintas definiciones del elemento.
En AdventureCharacter.h, en la sección pública, declara una función llamada GiveItem() que tome un puntero UItemDefinition(). Otras clases llaman a esta función cuando intentan darle un objeto al jugador.
// Public function that other classes can call to attempt to give an item to the player
UFUNCTION()
void GiveItem(UItemDefinition* ItemDefinition);En AdventureCharacter.cpp, implementa GiveItem(). Empieza declarando una sentencia de cambio donde los casos en función del ItemType del elemento se hayan pasado a esta función.
void AAdventureCharacter::GiveItem(UItemDefinition* ItemDefinition)
{
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
}
}
Dentro de la sentencia del interruptor, declara los casos para EItemType::Tool, EItemType::Consumable y un caso por defecto. En este tutorial, solo implementarás el objeto de tipo herramienta, así que en los casos de consumible y por defecto, registra el tipo de objeto y sal del caso del interruptor.
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
case EItemType::Tool:
{
}
case EItemType::Consumable:
{
// Not yet implemented
break;
Dentro de la caja de herramientas, convierte ItemDefinition en un puntero UEquippableToolDefinition llamado ToolDefinition.
A continuación, garantizar si ToolDefinition es nulo para asegurarte de que se haya realizado correctamente. Si no es nulo, llama a AttachTool() para vincular la herramienta al personaje. De lo contrario, imprime el error e interrúmpelo.
case EItemType::Tool:
{
// If the item is a tool, attempt to cast and attach it to the character
UEquippableToolDefinition* ToolDefinition = Cast<UEquippableToolDefinition>(ItemDefinition);
if (ToolDefinition != nullptr)
{
AttachTool(ToolDefinition);
}
Tu función GiveItem() completa debería tener el siguiente aspecto:
void AAdventureCharacter::GiveItem(UItemDefinition* ItemDefinition)
{
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
case EItemType::Tool:
{
// If the item is a tool, attempt to cast and attach it to the character
Por último, necesitas un trigger en el juego para establecer tu lógica de concesión de objetos. Cuando un personaje colisiona con un objeto coleccionable, este debería realizar una llamada a la función GiveItem() del personaje para otorgarle el elemento ReferenceItem del personaje.
Para ello, abre PickupBase.cpp.
En OnSphereBeginOverlap(), después de comprobar si el Personaje es válido, llama a Personaje->GiveItem(ReferenceItem) para otorgar el objeto a tu personaje.
// Checking if it is a First Person Character overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);
if (Character != nullptr)
{
// Give the item to the character
Character->GiveItem(ReferenceItem);
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
Ahora que ya has configurado los datos de las herramienta y el actor, puedes usarlos para crear una herramienta real para que tu personaje la equipe en el juego.
Implementación de un iniciador de dardos
Como primera herramienta equipable, crearás un iniciador de dardos capaz de lanzamiento proyectiles. En esta sección, empezarás creando la herramienta que tu personaje sujetará y usará. En la siguiente sección de este tutorial, implementarás la lógica de proyectiles.
Configuración de una nueva clase de DartLauncher
En Unreal Editor, ve a Herramientas > Nueva clase C++.
Ve a Todas las clases, busca y selecciona EquippableToolBase como clase principal y nombra a la clase DartLauncher. Crea una nueva carpeta llamada Herramientas para almacenar el código de tus herramientas. Haz clic en Crear clase.
En Visual Studio, en la parte superior de DartLauncher.h:
Añade una instrucción de inclusión para
«[ProjectName]/EquippableToolBase.h».Añade los especificadores
BlueprintTypeyBlueprintablea la macroUCLASS().En la sección
pública, declara las anulaciones para las funcionesUse()yBindInputAction()deAEquippableToolBase.
Tu DartLauncher.h completo la clase debería tener el siguiente aspecto:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "DartLauncher.generated.h"
UCLASS(BlueprintType, Blueprintable)
class ADVENTUREGAME_API ADartLauncher : public AEquippableToolBase
En DartLauncher.cpp, al principio del archivo, añade una instrucción de inclusión para «[ProjectName]/AdventureCharacter.h». Lo necesitarás en la función BindInputAction().
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"Implementar Controles de la herramienta
Ahora que estás trabajando en una herramienta específica y sabes qué función estás vinculando, puedes implementar BindInputAction().
Primero, implementa la función Use(). Dentro de Use(), añade un mensaje de depurar que notifique cuando el jugador triggers la función.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
A continuación, implementa la función BindInputAction(). Dentro de la función, en una instrucción if, obtén el controlador de jugador para el OwningCharacter usando GetController() y luego conviértelo en APlayerController. Es similar a añadir un contexto de asignación en la función AAdventureCharacter::BeginPlay().
void ADartLauncher::BindInputAction(const UInputAction* InputToBind)
{
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(OwningCharacter->GetController()))
{
}
}
Al igual que cuando vinculas tus acciones de movimiento en la sección Vinculación de acciones de movimiento de Configuración del movimiento del personaje, en otra declaración if, declara un nuevo puntero UEnhancedInputComponent llamado EnhancedInputComponent. Iguala esto con el resultado de llamar a Cast() en el PlayerInputComponent que se pasa a esta función mientras se convierte a UEnhancedInputComponent.
Por último, usa BindAction para vincular la acción ADartLauncher::Use a la acción InputToBind que se pasa a esta función mediante BindAction(). Esto vincula InputAction a Use(); para que, cuando se produzca la acción indicada, se llame a Use().
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(OwningCharacter->GetController()))
{
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent))
{
// Fire
EnhancedInputComponent->BindAction(InputToBind, ETriggerEvent::Triggered, this, &ADartLauncher::Use);
}
}
Cuando configuraste el movimiento de tu personaje, usaste CastChecked<>, que bloquea el juego si falla. En este caso, no quieres que el juego se detenga si los controles de activación no se inicializan correctamente, así que usa Cast<>. Usa CastChecked<> solo cuando una conversión fallida indicaría un error grave.
Guarda el código y compílalo.
Ahora tu función BindInputAction() y tu clase DartLauncher.cpp completa deberían tener este aspecto:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
Cómo adaptar un blueprint de animación a tu Personaje
La plantilla en primera persona incluye un blueprint de animación de muestra para objetos de tipo arma, pero tendrás que hacer algunos cambios en el blueprint para que funcione con tu iniciador de dardos.
Para adaptar un blueprint de animación existente para tu personaje, sigue los siguientes pasos:
En el explorador de contenido, ve a la carpeta Content > Variant_Shooter > Anim, haz clic derecho en el
blueprint de animación ABP_FP_Pistoly selecciona Duplicate.Llama a esta copia ABP_FP_DartLauncher, arrástrala a la carpeta Contenido > FirstPerson > Animaciones y selecciona Mover aquí.
ABP_TP_Pistolno usa ninguna lógica específica del personajeBP_FPShooter; no hace falta que la modifiques para tu personaje.En la parte situada al lado de la parte superior del grafo de eventos, haz zoom para ampliar el grupo de nodos que empieza por un nodo de evento blueprint Begin Play.
Este blueprint obtiene variables de malla en primera persona y Cámara en primera persona de
BP_FPShooter, así que cambiarás esto para usar tu blueprint de Personaje en su lugar (este tutorial usaBP_AdventureCharacter).Haz clic en cada uno de estos nodos y pulsa Delete:
Proyectar a BP_FPShooter
Malla en primera persona
Cámara en primera persona
Haz clic derecho en el Gráfico de eventos y, a continuación, busca y selecciona un nodo Proyectar a BP_AdventureCharacter.
Conecta los pines de ejecución del Evento blueprint de comenzar reproducción al nuevo nodo, y luego al nodo establecer malla en primera persona.
Conecta el pin de Valor de retorno del nodo Intentar conseguir propietario de peón al pin Proyectar al objeto del nodo.
Para crear un nuevo nodo a partir de Proyectar a BP_AdventureCharacter, haz clic en el pin Como BP Mi personaje de aventuras y arrastra a un lugar vacío del grafo.
Busca Conseguir malla y selecciónalo en Variables > Personaje. Conecta el pin de Malla del nuevo nodo al nodo Establecer malla de primera persona.
Para la cámara, arrastra otro nodo desde el pin Como BP El personaje de mi primera persona, busca y selecciona Conseguir componente por clase.
Asegúrate de crear un nodo Conseguir componente por clase con «by» en minúsculas.
En el nuevo nodo, establece Clase de componente como Componente de la cámara. Después, conecta el pin Valor de retorno al nodo Establecer cámara en primera persona.
Guarda tu blueprint ABP_FP_DartLauncher y compílalo.
Definir controles de iniciador de dardos
Tu iniciador de dardos necesita una acción de entrada y un contexto de asignación de entrada para que el personaje pueda disparar proyectiles desde la herramienta.
Si quieres crear controles de jugador para tu iniciador de herramienta, sigue estos pasos:
En el Explorador de contenido, ve a la carpeta Entrada > Acciones.
Crea y configura una acción de entrada «use herramienta»:
Haz clic en Añadir, dirígete a Entrada y selecciona Acción de entrada. Llámalo IA_UseTool.
Haz doble clic en IA_UseTool para abrirlo. En su panel de detalles , asegúrate de que el tipo sea Digital (booleano).
Haz clic el botón del signo más (+) junto a triggers y selecciona Pulsado en la lista de triggers.
Guarda y cierra la acción de entrada.
De vuelta en el Explorador de contenido, ve a la Carpeta Entrada.
Crear y configura un nuevo contexto de asignación de entrada que asigne el botón izquierdo del ratón y el trigger derecho del mando a la acción Usar del lanzador de dardos:
Crea un nuevo Contexto de asignación de entradas llamado IMC_DartLauncher.
Abre IMC_DartLauncher. Haz clic en el botón más junto a Asignaciones.
En la lista desplegable, selecciona
IA_UseTool.Haz clic en la flecha para expandir la asignación. Haz clic en el botón del teclado y pulsa el botón izquierdo del ratón para vincular ese botón a
IA_UseTool.Haz clic en el botón del signo más que hay junto a IA_UseTool para añadir otra vinculación. En la lista desplegable, expande Gamepad y selecciona Eje de triggers derechos del mando.
Guarda y cierra el contexto de asignación de entradas.
Creación del blueprint de DartLauncher
De vuelta en el editor principal, en el árbol de recursos del Explorador de contenido, ve a la carpeta Clases de C++, haz clic derecho en tu clase de DartLauncher, y crea una nueva clase de Blueprint.
Llámalo BP_DartLauncher. En FirstPerson > Blueprints, crea una nueva carpeta llamada Herramientas para almacenar los elementos que se pueden equipar y luego termina de crear el blueprint.
En el panel de Detalles del blueprint, establece las siguientes propiedades predeterminadas:
Establece
ABP_FP_DartLauncherpara la animación de la herramienta de primera persona.Establece
ABP_TP_Pistolpara la animación de la herramienta de tercera persona.Establece Contexto de asignación de herramienta en
IMC_DartLauncher.
En la pestaña Componentes, selecciona el Componente de malla de herramienta y define el Recurso de malla esquelética como SKM_Pistol.
Crear el recurso de datos del iniciador de Dart
Para crear un recurso de datos en el que almacenar los datos de este blueprint, sigue estos pasos:
En el explorador de contenido, en la carpeta FirstPerson > datos, crea un nuevo recurso de datos y selecciona Definición de herramienta equipable como instancia del recurso de datos. Llama a este recurso
DA_DartLauncher.Dentro de
DA_DartLauncher, en el panel de detalles , establece las siguientes propiedades:Establece Tool Asset como
BP_DartLauncher.Establece el ID en tool_001.
Selecciona Herramienta para Tipo de objeto.
Establece Mundo Mesh como
SM_Pistol.
Introduce un Nombre y una Descripción.
Guarda tu recurso de datos.
Creación de una tabla de datos para herramientas
Aunque esta herramienta podría ir en tu tabla DT_PickupData, es útil para organizar las tablas para realizar seguimientos de cosas específicas. Por ejemplo, podrías tener distintas tablas para los objetos con los que pueden equiparse clases específicas, o tablas con los objetos que sueltan los distintos enemigos al derrotarlos. En este tutorial, tendrás una tabla para consumibles y otra para herramientas.
Para crear una nueva tabla de datos y realizar un seguimiento de los elementos de la herramienta, sigue estos pasos:
En el Explorador de contenido, dirígete a la carpeta FirstPerson > datos y crea una nueva Tabla de datos.
Selecciona ItemData como estructura de filas.
Asigna el nombre DT_ToolData a la tabla y, a continuación, haz doble clic en ella para abrirla.
Dentro de DT_ToolData, clic en Añadir para crear una nueva fila para tu iniciador de dardos.
Con la nueva fila seleccionada, establece los siguientes campos:
Establece tool_001 en Nombre de la fila y el ID.
Selecciona Herramienta para Tipo de objeto.
Establece Base de objeto en
DA_DartLauncher.
Guarda y cierra la tabla de datos.
Prueba de una recogida de Dart Launcher en el juego
Has modificado tu clase de recogida para que conceda un objeto al usuario, has creado una clase de objeto equipable que proporciona al jugador una nueva malla, animaciones y controles nuevos, y configura una herramienta de iniciador de dardos. Ha llegado el momento de ponerlo todo en marcha y crear el elemento que se puede recoger en el juego triggers toda la lógica de objetos equipables que hemos configurado en esta parte del tutorial.
En el Explorador de contenido, ve a Contenido > FirstPerson > Blueprints y arrastra un nuevo BP_PickupBase al nivel. Establece tool_001 para ID del objeto de recogida y DT_ToolData para la Tabla de datos de recogida.
Haz clic en Reproducir para probar tu juego. Cuando empiece el juego, tu pastilla debería inicializarse como el iniciador de dardos. Cuando sobre a tu fonocaptor, tu personaje debería empezar a sujetar la herramienta.
Siguiente
En la sección final, implementarás la física de los proyectiles en tu iniciador de dardos y harás que se lancen dardos de espuma.
Implementación de un proyectil
Aprende a usar C++ para implementar proyectiles y aparecer durante el juego.
Código completo
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ItemData.h"
#include "ItemDefinition.generated.h"
/**
* Defines a basic item with a static mesh that can be built from the editor.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ItemDefinition.h"
#include "EquippableToolDefinition.generated.h"
class AEquippableToolBase;
class UInputMappingContext;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolDefinition.h"
UEquippableToolDefinition* UEquippableToolDefinition::CreateItemCopy() const
{
UEquippableToolDefinition* ItemCopy = NewObject<UEquippableToolDefinition>(StaticClass());
ItemCopy->ID = this->ID;
ItemCopy->ItemType = this->ItemType;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "DartLauncher.generated.h"
UCLASS(BlueprintType, Blueprintable)
class ADVENTUREGAME_API ADartLauncher : public AEquippableToolBase
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.generated.h"
class AAdventureCharacter;
class UInputAction;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolBase.h"
#include "AdventureCharacter.h"
AEquippableToolBase::AEquippableToolBase()
{
// 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;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "EquippableToolBase.h"
#include "EquippableToolDefinition.h"
#include "ItemDefinition.h"
#include "InventoryComponent.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()