So in my project (known as Quest: To Be Defined), I set-up a class in C++, for which there is then a Blueprint Class that inherits from this native class (allowing one to choose certain components of this entity).
This class is known as the ‘CombatAdmin’ (for a Character in my project, which follows the Player from where the game begins. As I had intended for the CombatAdmin to fly above the player, their class inherits from AActor as opposed to ACharacter).
This class is defined as such (CombatAdmin.h):
/**
This class is for another of the Player's 'Companions'.
*/
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Engine/Classes/Components/StaticMeshComponent.h"
#include "Runtime/Engine/Classes/Components/InterpToMovementComponent.h"
#include "QuestToBeDefinedCharacter.h"
#include "CombatAdmin.generated.h"
UCLASS()
class QUESTTOBEDEFINED_API ACombatAdmin : public AActor
{
GENERATED_BODY()
public:
/** For initialisation */
ACombatAdmin();
/**
To reference the static mesh assets to use.
(EditAnywhere and BlueprintReadWrite flags used
instead of EditDefaultsOnly and BlueprintReadOnly,
as these flags allow one to choose the assets
to use in the editor, but this change is not reflected
upon beginning play).
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMeshReferences)
UStaticMesh* CoreMeshReference;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMeshReferences)
UStaticMesh* WingMeshReference;
private:
// Functions:
/** Initialise components before starting play */
virtual void BeginPlay() override;
/** Handle updates to the CombatAdmin */
virtual void Tick(float DeltaSeconds)override;
/**
Move to the Player's current position.
// Not present for now:
@Param: float DeltaTime: From Tick(), to
use in Vector2DInterpTo().
*/
void UpdatePlayerPosition();
/** Adjust the control points for the new Player's position */
void UpdateControlPoints();
// Properties:
// Core components:
UStaticMeshComponent* BodyCore;
UStaticMeshComponent* LeftWing;
UStaticMeshComponent* RightWing;
/** For handling movement */
UInterpToMovementComponent* PrimaryMovementComponent;
/** The default position to interperlate to (above the Player Character) */
FVector DefaultTargetPosition;
/** To refer to the Player Character's location */
AQuestToBeDefinedCharacter* PlayerCharacterReference;
// Constant values:
const float DEFAULT_FLYING_HEIGHT = 1000.0f;
const float DEFAULT_MOVEMENT_TIME = 3.0f;
};
The implementation of this class is as follows (CombatAdmin.cpp):
// Fill out your copyright notice in the Description page of Project Settings.
#include "CombatAdmin.h"
#include "Engine.h"
// Perform pre-game initialisation:
ACombatAdmin::ACombatAdmin()
{
// 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;
// Initialise the components:
BodyCore = CreateDefaultSubobject<UStaticMeshComponent>(FName("BodyCore"));
LeftWing = CreateDefaultSubobject<UStaticMeshComponent>(FName("LeftWing"));
RightWing = CreateDefaultSubobject<UStaticMeshComponent>(FName("RightWing"));
PrimaryMovementComponent = CreateDefaultSubobject<UInterpToMovementComponent>(FName("PrimaryMovementComponent"));
}
// Perform initialisation that can only take place after play begins:
void ACombatAdmin::BeginPlay()
{
Super::BeginPlay();
BodyCore->SetStaticMesh(CoreMeshReference);
LeftWing->SetStaticMesh(WingMeshReference);
LeftWing->AttachToComponent(BodyCore, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
LeftWing->SetRelativeLocationAndRotation(FVector(5.0f, -50.0f, 45.0f),
FRotator(0.0f, 90.0f, 0.0f).Quaternion());
RightWing->SetStaticMesh(WingMeshReference);
RightWing->AttachToComponent(BodyCore, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
RightWing->SetRelativeLocationAndRotation(FVector(5.0f, 50.0f, 45.0f),
FRotator(0.0f, -90.0f, 0.0f).Quaternion());
// To move to the default target position:
/**
PrimaryMovementComponent->bAllowConcurrentTick = true;
PrimaryMovementComponent->bAutoRegisterUpdatedComponent = true;
PrimaryMovementComponent->bAutoRegister = true;
PrimaryMovementComponent->bAutoActivate = true;
*/
PrimaryMovementComponent->BehaviourType = EInterpToBehaviourType::OneShot;
//PrimaryMovementComponent->bTickInEditor = true;
UpdatePlayerPosition();
//PrimaryMovementComponent->ConstrainLocationToPlane()
/**
This causes the CombatAdmin to move to
the Player's starting location (1000 units above), but no further.
*/
PrimaryMovementComponent->AddControlPointPosition(GetActorLocation(), false);
PrimaryMovementComponent->AddControlPointPosition(DefaultTargetPosition, false);
PrimaryMovementComponent->Duration = DEFAULT_MOVEMENT_TIME;
PrimaryMovementComponent->Activate();
}
void ACombatAdmin::Tick(float DeltaSeconds)
{
if (PlayerCharacterReference)
{
if (DefaultTargetPosition != (PlayerCharacterReference->GetActorLocation()
+ FVector(0.0f, 0.0f, DEFAULT_FLYING_HEIGHT)))
{
UpdatePlayerPosition();
}
}
}
void ACombatAdmin::UpdatePlayerPosition()
{
if (!PlayerCharacterReference)
{
PlayerCharacterReference = Cast<AQuestToBeDefinedCharacter>(UGameplayStatics::GetPlayerCharacter(this, 0));
}
// For the new initial point:
UpdateControlPoints();
// Then get the new position:
DefaultTargetPosition = PlayerCharacterReference->GetActorLocation() + FVector(0.0f, 0.0f, DEFAULT_FLYING_HEIGHT);
PrimaryMovementComponent->AddControlPointPosition(DefaultTargetPosition, false);
}
// For the Player's new position:
void ACombatAdmin::UpdateControlPoints()
{
PrimaryMovementComponent->ControlPoints.Empty();
PrimaryMovementComponent->AddControlPointPosition(DefaultTargetPosition, false);
}
/**
This is what allows the CombatAdmin to move to
the Player's initial position from their starting
position (but after this, they then stop):
*/
//// Fill out your copyright notice in the Description page of Project Settings.
//
//#include "CombatAdmin.h"
//#include "Engine.h"
//
//// Perform pre-game initialisation:
//ACombatAdmin::ACombatAdmin()
//{
// // 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;
//
// // Initialise the components:
//
// BodyCore = CreateDefaultSubobject<UStaticMeshComponent>(FName("BodyCore"));
// LeftWing = CreateDefaultSubobject<UStaticMeshComponent>(FName("LeftWing"));
// RightWing = CreateDefaultSubobject<UStaticMeshComponent>(FName("RightWing"));
// PrimaryMovementComponent = CreateDefaultSubobject<UInterpToMovementComponent>(FName("PrimaryMovementComponent"));
//}
//
//// Perform initialisation that can only take place after play begins:
//void ACombatAdmin::BeginPlay()
//{
// Super::BeginPlay();
//
// BodyCore->SetStaticMesh(CoreMeshReference);
//
// LeftWing->SetStaticMesh(WingMeshReference);
// LeftWing->AttachToComponent(BodyCore, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
// LeftWing->SetRelativeLocationAndRotation(FVector(5.0f, -50.0f, 45.0f),
// FRotator(0.0f, 90.0f, 0.0f).Quaternion());
//
// RightWing->SetStaticMesh(WingMeshReference);
// RightWing->AttachToComponent(BodyCore, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
// RightWing->SetRelativeLocationAndRotation(FVector(5.0f, 50.0f, 45.0f),
// FRotator(0.0f, -90.0f, 0.0f).Quaternion());
//
// // To move to the default target position:
// /**
// PrimaryMovementComponent->bAllowConcurrentTick = true;
// PrimaryMovementComponent->bAutoRegisterUpdatedComponent = true;
// PrimaryMovementComponent->bAutoRegister = true;
// PrimaryMovementComponent->bAutoActivate = true;
// PrimaryMovementComponent->BehaviourType = EInterpToBehaviourType::PingPong;
// */
// //PrimaryMovementComponent->bTickInEditor = true;
// UpdatePlayerPosition();
//
// //PrimaryMovementComponent->ConstrainLocationToPlane()
//
// /**
// This causes the CombatAdmin to move to
// the Player's starting location (1000 units above), but no further.
// */
// PrimaryMovementComponent->AddControlPointPosition(GetActorLocation(), false);
// PrimaryMovementComponent->AddControlPointPosition(DefaultTargetPosition, false);
// PrimaryMovementComponent->Duration = DEFAULT_MOVEMENT_TIME;
// PrimaryMovementComponent->Activate();
//}
//
//void ACombatAdmin::Tick(float DeltaSeconds)
//{
// if (PlayerCharacterReference)
// {
// if (DefaultTargetPosition != (PlayerCharacterReference->GetActorLocation()
// + FVector(0.0f, 0.0f, DEFAULT_FLYING_HEIGHT)))
// {
// UpdatePlayerPosition();
// }
// }
//}
//
//void ACombatAdmin::UpdatePlayerPosition()
//{
// if (!PlayerCharacterReference)
// {
// PlayerCharacterReference = Cast<AQuestToBeDefinedCharacter>(UGameplayStatics::GetPlayerCharacter(this, 0));
// }
//
// DefaultTargetPosition = PlayerCharacterReference->GetActorLocation() + FVector(0.0f, 0.0f, DEFAULT_FLYING_HEIGHT);
// PrimaryMovementComponent->AddControlPointPosition(DefaultTargetPosition, false);
//}
Note that the implementation that is ‘commented-out’ (with ‘//’ preceding each line of code), causes the CombatAdmin to move to the Player’s initial location, but remain there, even if the Player moves from their spawn point.
For the implementation that is not ‘commented-out’, the CombatAdmin moves to and remains at another location, with the co-ordinates for such, shown in the screenshot below:
For reference, the Player’s spawn point is located at (X=19190.000000,Y=17580.000000,Z=302.000000), with a rotation of (Pitch=0.000000,Yaw=-129.999451,Roll=0.000000).
Even if one looks towards that location as the Player Character, they are not able to see the CombatAdmin at all.
To the point then, I would simply want the CombatAdmin to linearly-interpolate to the Player’s current position, whenever the Player moves. I was able to get this working with an old Blueprint for the CombatAdmin, but it seems as though the C++ implementation for such is not as straight forward.
Please then, could anyone provide assistance with this issue? I would greatly appreciate this, as I have found myself stuck on this issue for several days now.