Antes de empezar
Asegúrate de que tienes un proyecto de Unreal Engine basado en C++.
Esta página es la continuación de Añadir una cámara en primera persona, una malla y una animación; sin embargo, puedes completarla independientemente del resto de la serie de tutoriales sobre cómo Programar un juego de aventuras en primera persona si lo que quieres es aprender más sobre la jugabilidad basadas en datos.
Organización de datos en juegos
La forma de organizar y representar los datos es una parte importante del diseño de un juego. Los elementos con los que se puede interactuar pueden tener cualidades muy distintas y pueden existir en el juego o simplemente como datos. Podrías representar estos datos creando clases y blueprints independientes para cada tipo de elemento, distribuyendo y comunicando datos entre distintos actores. Sin embargo, esto se vuelve ineficiente a medida que crece el juego y la cantidad de datos almacenados.
Un mejor enfoque sería el juego basado en datos. En vez de codificar valores, organizarás los datos en una posición centralizada gestionada por los sistemas de tu juego. El juego basado en datos te permite cargar lo que necesitas cuando lo necesitas. Por ejemplo, muchos juegos utilizan documentos de hoja de cálculo para organizar el diálogo, ya que es más fácil para un sistema extraer una línea específica de diálogo en vez de almacenar potencialmente cientos de líneas en cada personaje.
En esta sección, aprenderás a utilizar esta metodología para empezar a crear elementos personalizados.
Elementos de juego basados en datos
Antes de empezar a compilar elementos básicos, es importante tener en cuenta qué define a un “elemento”. Dado que un elemento puede ser cualquier cosa con la que interactúe un jugador, debería tener un conjunto mínimo de propiedades que sean válidas para cualquier tipo de elemento. Configurarás esto en una estructura de datos de elemento. También deberías tener un lugar centralizado donde organizar y mostrar los datos de este elemento. Para ello, utilizarás un recurso de tabla de datos.
Tu struct de datos de elementos actúa como una plantilla que define el tipo de datos que tu elemento tiene. La tabla de datos y los recursos de datos almacenan las entradas de datos reales basadas en tu struct.
El siguiente diagrama muestra los cuatro elementos de juego basados en datos que crearás en esta parte del tutorial. Cuando hayas terminado de configurar los cuatro elementos, volverás a este diagrama con más detalle para resumir lo que has creado.
Primero, crearás dos archivos para definir los datos de tus elementos:
ItemData.h: Un contenedor para la declaración de la estructura de datos de elemento (FItemData).ItemDefinition.h: Una clase que hereda deUDataAssetpara que los datos de tus elementos se puedan utilizar en Unreal Editor.
La estructura de datos de elemento no se hereda de UObject y no se pueden instanciar en un nivel, por lo que también necesitarás utilizar la clase de recurso de datos y hacer referencia a ella en el editor.
A continuación, en Unreal Editor, crearás una tabla de datos y una instancia de recurso de datos basados en ItemDefinition.
Definir los datos de elementos
Tu struct de datos de elemento define los datos o las propiedades que debe tener cada elemento de la tabla de datos y actúa como las columnas de la tabla.
Tu struct de datos de elemento tendrá las siguientes propiedades:
ID: un nombre unique para el elemento, útil para referenciar las filas de la tabla más adelante.
Tipo de elemento: el tipo de este elemento (en este caso, definirás los tipos de herramienta y consumible).
Texto de elemento: datos textuales sobre el elemento, incluidos el nombre y la descripción.
Base del elemento: el recurso de datos ItemDefinition asociado a este elemento.
Si quieres crear tus propios campos de tabla (columnas), ten en cuenta que los campos de la tabla de datos pueden ser de cualquier tipo compatible con UPROPERTY().
Crear un contenedor de archivo de cabecera para la struct
Configura una nueva carpeta y un nuevo archivo de cabecera (.h) para almacenar la definición de la estructura de datos de elemento.
Podrías crear la struct FItemData dentro de ItemDefinition.h, pero poner la struct en un archivo aparte ayuda a organizar los elementos de datos y permite su reutilización.
Para configurar un archivo de cabecera como contenedor de tu struct de datos de elemento, sigue los siguientes pasos:
En Unreal Editor, ve a Herramientas > Nueva clase de C++.
En la ventana Elegir clase padre, selecciona Ninguna como clase padre y haz clic en Siguiente.
Al lado de la Ruta, haz clic en el icono de la carpeta. En tu carpeta
Source/[ProjectName], crea una nueva carpeta con el nombreDatapara almacenar esta clase.Asigna a la clase el nombre
ItemDatay haz clic en Crear clase.Si Unreal Engine no abre tus nuevos archivos de clase automáticamente, abre tu proyecto y el archivo
ItemData.hen Visual Studio.Borra todo el texto de
ItemData.cpp, a continuación, guarda y cierra el archivo. No lo vas a utilizar.En
ItemData.h, borra todo lo que haya debajo de la línea#include “CoreMinimal.h”.La cabecera
CoreMinimal.hincluye tipos básicos comoFName,FStringy otros tipos que necesitarás para definir tus datos.En la parte superior de
ItemData.h, añade#pragma oncey las siguientes sentencias nclude:#include “Engine/DataTable.h”: requerido para que tu struct herede deFTableRowBase.#include “ItemData.generated.h”: requerido por la herramienta de cabeceras de Unreal Engine. Asegúrate de que la sentencia include aparece en último lugar para que tu código compile correctamente.
C++#pragma once #include "CoreMinimal.h" #include "Engine/DataTable.h" #include "ItemData.generated.h"Añade una declaración directa para una clase con el nombre
UItemDefinition. Será el recurso de datos con el que podrás trabajar en el editor.C++class UItemDefinition;
Definir las propiedades de los elementos
No existen tipos de variable para un tipo de elemento y datos de texto, por lo que tendrás que definirlos.
Para definir un enum de tipo de elemento, sigue los siguientes pasos:
Crea una nueva
clase enumque enumere todos los tipos de elementos posibles. En este tutorial, crearás elementos de tipo herramienta y consumible.Asigna a la clase enum el nombre
EItemTypey el tipouint8. Añade la macroUENUM()encima para declarar este enum en la herramienta de cabeceras de Unreal Engine.C++// Defines the type of the item. UENUM() enum class EItemType : uint8 { };Añade dos valores personalizados a este enum:
Tool, añadiendo una macroUMETA()conDisplayName = “Tool”Consumable, añadiendo una macroUMETA()conDisplayName = “Consumable”
C++// Defines the type of the item. UENUM() enum class EItemType : uint8 { Tool UMETA(DisplayName = "Tool"), Consumable UMETA(DisplayName = "Consumable") };
Estos tipos de elementos son personalizados y no están integrados en Unreal Engine, por lo que, una vez que hayas aprendido lo básico, ¡podrás crear lo que quieras! (¿Tal vez un QuestItem, Currency o Disguise?)
Para definir una struct de texto de elemento, sigue los siguientes pasos:
Después de
EItemType, crea una nueva estructura con el nombreFItemTextcon una macroUSTRUCT(). Esta struct contiene datos textuales sobre tu elemento.C++// Contains textual data about the item. USTRUCT() struct FItemText { };Dentro de
FItemText, añade la macroGENERATED_BODY().A continuación, añade dos propiedades
FTextcon los nombresNameyDescriptionpara almacenar el nombre y la descripción de este elemento. Añade la macroUPROPERTY()a cada una conEditAnywherecomo argumentos.C++// Contains textual data about the item. USTRUCT() struct FItemText { GENERATED_BODY() // The text name of the item. UPROPERTY(EditAnywhere) FText Name;
Crear la struct de datos de elemento
Ahora que has añadido esas declaraciones de requisitos previos, crea la struct de datos de elemento con las propiedades de tu elemento dentro. Estas propiedades se convierten en los campos de la tabla de datos.
Define una struct con el nombre FItemData que herede de FTableRowBase. Añade el especificador public para permitir que sea visible en cualquier lugar y añade GENERATED_BODY() para la herramienta de cabeceras de Unreal Engine.
// Defines a basic item that can be used in a data table.
USTRUCT()
struct FItemData : public FTableRowBase
{
GENERATED_BODY()
};FTableRowBase es una struct base de Unreal Engine que te permite utilizar tus USTRUCT personalizadas en un recurso de tabla de datos. Unreal Engine lo utiliza para saber cómo serializar tu struct de filas, para permitir la importación y exportación de datos desde archivos CSV o JSON, y para garantizar la seguridad de tipo al extraer datos de la tabla.
En la struct FItemData, añade las siguientes declaraciones:
Un
FNamecon el nombreID. Cada fila de una tabla de datos necesita unFNameasociado al que hacer referencia.Un
enum EItemTypecon el nombreItemType. Este es el enum de los tipos de elementos que declaraste antes.Una struct
FItemTextcon el nombreItemText. Esta es la struct de los data de texto que declaraste antes.
Añade la macro UPROPERTY() a cada declaración con los argumentos EditAnywhere y Category = “Item Data”.
// The ID name of this item for referencing in a table row.
UPROPERTY(EditAnywhere, Category = "Item Data")
FName ID;
// The type of the item.
UPROPERTY(EditAnywhere, Category = "Item Data")
EItemType ItemType;
// Text struct including the item name and description.
UPROPERTY(EditAnywhere, Category = "Item Data")
Añade una declaración más: un TObjectPtr a una UItemDefinition con el nombre ItemBase. Asígnale la misma macro UPROPERTY que al resto de propiedades de la struct.
// The Data Asset item definition associated with this item.
UPROPERTY(EditAnywhere, Category = "Item Data")
TObjectPtr<UItemDefinition> ItemBase;TObjectPtr es un tipo 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.
En el siguiente paso, crearás una clase UDataAsset con el nombre ItemDefinition. En tu tabla de datos, utilizarás el campo ItemBase para hacer referencia a las instancias de recursos de datos de ItemDefinition.
Ahora tu struct FItemData debería tener este aspecto:
// Defines a basic item that can be used in a data table.
USTRUCT()
struct FItemData : public FTableRowBase
{
GENERATED_BODY()
// The ID name of this item for referencing in a table row.
UPROPERTY(EditAnywhere, Category = "Item Data")
FName ID;
Guarda el código.
Crear una definición de elemento DataAsset
Hasta ahora, has definido los datos de elemento, es decir, el tipo de datos que aparecerán en tu tabla de datos. A continuación, implementarás la clase UItemDefinition para la que hiciste una declaración directa en ItemData.h.
Esta clase hereda de UDataAsset y, por lo tanto, es un UObject, lo que significa que puedes crear y utilizar instancias de la misma directamente en el editor sin tener que pasar por el código. Rellenarás tu tabla de datos con instancias de la clase UItemDefinition.
Para crear la clase de DataAsset ItemDefinition (ItemDefinition.h), sigue los siguientes pasos:
En Unreal Editor, ve a Herramientas > Nueva clase de C++.
En la ventana Elegir clase padre, haz clic en Todas las clases.
Busca y selecciona DataAsset como clase padre y haz clic en Siguiente.
Asigna a la clase el nombre
ItemDefinition(que coincida con la declaración directa que hiciste enItemData.h) y después haz clic en Crear clase.
VS debería abrir automáticamente los archivos .h y .cpp de tu nueva clase. En caso de que no lo haga, actualiza VS y abre los archivos manualmente. Solo estarás trabajando en el archivo .h, así que puedes cerrar el archivo .cpp si quieres.
En ItemDefinition.h, añade un include para ItemData.h porque vas a querer reutilizar las propiedades ItemType e ItemText que declaraste en ese archivo.
#include "CoreMinimal.h"
#include "Data/ItemData.h"
#include "ItemDefinition.generated.h"En ItemDefinition.h, en la macro UCLASS() que se encuentra encima de la definición de la clase, añade los especificadores BlueprintType y Blueprintable para que quede como clase base a la hora de crear blueprints.
// Defines a basic item with a static mesh that can be built from the editor.
UCLASS(BlueprintType, Blueprintable)
class FIRSTPERSON_API UItemDefinition : public UDataAsset
{
GENERATED_BODY()
public:
// Default constructor for the class.
UItemDefinition();
};En la sección public, copia las declaraciones FName ID, EItemType ItemType y FItemText ItemText de ItemData.h.
La definición del elemento obtiene los mismos datos que la struct FItemData, por lo que no es necesario hacer referencia a la tabla original cuando se desea obtener información sobre el elemento.
public:
// The ID name of this item for referencing in a table row.
UPROPERTY(EditAnywhere, Category = "Item Data")
FName ID;
// The type of this item.
UPROPERTY(EditAnywhere, Category = "Item Data")
EItemType ItemType;
Después de ItemText, declara un TSoftObjectPtr de tipo UStaticMesh con el nombre WorldMesh. Utilizarás esta malla estática para mostrar este elemento en el mundo.
TSoftObjectPtr es un tipo especial de puntero débil que actúa como una representación de cadena de la ruta de un recurso que solo se carga cuando es necesario. Normalmente, al declarar una propiedad de puntero UObject que hace referencia a un recurso, ese recurso se carga cuando se carga el objeto que contiene la propiedad. Esto podría hacer que todos los recursos se carguen al iniciar el juego, lo que provocaría una sobrecarga y ralentización tremendas. TSoftObjectPtr es útil para recursos grandes, como mallas, que solo deberían cargarse a la carta. Para obtener más información, consulta la sección Carga asincrónica de recursos.
Añade la misma macro UPROPERTY(EditAnywhere, Category = “Item Data”) a esta propiedad.
// Text struct including the item name and description.
UPROPERTY(EditAnywhere, Category = "Item Data")
FItemText ItemText;
// The Static Mesh used to display this item in the world.
UPROPERTY(EditAnywhere, Category = "Item Data")
TSoftObjectPtr<UStaticMesh> WorldMesh;
Las filas de la tabla de datos son similares a las filas de CSV, pero están pensadas para almacenar datos textuales y no para almacenar recursos completos. Para optimizar la gestión de datos, recomendamos agrupar información como la malla, el material y las animaciones de un elemento en un DataAsset, ya que este es el lugar central donde residen todos los datos sobre un elemento en particular. Así, la propiedad de malla estática del elemento está aquí en UItemDefinition en vez de en la struct FItemData.
Tu clase UItemDefinition completa debería tener ahora el siguiente aspecto:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Data/ItemData.h"
#include "ItemDefinition.generated.h"
/**
* Defines a basic item with a static mesh that can be built from the editor.
Guarda tu código y compílalo desde Visual Studio.
Crear una instancia de recurso de datos
Con tus datos de elemento (struct FItemData de ItemData.h) y la definición del elemento (clase UItemDefinition) definidos, tienes ya todo lo que necesitas para compilar las instancias del elemento y la tabla de datos.
En primer lugar, crea un recurso de datos para un nuevo objeto de recogida de proyectiles y, a continuación, crea una tabla de datos y rellénala con la información del recurso de datos.
Para crear un elemento del recurso de datos a partir de tu clase ItemDefinition, sigue estos pasos:
En el Explorador de contenido, ve a la carpeta Contenido > FirstPerson , haz clic en Añadir o haz clic con el botón derecho en un área en blanco de la Vista de recursos, selecciona Nueva carpeta y asígnale el nombre
Data.Aquí es donde almacenarás y organizarás los recursos de datos en tu juego.
En la carpeta
Data, haz clic en Añadir o haz clic con el botón derecho en un área en blanco de la vista de recursos y selecciona Varios > Recurso de datos.Asegúrate de elegir la opción Recurso de datos con el icono de gráfico circular.
En la ventana Elige la clase para la instancia de recurso de datos, selecciona Definición del elemento (la clase de C++ que definiste antes) y, a continuación, haz clic en Seleccionar. Asigna al nuevo Recurso de datos el nombre
DA_Pickup_001.Haz doble clic en
DA_Pickup_001para abrirlo. En el panel Detalles, verás todas las propiedades que has definido enItemDefinition.h.Introduce
pickup_001como ID.Establece el Tipo de elemento en Consumible.
Expande el texto del elemento e introduce un nombre y una descripción.
Establece la malla de mundo en
SM_FoamBullet.Haz clic en Guardar en la esquina superior izquierda de la ventana para guardar tu recurso de datos.
Definir una tabla de datos
Ahora que ya tienes al menos un recurso de Datos con el que rellenar una tabla de datos, ¡puedes crear la tabla!
Cada fila de la tabla de datos es una instancia rellenada de la struct ItemData.
Para crear una tabla de datos, sigue los siguientes pasos:
En el Explorador de contenido, dentro de la carpeta
Data, haz clic con el botón derecho en un área en blanco y selecciona Varios > Tabla de datos.En la ventana Seleccionar estructura de filas, selecciona ItemData (la struct
FItemDataque definiste enItemData.h) y, a continuación, haz clic en Aceptar.Asigna a la nueva tabla el nombre
DT_PickupDatay haz doble clic sobre ella para abrirla.
Inicialmente, la tabla de datos está vacía. Sin embargo, verás las propiedades que has definido en FItemData como cabeceras en la parte superior de la tabla, además de una columna adicional con el nombre Row Name (nombre de la fila).
Para añadir el recurso de datos de tu elemento de recogida como fila en la tabla, sigue los siguientes pasos:
Haz clic en Añadir para añadir una nueva fila a tu tabla. El editor DataTable enumera las entradas de las filas en el panel superior izquierdo de la pestaña Tabla de datos.
Haz doble clic en el nombre de la fila
NewRowy cámbialo porpickup_001(el ID de tu recurso de datos).Puedes utilizar cualquier
FNamecomo nombre de fila; no obstante, para que sea más fácil referenciar la fila en el código, haz que el nombre de la fila coincida con el ID del recurso de datos.En el panel Editor de filas, introduce los mismos valores que pusiste en el recurso de datos
DA_Pickup_001en los campos ID, Tipo de elemento y Texto de elemento.Establece la base de elementos en tu recurso de datos
DA_Pickup_001.Guarda la tabla de datos.
¡Y ya está! Vuelve a echar un vistazo al diagrama de elementos de juego basados en datos que has creado en este paso y observa cómo están todos conectados:
Has creado una tabla de datos que obtiene sus columnas de tu struct FItemData. Has rellenado la tabla con una fila que contiene los datos de la instancia de recurso de datos ItemDefinition de tipo consumible que has creado y has usado el puntero ItemBase para referenciar el recurso de datos. Por último, tu instancia de recurso de datos obtuvo sus propiedades de la clase padre de recurso de datos UItemDefinition que creaste.
Siguiente
En la siguiente sección, aprenderás a ampliar la definición del elemento para crear una clase personalizada y a instanciarla en tu nivel.
Código completo
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "ItemData.generated.h"
class UItemDefinition;
/**
* Defines the type of the item.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Data/ItemData.h"
#include "ItemDefinition.generated.h"
/**
* Defines a basic item with a static mesh that can be built from the editor.