Hi guys, I have this lingering problem that i cannot figure out, can you please help me?
I have programmed some code for spatial hashing, but when i use the functions in the code it thows an error LNK2019 when building
I’m currently programming the player’s death through the use of an health component
I also tried to include way too many libraries, but i don’t know which ones to delete right now, so if you figure it out please tell me
Here is all the code you may need
TFSpatialHash.h
#pragma once
#include <CoreMinimal.h>
#include <Math/UnrealMathUtility.h>
#include <Math/Vector2D.h>
#include <Containers/Map.h>
#include <Containers/Array.h>
#include <GameFramework/Actor.h>
namespace Utils
{
template<typename T, typename CellSizeType = int, typename Hash = TMap<FVector2D, T>>
class ATTRITION_API TFSpatialHash
{
public:
virtual ~TFSpatialHash() {};
//You have to call this function
void SetCellSize(CellSizeType CellSize);
void Insert(const FVector2D& Point, const T Object);
//Works only if Object is or is derived from AActor class
void Insert(const T Actor);
void Remove(const FVector2D& Point);
TArray<T> Search(const FVector2D& Point, const double Range) const;
T SearchNearest(const FVector2D& Point) const;
void Update(const FVector2D& OldPoint, const FVector2D& NewPoint, const T Object) const;
void Clear();
private:
CellSizeType CellSize_;
Hash HashTable_;
static FVector2D HashPoint(const FVector2D& Point);
virtual void BeginPlay() {};
};
}
TFSpatialHash.cpp
#include <TFSpatialHash.h>
#include <Math/Vector2D.h>
#include <Containers/Map.h>
#include <Misc/AssertionMacros.h>
#include <Math/UnrealMathUtility.h>
#include <GameFramework/Actor.h>
using namespace Utils;
template<typename T, typename CellSizeType, typename Hash>
void TFSpatialHash<T, CellSizeType, Hash>::SetCellSize(CellSizeType CellSize)
{
CellSize_ = CellSize;
}
template <typename T, typename CellSizeType, typename Hash>
void TFSpatialHash<T, CellSizeType, Hash>::Insert(const FVector2D& Point, const T Object)
{
const FVector2D Key = HashPoint(Point);
HashTable_.Add(Key, Object);
}
template <typename T, typename CellSizeType, typename Hash>
void TFSpatialHash<T, CellSizeType, Hash>::Insert(const T Actor)
{
const FVector2D Point(Actor->GetActorLocation().X, Actor->GetActorLocation().Y);
const FVector2D Key = HashPoint(Point);
HashTable_.Add(Key, Actor);
}
template <typename T, typename CellSizeType, typename Hash>
void TFSpatialHash<T, CellSizeType, Hash>::Remove(const FVector2D& Point)
{
FVector2D Key = HashPoint(Point);
HashTable_.Remove(Key);
}
template <typename T, typename CellSizeType, typename Hash>
TArray<T> TFSpatialHash<T, CellSizeType, Hash>::Search(const FVector2D& Point, const double Range) const
{
TArray<T> Results;
const FVector2D MinKey = HashPoint({Point.X - Range, Point.Y - Range});
const FVector2D MaxKey = HashPoint({Point.X + Range, Point.Y + Range});
for(double I = MinKey.X; I <= MaxKey.X; ++I)
{
for(double J = MinKey.Y; J <= MaxKey.Y; ++J)
{
FVector2D Key = FVector2D(I, J);
const T* Object = HashTable_.Find(Key);
if(Object && FVector2D::DistSquared(Point, Key) <= Range * Range)
{
Results.Add(*Object);
}
}
}
return Results;
}
template <typename T, typename CellSizeType, typename Hash>
T TFSpatialHash<T, CellSizeType, Hash>::SearchNearest(const FVector2D& Point) const
{
T NearestObject = nullptr;
double NearestDistance = DBL_MAX;
for(auto& [Key, Object] : HashTable_)
{
if(const double Distance = FVector2D::DistSquared(Point, Key); Distance < NearestDistance)
{
NearestObject = &Object;
NearestDistance = Distance;
}
}
return NearestObject;
}
template <typename T, typename CellSizeType, typename Hash>
void TFSpatialHash<T, CellSizeType, Hash>::Update(const FVector2D& OldPoint, const FVector2D& NewPoint,
const T Object) const
{
if(OldPoint == NewPoint)
return;
if(HashTable_.Find(OldPoint) == &Object)
{
Remove(OldPoint);
Insert(NewPoint);
}
}
template <typename T, typename CellSizeType, typename Hash>
void TFSpatialHash<T, CellSizeType, Hash>::Clear()
{
HashTable_.Reset();
}
template <typename T, typename CellSizeType, typename Hash>
FVector2D TFSpatialHash<T, CellSizeType, Hash>::HashPoint(const FVector2D& Point)
{
const double X = FMath::FloorToInt(Point.X / CellSize_);
const double Y = FMath::FloorToInt(Point.Y / CellSize_);
return FVector2D(X, Y);
}
HealthComponent.h
#pragma once
#include <CoreMinimal.h>
#include <TFSpatialHash.h>
#include <Components/ActorComponent.h>
#include <Math/Vector2D.h>
#include <Containers/Map.h>
#include <GameFramework/Actor.h>
#include "HealthComponent.generated.h"
//using namespace UUtility;
//TODO: Add health for NPCs(mainly enemies)
//Do we need this in BP? If not, we could delete the dynamic part for some faster code
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerDiedSignature, ACharacter*, Player);
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class ATTRITION_API UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
//Sets default values for this component's properties
UHealthComponent();
UPROPERTY(BlueprintReadOnly, Category="Health|General")
float Health;
//Create and assign signature functions
const FOnPlayerDiedSignature& GetOnPlayerDied() const {return OnPlayerDied;}
UPROPERTY(BlueprintAssignable, Category="Health|Respawn|Signatures")
FOnPlayerDiedSignature OnPlayerDied;
//Addition to OnTakeAnyDamage Signature
UFUNCTION()
void OnDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser);
//Handle the death events
UFUNCTION()
void PlayerDied(ACharacter* Player);
protected:
//Called when the game starts
virtual void BeginPlay() override;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Health|General")
float MaxHealth;
//The owner of this instance of the component
UPROPERTY(BlueprintReadOnly, Category="Health|General")
ACharacter* PlayerOwner;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Health|Respawn")
float RespawnTimer;
UPROPERTY()
const UWorld* WorldPtr;
UPROPERTY()
AGameModeBase* GameMode;
Utils::TFSpatialHash<AActor*> PlayerStartHash;
};
HealthComponent.cpp
#pragma region Includes
#include <HealthComponent.h>
#include <TFSpatialHash.h>
#include <Engine/World.h>
#include <GameFramework/Character.h>
#include <GameFramework/GameModeBase.h>
#include <GameFramework/PlayerStart.h>
#include <Kismet/GameplayStatics.h>
#include <Math/Vector2D.h>
#include <Containers/Map.h>
#include <Math/Vector.h>
#include <Math/UnrealMathUtility.h>
#include <GameFramework/Actor.h>
#include <Containers/Array.h>
#include <Misc/AssertionMacros.h>
#pragma endregion
// Sets default values for this component's properties
UHealthComponent::UHealthComponent()
{
//Does the object need to tick?
PrimaryComponentTick.bCanEverTick = false;
//Populating variables
MaxHealth = 100.0f;
Health = MaxHealth;
RespawnTimer = 5.0f;
}
// Called when the game starts
void UHealthComponent::BeginPlay()
{
Super::BeginPlay();
//Get owner of component
WorldPtr = GetWorld();
PlayerOwner = Cast<ACharacter>(GetOwner());
GameMode = Cast<AGameModeBase>(WorldPtr->GetAuthGameMode());
PlayerStartHash.SetCellSize(1000);
//add our damage function to give the hit signature more functionality
if(PlayerOwner)
{
PlayerOwner->OnTakeAnyDamage.AddDynamic(this, &UHealthComponent::OnDamage);
UE_LOG(LogTemp, Log, TEXT("Bound 1"))
}
//Bind functions to the delegates
if(!OnPlayerDied.IsBound())
{
OnPlayerDied.AddDynamic(this, &UHealthComponent::PlayerDied);
UE_LOG(LogTemp, Log, TEXT("Bound 2"))
}
TArray<AActor*> PlayerStartsArr;
UGameplayStatics::GetAllActorsOfClass(WorldPtr, APlayerStart::StaticClass(), PlayerStartsArr);
for(AActor* SpawnPoint : PlayerStartsArr)
{
PlayerStartHash.Insert(SpawnPoint);
}
}
/**
* @brief Added function to the damage event, all the parameters are needed by the signature
*/
void UHealthComponent::OnDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
{
UE_LOG(LogTemp, Log, TEXT("Damage func called"))
//Having negative damage doesn't make sense
if(Damage <= 0.0f)
return;
//Clamp so we don't get negative health
Health = FMath::Clamp(Health - Damage, 0.0f, MaxHealth);
if(Health <= 0.0f && PlayerOwner)
{
UE_LOG(LogTemp, Log, TEXT("Death function called"))
GetOnPlayerDied().Broadcast(PlayerOwner);
}
}
/**
* @brief Function to be called when player dies, connected to player died signature
* @param Player Player to kill
*/
void UHealthComponent::PlayerDied(ACharacter* Player)
{
UE_LOG(LogTemp, Log, TEXT("Dying"))
//Get Player's controller
AController* PlayerController = Player->GetController();
const FVector2D PlayerPos = FVector2D(Player->GetActorLocation().X, Player->GetActorLocation().Z);
GameMode->RestartPlayerAtPlayerStart(PlayerController, PlayerStartHash.SearchNearest(PlayerPos));
}
Thanks to everyone