Esta página cubre algunos conceptos avanzados de programación en Unreal Engine. Si continúas leyendo, se asume que estás ya familiarizado con C# de Unity y quieres aprender a utilizar C++ de Unreal Engine. Dicho esto, en la mayoría de los casos, también se puede utilizar Blueprint y obtener los mismos resultados, por lo que hemos añadido ejemplos tanto en C++ como en Blueprint siempre que ha sido posible.
Hablemos de algunos de los patrones de programación de jugabilidad más comunes y de cómo abordarlos en Unreal Engine. Los siguientes ejemplos cubren algunas funciones comunes de Unity y cómo implementar la misma funcionalidad en Unreal Engine.
Creación de instancias de GameObject/generación de actores
En Unity, se utilizaba la función Instantiate para crear nuevas instancias de objetos. Esta función toma cualquier tipo de UnityEngine.Object (GameObject, MonoBehaviour, etc.) y hace una copia de él.
public GameObject EnemyPrefab;
public Vector3 SpawnPosition;
public Quaternion SpawnRotation;
void Start()
{
GameObject NewGO = (GameObject)Instantiate(EnemyPrefab, SpawnPosition, SpawnRotation);
NewGO.name = "MyNewGameObject";
}
Unreal Engine tiene dos funciones diferentes para crear instancias de objetos:
NewObjectcrea tipos nuevos deUObject.SpawnActorgenera tipos deAActor.
UObject y NewObject
Crear subclases de UObject en Unreal Engine es muy parecido a hacerlo con ScriptableObject en Unity. Son útiles para las clases de jugabilidad que no necesitan aparecer en el mundo o tener componentes asociados, como los actores.
En Unity, si crearas tu propia subclase de ScriptableObject, la instanciarías de la siguiente manera:
MyScriptableObject NewSO = ScriptableObject.CreateInstance<MyScriptableObject>();
En Unreal Engine, si creas tu propio tipo derivado UObject, puedes instanciarlo así:
UMyObject* NewObj = NewObject<UMyObject>();
AActor y SpawnActor
Los actores se generan utilizando el método SpawnActor en un objeto World (UWorld en C++). Algunos UObjects te proporcionan un método GetWorld (como ejemplo, todos los actores lo hacen). Con este método se obtiene un objeto World.
En el siguiente ejemplo, como podrás observar, en lugar de pasar otro actor, pasamos la clase del actor que queremos generar. En nuestro ejemplo, la clase puede ser cualquier subclase de AMyEnemy.
¿Y si quieres hacer una copia de otro objeto, como lo que te permite hacer el uso de Instantiate en Unity?
A las funciones NewObject y SpawnActor también se les puede dar una plantilla de objeto con la que trabajar. Unreal Engine hará una copia de ese objeto en lugar de crear un nuevo objeto desde cero. Esto copiará todas sus UPROPERTY y componentes.
AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
UWorld* World = ExistingActor->GetWorld();
FActorSpawnParameters SpawnParams;
SpawnParams.Template = ExistingActor;
World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}
Quizá te preguntes qué significa «desde cero» en este contexto. Cada clase de objeto que se crea tiene una plantilla predeterminada que contiene valores por defecto para sus propiedades y componentes. Si no anulas estas propiedades y no proporcionas tu propia plantilla, Unreal Engine utilizará estos valores predeterminados para construir tu objeto. Para ilustrar esto, veamos primero un ejemplo de MonoBehaviour:
public class MyComponent : MonoBehaviour
{
public int MyIntProp = 42;
public SphereCollider MyCollisionComp = null;
void Start()
{
// Crear el componente `collision` si aún no lo tenemos
if (MyCollisionComp == null)
{
MyCollisionComp = gameObject.AddComponent<SphereCollider>();
MyCollisionComp.center = Vector3.zero;
MyCollisionComp.radius = 20.0f;
}
}
}
En el ejemplo anterior, tenemos una propiedad int que por defecto es 42 y un componente SphereCollider que por defecto tiene un radio de 20.
Podemos lograr lo mismo en Unreal Engine utilizando el constructor de objetos:
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
int32 MyIntProp;
UPROPERTY()
USphereComponent* MyCollisionComp;
AMyActor()
{
MyIntProp = 42;
MyCollisionComp = CreateDefaultSubobject<USphereComponent>(FName(TEXT("CollisionComponent"));
MyCollisionComp->RelativeLocation = FVector::ZeroVector;
MyCollisionComp->SphereRadius = 20.0f;
}
};
Establecemos los valores predeterminados de las propiedades de la clase en el constructor de AMyActor. Observa el uso de la función CreateDefaultSubobject; podemos utilizar esta función para crear componentes y asignarles propiedades predeterminadas. Todos los subobjetos creados con esta función actúan como plantilla predeterminada, por lo que podemos modificarlos en una subclase o blueprint.
Cómo pasar de un tipo a otro
En este caso, obtenemos un componente que sabemos que tenemos, luego lo convertimos a un tipo específico y después, hacer algo con él.
C# de Unity
Collider collider = gameObject.GetComponent<Collider>;
SphereCollider sphereCollider = collider as SphereCollider;
if (sphereCollider != null)
{
// ...
}
C++ de Unreal Engine
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
if (SphereCollider != nullptr)
{
// ...
}
Cómo destruir elementos GameObject/Actor
Unity
|
C++
|
Blueprint ![]() |
Cómo destruir elementos GameObject/Actor (con un retraso de 1 segundo)
Unity
|
C++
|
Blueprint
|
Cómo desactivar elementos GameObject/Actor
Unity
|
C++
|
Blueprint
|
Cómo acceder a elementos GameObject/Actor desde un componente
Unity
|
C++
|
Blueprint
|
Cómo acceder a un componente desde elementos GameObject/Actor
C# de Unity
MyComponent MyComp = gameObject.GetComponent<MyComponent>();
C++ de Unreal Engine
UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();
Blueprint
Cómo buscar elementos GameObject/Actor
C# de Unity
// Buscar GameObjects por nombre
GameObject MyGO = GameObject.Find("MyNamedGameObject");
// Buscar objetos por tipo
MyComponent[] Components = Object.FindObjectsOfType(typeof(MyComponent)) as MyComponent[];
foreach (MyComponent Component in Components)
{
// ...
}
// Buscar GameObjects por etiqueta
GameObject[] GameObjects = GameObject.FindGameObjectsWithTag("MyTag");
foreach (GameObject GO in GameObjects)
{
// ...
}
C++ de Unreal Engine
// Buscar actor por nombre (también válido para UObjects)
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
// Buscar actores por tipo (necesita un objeto UWorld)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
AMyActor* MyActor = *It;
// ...
}
Blueprint
C++ de Unreal Engine
// Buscar UObjects por tipo
for (TObjectIterator<UMyObject> It; It; ++it)
{
UMyObject* MyObject = *It;
// ...
}
// Buscar actores por etiqueta (también válido para ActorComponents con TObjectIterator)
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
AActor* Actor = *It;
if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
{
// ...
}
}
Blueprint
Cómo añadir etiquetas a elementos GameObject/Actor
C# de Unity
MyGameObject.tag = "MyTag";
C++ de Unreal Engine
// Los actores pueden tener varias etiquetas.
MyActor.Tags.AddUnique(TEXT("MyTag"));
Blueprint
Cómo añadir etiquetas a elementos MonoBehaviour/ActorComponent
C# de Unity
// Esto cambia la etiqueta del elemento GameObject al que está unido.
MyComponent.tag = "MyTag";
C++ de Unreal Engine
// Los componentes tienen su propio conjunto de etiquetas.
MyComponent.ComponentTags.AddUnique(TEXT("MyTag"));
Cómo comparar etiquetas de elementos GameObject/Actor y MonoBehaviour/ActorComponent
C# de Unity
if (MyGameObject.CompareTag("MyTag"))
{
// ...
}
// Comprueba la etiqueta del elemento GameObject al que está unido.
if (MyComponent.CompareTag("MyTag"))
{
// ...
}
C++ de Unreal Engine
// Comprueba si un elemento Actor tiene esta etiqueta.
if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
{
// ...
}
Blueprint
C++ de Unreal Engine
// Comprueba si un elemento ActorComponent tiene esta etiqueta.
if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{
// ...
}
Blueprint
Físicas: componente RigidBody y componente Primitive
En Unity, para dotar a cualquier elemento GameObject de características físicas, primero habría que darle un componente RigidBody.
En Unreal Engine, cualquier componente Primitive (UPrimitiveComponent en C++) puede ser un objeto físico. Algunos componentes Primitive comunes son:
- Componentes de forma (Capsule, Sphere y Box)
- Componentes de malla estática
- Componentes de malla esquelética
A diferencia de Unity, que separa las responsabilidades de colisión y visualizaciones en componentes independientes, Unreal Engine combina los conceptos «potencialmente físico» y «potencialmente visible» en un único componente Primitive. Cualquier componente que tenga alguna geometría en el mundo, que pueda ser renderizado o con el que se pueda interactuar físicamente, es una subclase de PrimitiveComponent.
Capas frente a canales
Los canales de colisión son el equivalente en Unreal Engine a las capas en Unity. Para más información, consulta Collision Filtering (Filtrado de colisiones).
RayCast frente a RayTrace
C# de Unity
GameObject FindGOCameraIsLookingAt()
{
Vector3 Start = Camera.main.transform.position;
Vector3 Direction = Camera.main.transform.forward;
float Distance = 100.0f;
int LayerBitMask = 1 << LayerMask.NameToLayer("Pawn");
RaycastHit Hit;
bool bHit = Physics.Raycast(Start, Direction, out Hit, Distance, LayerBitMask);
if (bHit)
{
return Hit.collider.gameObject;
}
return null;
}
C++ de Unreal Engine
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
// Puedes utilizarlo para personalizar varias propiedades del trazado.
FCollisionQueryParams Params;
// Ignorar el peón del jugador
Params.AddIgnoredActor(GetPawn());
// El resultado del impacto se rellena con el trazado de la línea.
FHitResult Hit;
// Proyección de rayos fuera de la cámara, solo colisiona con los peones (está en el canal de colisión ECC_Pawn).
FVector Start = PlayerCameraManager->GetCameraLocation();
FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
if (bHit)
{
// Hit.Actor contiene un puntero débil al actor en el que impactó el trazado.
return Cast<APawn>(Hit.Actor.Get());
}
return nullptr;
}
Blueprint
Haz clic en la imagen para verla a tamaño completo.
Volúmenes de activador
C# de Unity
public class MyComponent : MonoBehaviour
{
void Start()
{
collider.isTrigger = true;
}
void OnTriggerEnter(Collider Other)
{
// ...
}
void OnTriggerExit(Collider Other)
{
// ...
}
}
C++ de Unreal Engine
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// Mi componente activador
UPROPERTY()
UPrimitiveComponent* Trigger;
AMyActor()
{
Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));
// Ambos colisionadores deben tenerlo activado para que los eventos se activen.
Trigger.bGenerateOverlapEvents = true;
// Establecer el modo de colisión para el colisionador
// Este modo solo habilitará el colisionador para proyecciones de rayos, barridos y solapamientos.
Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
virtual void NotifyActorBeginOverlap(AActor* Other) override;
virtual void NotifyActorEndOverlap(AActor* Other) override;
};
Blueprint de Unreal Engine
Para obtener más información sobre la configuración de las respuestas ante colisiones, consulta la página Collision.
Cuerpos rígidos de cinemática
C# de Unity
public class MyComponent : MonoBehaviour
{
void Start()
{
rigidbody.isKinematic = true;
rigidbody.velocity = transform.forward * 10.0f;
}
}
En Unreal Engine, el componente de colisión y el componente de cuerpo rígido son uno solo. La clase base para esto es UPrimitiveComponent, que tiene muchas subclases (USphereComponent, UCapsuleComponent, etc.) para adaptarse a tus necesidades.
C++ de Unreal Engine
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
UPrimitiveComponent* PhysicalComp;
AMyActor()
{
PhysicalComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionAndPhysics"));
PhysicalComp->SetSimulatePhysics(false);
PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);
}
};
Eventos de entrada
C# de Unity
public class MyPlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetButtonDown("Fire"))
{
// ...
}
float Horiz = Input.GetAxis("Horizontal");
float Vert = Input.GetAxis("Vertical");
// ...
}
}
C++ de Unreal Engine:
UCLASS()
class AMyPlayerController : public APlayerController
{
GENERATED_BODY()
void SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent);
InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent);
InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent);
}
void HandleFireInputEvent();
void HandleHorizontalAxisInputEvent(float Value);
void HandleVerticalAxisInputEvent(float Value);
};
Blueprint
Este es el aspecto que podrían tener tus propiedades de entrada en la configuración del proyecto:
Si deseas obtener más información sobre cómo configurar la entrada para tu proyecto de Unreal Engine, consulta la página Input.
