tutoriales.com

Creación de un Sistema de Lanzamiento de Proyectiles Dirigido con C++ y Blueprint en Unreal Engine 5

Este tutorial te guiará paso a paso en la creación de un sistema de lanzamiento de proyectiles dirigido en Unreal Engine 5. Combinaremos la eficiencia de C++ para la lógica central y la flexibilidad de Blueprints para la implementación y los efectos visuales. Aprenderás a configurar la detección del objetivo, la trayectoria del proyectil y el manejo de impacto.

Intermedio20 min de lectura13 views
Reportar error

🎯 Introducción al Sistema de Proyectiles Dirigidos

En el desarrollo de videojuegos, un sistema de lanzamiento de proyectiles es fundamental para muchos géneros, desde shooters hasta RPGs de acción con hechizos. Un proyectil dirigido va un paso más allá, ya que no solo se lanza en una dirección, sino que activamente busca y persigue un objetivo hasta impactar o desaparecer. Esto añade una capa de complejidad y dinamismo a tus habilidades y ataques.

En este tutorial, construiremos un sistema robusto que permitirá a tus personajes lanzar proyectiles que persiguen a sus enemigos. Utilizaremos C++ para establecer la base lógica de nuestro proyectil y su componente de búsqueda, y Blueprints para refinar el comportamiento, añadir efectos visuales (VFX) y sonidos (SFX).

¿Por qué C++ y Blueprint? 💡

La combinación de C++ y Blueprint es una práctica común y muy potente en Unreal Engine:

  • C++: Ofrece mayor rendimiento, acceso de bajo nivel al motor y facilita la creación de bases de código reusables y complejas. Ideal para la lógica central y los componentes que requieren alta eficiencia.
  • Blueprint: Permite una rápida iteración, prototipado y visualización del flujo de lógica. Es excelente para integrar arte, sonidos, animaciones y para la lógica específica de cada proyectil o habilidad.

Al usar ambos, obtenemos lo mejor de ambos mundos: un núcleo eficiente y una implementación flexible y fácil de ajustar.


🛠️ Configuración Inicial del Proyecto

Antes de sumergirnos en el código, necesitamos configurar nuestro entorno de desarrollo.

1. Crear un Nuevo Proyecto

Para este tutorial, usaremos una plantilla de Third Person o First Person en Unreal Engine 5, ya que ya tienen un personaje y un controlador configurados. Asegúrate de que el proyecto sea C++.

  1. Abre el Epic Games Launcher.
  2. Inicia Unreal Engine 5.
  3. Selecciona Games en la categoría de proyectos.
  4. Elige la plantilla Third Person o First Person.
  5. Asegúrate de que el tipo de proyecto sea C++.
  6. Elige una ubicación y dale un nombre, por ejemplo, ProyectilesDirigidos.
  7. Haz clic en Create.
📌 Nota: Es posible adaptar este tutorial a un proyecto existente, pero para seguirlo de forma más limpia, se recomienda un proyecto nuevo.

2. Abrir el Entorno de Desarrollo

Una vez que el proyecto se ha creado, Unreal Engine abrirá Visual Studio (o tu IDE C++ preferido) automáticamente. Si no es así, puedes ir a File > Open Visual Studio desde el editor de Unreal.


🚀 Creando el Actor del Proyectil (C++)

Vamos a empezar creando la clase base de nuestro proyectil en C++. Este AActor será el encargado de la representación visual, el movimiento básico y la detección de colisiones.

1. Clase Base del Proyectil AProjectileBase

Crearemos una nueva clase C++ que herede de AActor.

  1. En el Editor de Unreal, ve a Tools > New C++ Class....
  2. Selecciona Actor como la clase padre.
  3. Nombra la clase ProjectileBase.
  4. Haz clic en Create Class.

Esto generará los archivos ProjectileBase.h y ProjectileBase.cpp.

ProjectileBase.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProjectileBase.generated.h"

class USphereComponent;
class UProjectileMovementComponent;
class UParticleSystemComponent;
class USoundCue;

UCLASS()
abstract class PROYECTILESDIRECCION_API AProjectileBase : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AProjectileBase();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	/** Componente de colisión para el proyectil */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	USphereComponent* CollisionSphere;

	/** Componente de movimiento del proyectil */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	UProjectileMovementComponent* ProjectileMovement;

	/** Componente para efectos visuales (partículas) */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	UParticleSystemComponent* ParticleFXComponent;

	/** Mesh del proyectil (opcional, para representación visual) */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	UStaticMeshComponent* ProjectileMesh;

	/** Daño que aplica el proyectil al impactar */
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Projectile")
	float DamageAmount;

	/** Partículas a spawnear en el impacto */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Projectile")
	UParticleSystem* ImpactFX;

	/** Sonido a reproducir en el impacto */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Projectile")
	USoundCue* ImpactSound;

	/** Función a llamar al impactar con algo */
	UFUNCTION()
	virtual void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

	/** Destruye el proyectil después de un breve delay tras el impacto para permitir que los FX se reproduzcan */
	UFUNCTION(BlueprintCallable, Category = "Projectile")
	void DestroyProjectileDelayed();

protected:
	/** Duración del delay antes de destruir el proyectil tras impacto */
	UPROPERTY(EditDefaultsOnly, Category = "Projectile")
	float DestroyDelayOnImpact;

	FTimerHandle DestroyTimerHandle;

};

ProjectileBase.cpp

#include "ProjectileBase.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"
#include "Components/StaticMeshComponent.h"

// Sets default values
AProjectileBase::AProjectileBase()
{
 	// 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;

	CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
	RootComponent = CollisionSphere;
	CollisionSphere->SetSphereRadius(20.0f);
	CollisionSphere->SetCollisionProfileName(TEXT("Projectile")); // Usaremos un perfil personalizado
	CollisionSphere->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit);

	ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMesh"));
	ProjectileMesh->SetupAttachment(RootComponent);
	ProjectileMesh->SetRelativeScale3D(FVector(0.5f, 0.5f, 0.5f));
	ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // El mesh no colisiona, solo la esfera

	ParticleFXComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleFXComponent"));
	ParticleFXComponent->SetupAttachment(RootComponent);
	ParticleFXComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));

	ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
	ProjectileMovement->SetUpdatedComponent(CollisionSphere);
	ProjectileMovement->InitialSpeed = 1500.0f;
	ProjectileMovement->MaxSpeed = 1500.0f;
	ProjectileMovement->bRotationFollowsVelocity = true;
	ProjectileMovement->bShouldBounce = false;
	ProjectileMovement->ProjectileGravityScale = 0.0f; // No queremos gravedad por defecto

	DamageAmount = 20.0f;
	DestroyDelayOnImpact = 0.5f; // 0.5 segundos antes de destruir el actor tras impacto
}

// Called when the game starts or when spawned
void AProjectileBase::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AProjectileBase::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AProjectileBase::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	// Ignorar impactos con el propio lanzador o si ya está marcado para destrucción
	if (GetInstigator() == OtherActor || OtherActor == this)
	{
		return;
	}

	// Desactivar el movimiento y la colisión para evitar múltiples impactos
	ProjectileMovement->StopMovementImmediately();
	CollisionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	// Reproducir efectos de impacto
	if (ImpactFX)
	{
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactFX, GetActorLocation(), GetActorRotation());
	}

	if (ImpactSound)
	{
		UGameplayStatics::PlaySoundAtLocation(GetWorld(), ImpactSound, GetActorLocation());
	}

	// Aplicar daño (si el otro actor implementa una interfaz de daño, por ejemplo)
	// UGameplayStatics::ApplyDamage(OtherActor, DamageAmount, GetInstigatorController(), this, UDamageType::StaticClass());

	// Detener el sistema de partículas del proyectil
	if (ParticleFXComponent)
	{
		ParticleFXComponent->DeactivateSystem();
	}

	// Destruir el proyectil después de un delay
	GetWorldTimerManager().SetTimer(DestroyTimerHandle, this, &AProjectileBase::DestroyProjectileDelayed, DestroyDelayOnImpact, false);
}

void AProjectileBase::DestroyProjectileDelayed()
{
	Destroy();
}

🔥 Importante: Después de añadir el código C++, compila el proyecto en Visual Studio o desde Unreal Editor (`Compile` botón).

2. Configuración del Perfil de Colisión

Necesitamos un perfil de colisión específico para nuestros proyectiles.

  1. En el Editor de Unreal, ve a Edit > Project Settings.
  2. En la sección Engine, selecciona Collision.
  3. Haz clic en New Trace Channel y nómbralo Projectile, con Default Response como Block.
  4. Haz clic en New Object Channel y nómbralo Projectile, con Default Response como Ignore.
  5. En la sección Preset, haz clic en New para crear un nuevo preset de colisión.
  6. Nombra el preset Projectile.
  7. Ajusta las respuestas como se indica en la tabla:
Canal de ObjetoRespuestaCanal de RastroRespuesta
------------
WorldStaticBlockVisibilityBlock
WorldDynamicBlockCameraIgnore
------------
PawnBlockProjectileBlock
PhysicsBodyBlock
------
VehicleIgnore
DestructibleBlock
------
ProjectileIgnore
💡 Consejo: Asegúrate de que el canal de objeto `Projectile` ignore a otros `Projectile` para evitar colisiones entre ellos. Y que bloquee a `Pawn` para que impacte con los personajes.

🧭 Creando el Componente de Búsqueda de Proyectiles (C++)

Aquí es donde reside la magia del "dirigido". Crearemos un UActorComponent que se encargará de encontrar y seguir al objetivo.

1. Clase UProjectileHomingComponent

  1. En el Editor de Unreal, ve a Tools > New C++ Class....
  2. Selecciona ActorComponent como la clase padre.
  3. Nombra la clase ProjectileHomingComponent.
  4. Haz clic en Create Class.

ProjectileHomingComponent.h

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ProjectileHomingComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class PROYECTILESDIRECCION_API UProjectileHomingComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UProjectileHomingComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	/** Actor al que el proyectil intentará seguir */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Homing")
	AActor* HomingTarget;

	/** Velocidad de rotación para apuntar al objetivo */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Homing", meta=(ClampMin = "0.0", UIMin = "0.0"))
	float HomingTurnSpeed;

	/** Distancia mínima para considerar que se ha llegado al objetivo y detener la búsqueda */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Homing", meta=(ClampMin = "0.0", UIMin = "0.0"))
	float HomingStopDistance;

	/** Establece el objetivo de búsqueda para el proyectil */
	UFUNCTION(BlueprintCallable, Category = "Homing")
	void SetHomingTarget(AActor* NewTarget);

private:
	/** El ProyectileMovementComponent del actor propietario */
	UPROPERTY()
	UProjectileMovementComponent* OwnerProjectileMovement;

	/** Referencia al actor propietario del componente */
	UPROPERTY()
	AActor* OwnerActor;

	/** Inicializa el componente de movimiento del proyectil del propietario */
	void InitializeOwnerProjectileMovement();

};

ProjectileHomingComponent.cpp

#include "ProjectileHomingComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"

// Sets default values for this component's properties
UProjectileHomingComponent::UProjectileHomingComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.
	PrimaryComponentTick.bCanEverTick = true;

	HomingTurnSpeed = 5.0f;
	HomingStopDistance = 100.0f;
}


// Called when the game starts
void UProjectileHomingComponent::BeginPlay()
{
	Super::BeginPlay();

	OwnerActor = GetOwner();
	InitializeOwnerProjectileMovement();
}

void UProjectileHomingComponent::InitializeOwnerProjectileMovement()
{
	if (OwnerActor)
	{
		OwnerProjectileMovement = OwnerActor->FindComponentByClass<UProjectileMovementComponent>();
		if (OwnerProjectileMovement)
		{
			// Habilitar el homing en el ProjectileMovementComponent
			OwnerProjectileMovement->bIsHomingProjectile = true;
			OwnerProjectileMovement->HomingAccelerationMagnitude = HomingTurnSpeed * 1000.0f; // Ajustar la aceleración
		}
	}
}

// Called every frame
void UProjectileHomingComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (HomingTarget && OwnerProjectileMovement)
	{
		// Actualizar el objetivo de homing si el ProjectileMovementComponent no lo hace automáticamente
		// O si queremos una lógica de homing más personalizada.
		// En este caso, el ProjectileMovementComponent ya gestiona el homing si bIsHomingProjectile es true
		// Y su HomingTargetComponent es establecido. Simplemente nos aseguramos de que el objetivo exista.

		// Si el objetivo está demasiado cerca, podríamos querer detener el homing
		float DistanceToTarget = FVector::Dist(OwnerActor->GetActorLocation(), HomingTarget->GetActorLocation());
		if (DistanceToTarget <= HomingStopDistance)
		{
			OwnerProjectileMovement->bIsHomingProjectile = false;
			// Opcionalmente, hacer algo más, como activar una explosión inmediata
		}
		else
		{
			// Asegurarse de que el homing esté activo si el objetivo no está cerca y sigue siendo válido
			OwnerProjectileMovement->bIsHomingProjectile = true;
			OwnerProjectileMovement->SetHomingTargetComponent(HomingTarget->GetRootComponent());
		}

		// Si el objetivo ya no es válido (destruido, etc.), detener el homing
		if (!HomingTarget->IsValidLowLevel() || HomingTarget->IsPendingKill())
		{
			HomingTarget = nullptr;
			OwnerProjectileMovement->bIsHomingProjectile = false;
		}
	}
	else if (OwnerProjectileMovement && OwnerProjectileMovement->bIsHomingProjectile)
	{
		// Si no hay objetivo, desactivar el homing
		OwnerProjectileMovement->bIsHomingProjectile = false;
	}
}

void UProjectileHomingComponent::SetHomingTarget(AActor* NewTarget)
{
	HomingTarget = NewTarget;
	if (OwnerProjectileMovement && HomingTarget)
	{
		OwnerProjectileMovement->bIsHomingProjectile = true;
		OwnerProjectileMovement->HomingAccelerationMagnitude = HomingTurnSpeed * 1000.0f; // Reajustar por si acaso
		OwnerProjectileMovement->SetHomingTargetComponent(HomingTarget->GetRootComponent());
	}
}

🔥 Importante: Compila el proyecto después de añadir el `ProjectileHomingComponent`.

🎨 Blueprint del Proyectil Dirigido

Ahora crearemos un Blueprint a partir de nuestras clases C++ para poder configurar la estética y el comportamiento específico sin necesidad de recompilar el código.

1. Crear el Blueprint del Proyectil

  1. En el Content Browser, haz clic derecho y selecciona Blueprint Class.
  2. En la ventana Pick Parent Class, busca ProjectileBase y selecciónala. Nombra el Blueprint BP_HomingProjectile.
  3. Abre BP_HomingProjectile.

2. Configurar el BP_HomingProjectile

En el editor de Blueprint, verás los componentes que definimos en C++:

  • CollisionSphere: Mantén el perfil Projectile que creamos.
  • ProjectileMovement: Puedes ajustar Initial Speed, Max Speed, etc. Asegúrate de que Projectile Gravity Scale esté en 0.0 para que el proyectil no caiga. bRotationFollowsVelocity es útil para que el mesh se oriente correctamente.
  • ParticleFXComponent: Selecciona una plantilla de sistema de partículas para el rastro del proyectil. Unreal tiene algunas por defecto en Engine/Content/FX/P_SmokeStack_01 o similar. Experimenta con ellas.
  • ProjectileMesh: Asigna un Static Mesh simple, como una esfera (Sphere) o un cono (Cone) para representar el proyectil. Ajusta su tamaño si es necesario.

Añadir el ProjectileHomingComponent

  1. En la pestaña Components del Blueprint, haz clic en + Add.
  2. Busca Projectile Homing Component y añádelo.
  3. En la sección Details de este componente, podrás configurar Homing Turn Speed y Homing Stop Distance.

Configurar las Propiedades del Proyectil (Defaults)

En la sección Defaults del Blueprint, podrás ajustar:

  • Damage Amount: El daño que infligirá el proyectil.
  • Impact FX: Selecciona un sistema de partículas para el efecto de impacto (ej. P_Explosion de los assets de Unreal).
  • Impact Sound: Selecciona un sonido para el impacto.
  • Destroy Delay On Impact: Mantén 0.5 segundos para que los efectos se reproduzcan antes de que el proyectil se destruya.
📌 Nota: Para los `Particle Systems` y `Sound Cues`, puedes usar los que vienen por defecto con Unreal Engine o importar los tuyos propios.

🔫 Implementando el Lanzamiento del Proyectil

Ahora que tenemos el proyectil base y su lógica de búsqueda, necesitamos una forma de lanzarlo. Lo haremos desde el personaje ThirdPersonCharacter (o FirstPersonCharacter).

1. Modificar ThirdPersonCharacter Blueprint

  1. Abre el Blueprint de tu personaje (ej. BP_ThirdPersonCharacter en la carpeta ThirdPerson/Blueprints).
  2. Ve al Event Graph.

2. Lógica de Lanzamiento

Utilizaremos un evento de entrada para lanzar el proyectil. Por ejemplo, el clic izquierdo del ratón o una tecla.

  1. Haz clic derecho y busca Input Action Fire (si usas la plantilla de FPS) o crea un nuevo Input Action en Project Settings > Input para LanzarProyectil y mapea una tecla (ej. Q).
  2. Arrastra el Projectile Homing Component desde el BP_HomingProjectile hacia el Event Graph.
  3. Desde el evento de input (Enhanced Input Action 'Fire' o Keyboard Q), arrastra un pin Pressed y conecta a un nodo Spawn Actor From Class.
    • Class: Selecciona BP_HomingProjectile.
    • Spawn Transform: Para la ubicación de spawn, puedes usar Get Actor Location + un Forward Vector para desplazarlo ligeramente delante del personaje, o un Scene Component adjunto a la mano del personaje para un punto de lanzamiento más preciso.
      • Una forma sencilla: Get Actor Location + Get Actor Forward Vector * SomeOffset (ej. 100 unidades).
  4. Encontrar un Objetivo: Para el objetivo de búsqueda, haremos un Line Trace By Channel o Sphere Trace desde la cámara o el personaje hacia adelante para detectar un Pawn (enemigo).
    • Realiza un Line Trace By Channel desde la cámara (o un punto de lanzamiento) hacia un rango máximo.
    • Asegúrate de que el Trace Channel sea Visibility o Camera.
    • Si el Hit Result es exitoso y el Hit Actor es un Pawn (que no sea el propio lanzador), ese será nuestro Homing Target.
  5. Desde el Return Value del nodo Spawn Actor From Class (que es una referencia a BP_HomingProjectile), arrastra un pin y busca Cast To ProjectileBase.
  6. Desde el As Projectile Base del nodo Cast, arrastra un pin y busca Get Projectile Homing Component.
  7. Desde el Projectile Homing Component resultante, arrastra un pin y busca Set Homing Target. Conecta el Hit Actor del Line Trace a este Homing Target.
Input Action Fire (Pressed) Line Trace By Channel Detectar Objetivo ¿Hit Actor Válido? (Branch) Spawn Actor From Class BP_HomingProjectile Cast To ProjectileBase Get Projectile Homing Component Set Homing Target Target = Hit Actor del Trace Activate Projectile Movement Impulso inicial Blueprint Firing Logic v1.0
Ejemplo de Blueprint para lanzar proyectil

// En el Event Graph del ThirdPersonCharacter

// Input Action: Lanzar Proyectil (ej. Tecla Q)
Event InputAction_LanzarProyectil (Pressed)
    -> Branch (DoOnce)
        // Line Trace para buscar objetivo
        -> Line Trace By Channel (Start: GetActorLocation, End: GetActorLocation + GetActorForwardVector * 2000)
            -> If Hit Result (Is Valid?)
                -> Cast To Pawn (Hit Actor)
                    -> If Valid Cast And Is Not Self
                        -> Spawn Actor From Class (BP_HomingProjectile)
                            -> Set Location (GetActorLocation + GetActorForwardVector * 100)
                            -> Set Rotation (GetActorRotation)
                            -> Set Instigator (Self)
                            -> From Spawned Actor Ref -> Get Projectile Homing Component -> Set Homing Target (Cast To Pawn Result)
                        -> DoOnce (Reset after delay, ej. 0.5s)
                    -> Else (No valid target)
                        -> Spawn Actor From Class (BP_HomingProjectile) // Lanza sin homing
                            -> Set Location (GetActorLocation + GetActorForwardVector * 100)
                            -> Set Rotation (GetActorRotation)
                            -> Set Instigator (Self)
                        -> DoOnce (Reset after delay, ej. 0.5s)
            -> Else (No hit)
                -> Spawn Actor From Class (BP_HomingProjectile) // Lanza sin homing
                    -> Set Location (GetActorLocation + GetActorForwardVector * 100)
                    -> Set Rotation (GetActorRotation)
                    -> Set Instigator (Self)
                -> DoOnce (Reset after delay, ej. 0.5s)
💡 Consejo: Para que el `Line Trace` ignore al propio personaje, puedes añadir el personaje a un `Actors to Ignore` en el nodo `Line Trace By Channel`.

3. Configurar Target Tags para el Line Trace (Opcional pero recomendado)

En lugar de simplemente Cast To Pawn, puedes usar Tags para identificar a los enemigos.

  1. En el Blueprint de un Enemy (si tienes uno, o simplemente el BP_ThirdPersonCharacter si quieres que el proyectil se dirija a otro personaje), en la sección Details, busca Tags y añade un Actor Tag, por ejemplo, Enemy.
  2. En la lógica de lanzamiento, después del Line Trace, en lugar de Cast To Pawn, usa Get Tags del Hit Actor y verifica si el array Contains el tag Enemy.

🧪 Pruebas y Ajustes

¡Es hora de probar nuestro sistema!

  1. Coloca varios personajes (ej. BP_ThirdPersonCharacter duplicados) en tu nivel para que sirvan de objetivos.
  2. Asegúrate de que tus personajes objetivo tengan el Actor Tag Enemy si optaste por esa aproximación.
  3. Juega el nivel y lanza los proyectiles.

Posibles Problemas y Soluciones:

  • Proyectil no se mueve: Revisa el ProjectileMovementComponent en BP_HomingProjectile. Asegúrate de que Initial Speed y Max Speed sean > 0. Asegúrate de que SetHomingTarget se esté llamando correctamente.
  • Proyectil no persigue al objetivo: Verifica el ProjectileHomingComponent en BP_HomingProjectile. Asegúrate de que Homing Turn Speed sea un valor razonable (ej. 5.0 - 15.0). También verifica que Set Homing Target esté recibiendo un AActor válido.
  • Proyectil pasa a través del objetivo: Revisa los perfiles de colisión. El CollisionSphere del proyectil debe Block o Overlap con el Object Channel del objetivo (ej. Pawn). El objetivo debe tener un componente de colisión configurado para responder al canal Projectile.
  • Proyectil se destruye inmediatamente: Revisa DestroyDelayOnImpact en BP_HomingProjectile. También, verifica la lógica OnHit para asegurarte de que no haya un Destroy() inmediato sin delay.
  • Falta de efectos visuales/sonoros: Asegúrate de que los Particle Systems y Sound Cues estén asignados en el BP_HomingProjectile y que existan en tu proyecto.
100% Completado

🚀 Mejoras Avanzadas (Ideas)

Una vez que tengas el sistema básico funcionando, puedes extenderlo de varias maneras:

  • Múltiples Proyectiles/Habilidades: Crea Blueprints hijos de BP_HomingProjectile para diferentes tipos de proyectiles con distintos efectos, velocidades o daños.
  • Pool de Proyectiles: En lugar de Spawn Actor From Class, implementa un Object Pool para reciclar proyectiles y mejorar el rendimiento, especialmente si lanzas muchos. Esto es crucial en juegos de alto rendimiento.
  • Homing Smoothing: Ajusta el HomingAccelerationMagnitude y el HomingTurnSpeed para un seguimiento más suave o más agresivo. Podrías incluso interpolar estos valores.
  • Anticipación del Objetivo: Para objetivos en movimiento rápido, el proyectil podría intentar predecir la posición futura del objetivo en lugar de apuntar a su posición actual. Esto requiere cálculos de trayectoria más avanzados.
  • Limitaciones de Homing: Añade un límite de distancia de búsqueda o un tiempo máximo para el homing antes de que el proyectil simplemente continúe en línea recta.
  • Diferentes Tipos de Búsqueda: Implementa diferentes estrategias de búsqueda, como búsqueda en arco, búsqueda con retardo, o búsqueda que se desactiva al pasar cerca del objetivo.
  • Aplicación de Daño y Estados: Implementa una interfaz de daño en tus personajes para que el OnHit del proyectil pueda llamar a una función TakeDamage en el objetivo, y aplicar estados (quemado, lento, etc.).
Preguntas Frecuentes (FAQ)

P: Mi proyectil no sigue al objetivo, solo vuela en línea recta. R: Verifica que hayas añadido el ProjectileHomingComponent a tu BP_HomingProjectile y que Set Homing Target esté recibiendo un actor válido. Además, revisa que bIsHomingProjectile esté activo en el ProjectileMovementComponent cuando se asigna un objetivo, y que HomingAccelerationMagnitude sea un valor lo suficientemente alto.

P: ¿Cómo puedo hacer que el proyectil tenga un rastro de partículas? R: En tu BP_HomingProjectile, selecciona el ParticleFXComponent y asigna un Template de sistema de partículas (como un P_SmokeStack_01 o similar).

P: Quiero que mi proyectil caiga con gravedad. R: En el ProjectileMovementComponent de tu BP_HomingProjectile, establece Projectile Gravity Scale a un valor mayor que 0.0 (ej. 1.0 para gravedad normal).


🏁 Conclusión

¡Felicidades! Has creado un sistema de lanzamiento de proyectiles dirigido en Unreal Engine 5 utilizando la potencia de C++ para la lógica central y la flexibilidad de Blueprints para la configuración. Este sistema es una base excelente que puedes expandir y adaptar para una amplia gama de habilidades, hechizos y ataques en tus juegos.

La combinación de estas dos herramientas te permite crear mecánicas de juego complejas de manera eficiente y escalable. ¡Ahora, a experimentar y llevar tus juegos al siguiente nivel!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!