feat: initial commit, only bugs remaining
This commit is contained in:
192
Source/UTAD_UI/SkillTree/CharacterStatsComponent.cpp
Normal file
192
Source/UTAD_UI/SkillTree/CharacterStatsComponent.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "CharacterStatsComponent.h"
|
||||
#include "SkillTreeComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
|
||||
UCharacterStatsComponent::UCharacterStatsComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
|
||||
// Default base stats
|
||||
BaseMaxHealth = 100.0f;
|
||||
BaseDamage = 10.0f;
|
||||
BaseSpeed = 600.0f;
|
||||
|
||||
// Initialize current values
|
||||
CurrentHealth = BaseMaxHealth;
|
||||
MaxHealth = BaseMaxHealth;
|
||||
CurrentDamage = BaseDamage;
|
||||
CurrentSpeed = BaseSpeed;
|
||||
|
||||
// Initialize bonuses to 0
|
||||
SkillHealthBonus = 0.0f;
|
||||
SkillDamageBonus = 0.0f;
|
||||
SkillSpeedBonus = 0.0f;
|
||||
SkillHealthBonusPercent = 0.0f;
|
||||
SkillDamageBonusPercent = 0.0f;
|
||||
SkillSpeedBonusPercent = 0.0f;
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Find the skill tree component on the same actor
|
||||
AActor* Owner = GetOwner();
|
||||
if (Owner)
|
||||
{
|
||||
OwnerSkillTree = Owner->FindComponentByClass<USkillTreeComponent>();
|
||||
|
||||
// Bind to skill tree events
|
||||
if (OwnerSkillTree)
|
||||
{
|
||||
OwnerSkillTree->OnSkillPurchased.AddDynamic(this, &UCharacterStatsComponent::OnSkillPurchased);
|
||||
}
|
||||
}
|
||||
|
||||
InitializeStats();
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::InitializeStats()
|
||||
{
|
||||
MaxHealth = BaseMaxHealth;
|
||||
CurrentHealth = MaxHealth;
|
||||
CurrentDamage = BaseDamage;
|
||||
CurrentSpeed = BaseSpeed;
|
||||
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::OnSkillPurchased(FName SkillID)
|
||||
{
|
||||
// Called when a skill is purchased, update all stats
|
||||
UpdateStatsFromSkillTree();
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::UpdateStatsFromSkillTree()
|
||||
{
|
||||
if (!OwnerSkillTree)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset skill bonuses
|
||||
SkillHealthBonus = 0.0f;
|
||||
SkillDamageBonus = 0.0f;
|
||||
SkillSpeedBonus = 0.0f;
|
||||
SkillHealthBonusPercent = 0.0f;
|
||||
SkillDamageBonusPercent = 0.0f;
|
||||
SkillSpeedBonusPercent = 0.0f;
|
||||
|
||||
// Calculate total bonuses from purchased skills
|
||||
TArray<FSkillNodeRuntime> AllSkills = OwnerSkillTree->GetAllSkillNodes();
|
||||
for (const FSkillNodeRuntime& Skill : AllSkills)
|
||||
{
|
||||
if (Skill.CurrentState == ESkillNodeState::Purchased)
|
||||
{
|
||||
// Add flat bonuses
|
||||
SkillHealthBonus += Skill.NodeData.HealthBonus;
|
||||
SkillDamageBonus += Skill.NodeData.DamageBonus;
|
||||
SkillSpeedBonus += Skill.NodeData.SpeedBonus;
|
||||
|
||||
// Add percentage bonuses
|
||||
SkillHealthBonusPercent += Skill.NodeData.HealthBonusPercent;
|
||||
SkillDamageBonusPercent += Skill.NodeData.DamageBonusPercent;
|
||||
SkillSpeedBonusPercent += Skill.NodeData.SpeedBonusPercent;
|
||||
}
|
||||
}
|
||||
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::RecalculateStats()
|
||||
{
|
||||
// Calculate final stats: Base + Flat Bonus + (Base * Percentage Bonus)
|
||||
float OldMaxHealth = MaxHealth;
|
||||
|
||||
MaxHealth = BaseMaxHealth + SkillHealthBonus + (BaseMaxHealth * SkillHealthBonusPercent / 100.0f);
|
||||
CurrentDamage = BaseDamage + SkillDamageBonus + (BaseDamage * SkillDamageBonusPercent / 100.0f);
|
||||
CurrentSpeed = BaseSpeed + SkillSpeedBonus + (BaseSpeed * SkillSpeedBonusPercent / 100.0f);
|
||||
|
||||
// Scale current health proportionally if max health changed
|
||||
if (OldMaxHealth > 0 && MaxHealth != OldMaxHealth)
|
||||
{
|
||||
float HealthPercent = CurrentHealth / OldMaxHealth;
|
||||
CurrentHealth = MaxHealth * HealthPercent;
|
||||
}
|
||||
|
||||
// Apply speed to character movement
|
||||
ApplySpeedToMovement();
|
||||
|
||||
// Broadcast events
|
||||
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
||||
OnDamageChanged.Broadcast(CurrentDamage);
|
||||
OnSpeedChanged.Broadcast(CurrentSpeed);
|
||||
OnStatsUpdated.Broadcast();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("Stats Updated - Health: %.0f/%.0f, Damage: %.0f, Speed: %.0f"),
|
||||
CurrentHealth, MaxHealth, CurrentDamage, CurrentSpeed);
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::TakeDamage(float DamageAmount)
|
||||
{
|
||||
if (DamageAmount <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.0f, MaxHealth);
|
||||
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
||||
|
||||
if (CurrentHealth <= 0.0f)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Character has died!"));
|
||||
}
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::Heal(float HealAmount)
|
||||
{
|
||||
if (HealAmount <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentHealth = FMath::Clamp(CurrentHealth + HealAmount, 0.0f, MaxHealth);
|
||||
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::SetHealth(float NewHealth)
|
||||
{
|
||||
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, MaxHealth);
|
||||
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
||||
}
|
||||
|
||||
FText UCharacterStatsComponent::GetHealthText() const
|
||||
{
|
||||
return FText::Format(FText::FromString(TEXT("{0:.0f} / {1:.0f}")),
|
||||
FText::AsNumber(CurrentHealth),
|
||||
FText::AsNumber(MaxHealth));
|
||||
}
|
||||
|
||||
FText UCharacterStatsComponent::GetDamageText() const
|
||||
{
|
||||
return FText::Format(FText::FromString(TEXT("Damage: {0:.0f}")),
|
||||
FText::AsNumber(CurrentDamage));
|
||||
}
|
||||
|
||||
FText UCharacterStatsComponent::GetSpeedText() const
|
||||
{
|
||||
return FText::Format(FText::FromString(TEXT("Speed: {0:.0f}")),
|
||||
FText::AsNumber(CurrentSpeed));
|
||||
}
|
||||
|
||||
void UCharacterStatsComponent::ApplySpeedToMovement()
|
||||
{
|
||||
// Apply speed to character movement component
|
||||
ACharacter* CharacterOwner = Cast<ACharacter>(GetOwner());
|
||||
if (CharacterOwner && CharacterOwner->GetCharacterMovement())
|
||||
{
|
||||
CharacterOwner->GetCharacterMovement()->MaxWalkSpeed = CurrentSpeed;
|
||||
UE_LOG(LogTemp, Log, TEXT("Applied movement speed: %.0f"), CurrentSpeed);
|
||||
}
|
||||
}
|
||||
140
Source/UTAD_UI/SkillTree/CharacterStatsComponent.h
Normal file
140
Source/UTAD_UI/SkillTree/CharacterStatsComponent.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "CharacterStatsComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, CurrentHealth, float, MaxHealth);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDamageChanged, float, CurrentDamage);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSpeedChanged, float, CurrentSpeed);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnStatsUpdated);
|
||||
|
||||
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
|
||||
class UTAD_UI_API UCharacterStatsComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UCharacterStatsComponent();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
// Base stats (without any bonuses)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Base Stats")
|
||||
float BaseMaxHealth;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Base Stats")
|
||||
float BaseDamage;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Base Stats")
|
||||
float BaseSpeed;
|
||||
|
||||
// Current values
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Current Stats")
|
||||
float CurrentHealth;
|
||||
|
||||
// Calculated stats (base + bonuses)
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Current Stats")
|
||||
float MaxHealth;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Current Stats")
|
||||
float CurrentDamage;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Current Stats")
|
||||
float CurrentSpeed;
|
||||
|
||||
// Skill tree bonuses (flat bonuses)
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses")
|
||||
float SkillHealthBonus;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses")
|
||||
float SkillDamageBonus;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses")
|
||||
float SkillSpeedBonus;
|
||||
|
||||
// Skill tree bonuses (percentage bonuses)
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses")
|
||||
float SkillHealthBonusPercent;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses")
|
||||
float SkillDamageBonusPercent;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses")
|
||||
float SkillSpeedBonusPercent;
|
||||
|
||||
public:
|
||||
// Initialize stats
|
||||
UFUNCTION(BlueprintCallable, Category = "Character Stats")
|
||||
void InitializeStats();
|
||||
|
||||
// Update stats from skill tree
|
||||
UFUNCTION(BlueprintCallable, Category = "Character Stats")
|
||||
void UpdateStatsFromSkillTree();
|
||||
|
||||
// Called when a skill is purchased
|
||||
UFUNCTION()
|
||||
void OnSkillPurchased(FName SkillID);
|
||||
|
||||
// Calculate final stats
|
||||
UFUNCTION(BlueprintCallable, Category = "Character Stats")
|
||||
void RecalculateStats();
|
||||
|
||||
// Getters for current stats
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats")
|
||||
float GetCurrentHealth() const { return CurrentHealth; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats")
|
||||
float GetMaxHealth() const { return MaxHealth; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats")
|
||||
float GetHealthPercentage() const { return MaxHealth > 0 ? CurrentHealth / MaxHealth : 0.0f; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats")
|
||||
float GetCurrentDamage() const { return CurrentDamage; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats")
|
||||
float GetCurrentSpeed() const { return CurrentSpeed; }
|
||||
|
||||
// Health management
|
||||
UFUNCTION(BlueprintCallable, Category = "Character Stats")
|
||||
void TakeDamage(float DamageAmount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Character Stats")
|
||||
void Heal(float HealAmount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Character Stats")
|
||||
void SetHealth(float NewHealth);
|
||||
|
||||
// Get formatted text for UI
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats UI")
|
||||
FText GetHealthText() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats UI")
|
||||
FText GetDamageText() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats UI")
|
||||
FText GetSpeedText() const;
|
||||
|
||||
// Events
|
||||
UPROPERTY(BlueprintAssignable, Category = "Stats Events")
|
||||
FOnHealthChanged OnHealthChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Stats Events")
|
||||
FOnDamageChanged OnDamageChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Stats Events")
|
||||
FOnSpeedChanged OnSpeedChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Stats Events")
|
||||
FOnStatsUpdated OnStatsUpdated;
|
||||
|
||||
private:
|
||||
// Apply speed to character movement
|
||||
void ApplySpeedToMovement();
|
||||
|
||||
// Reference to owner's skill tree component
|
||||
UPROPERTY()
|
||||
class USkillTreeComponent* OwnerSkillTree;
|
||||
};
|
||||
249
Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.cpp
Normal file
249
Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "SkillTreeBlueprintLibrary.h"
|
||||
#include "SkillTreeComponent.h"
|
||||
|
||||
FLinearColor USkillTreeBlueprintLibrary::GetSkillStateColor(ESkillNodeState State)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case ESkillNodeState::Locked:
|
||||
return FLinearColor(0.3f, 0.3f, 0.3f, 1.0f); // Gray
|
||||
case ESkillNodeState::Available:
|
||||
return FLinearColor(1.0f, 1.0f, 1.0f, 1.0f); // White
|
||||
case ESkillNodeState::Selected:
|
||||
return FLinearColor(1.0f, 0.8f, 0.0f, 1.0f); // Gold
|
||||
case ESkillNodeState::Purchased:
|
||||
return FLinearColor(0.0f, 1.0f, 0.0f, 1.0f); // Green
|
||||
default:
|
||||
return FLinearColor::White;
|
||||
}
|
||||
}
|
||||
|
||||
FText USkillTreeBlueprintLibrary::GetSkillStateText(ESkillNodeState State)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case ESkillNodeState::Locked:
|
||||
return FText::FromString(TEXT("Locked"));
|
||||
case ESkillNodeState::Available:
|
||||
return FText::FromString(TEXT("Available"));
|
||||
case ESkillNodeState::Selected:
|
||||
return FText::FromString(TEXT("Selected"));
|
||||
case ESkillNodeState::Purchased:
|
||||
return FText::FromString(TEXT("Purchased"));
|
||||
default:
|
||||
return FText::FromString(TEXT("Unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
FText USkillTreeBlueprintLibrary::GetSkillAvailabilityText(USkillTreeComponent* SkillTreeComponent, FName SkillID)
|
||||
{
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return FText::FromString(TEXT("No Skill Tree Component"));
|
||||
}
|
||||
|
||||
FSkillNodeRuntime NodeData;
|
||||
if (!SkillTreeComponent->GetSkillNodeData(SkillID, NodeData))
|
||||
{
|
||||
return FText::FromString(TEXT("Invalid Skill"));
|
||||
}
|
||||
|
||||
ESkillNodeState State = SkillTreeComponent->GetSkillState(SkillID);
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case ESkillNodeState::Purchased:
|
||||
return FText::FromString(TEXT("Already Purchased"));
|
||||
case ESkillNodeState::Selected:
|
||||
return FText::FromString(TEXT("Selected for Purchase"));
|
||||
case ESkillNodeState::Available:
|
||||
return FText::Format(FText::FromString(TEXT("Available - Cost: {0} Points")), NodeData.NodeData.Cost);
|
||||
case ESkillNodeState::Locked:
|
||||
{
|
||||
// Check why it's locked
|
||||
if (NodeData.NodeData.Prerequisites.Num() > 0)
|
||||
{
|
||||
FString MissingPrereqs;
|
||||
for (const FName& PrereqID : NodeData.NodeData.Prerequisites)
|
||||
{
|
||||
if (!SkillTreeComponent->IsSkillPurchased(PrereqID))
|
||||
{
|
||||
FSkillNodeRuntime PrereqData;
|
||||
if (SkillTreeComponent->GetSkillNodeData(PrereqID, PrereqData))
|
||||
{
|
||||
if (!MissingPrereqs.IsEmpty())
|
||||
{
|
||||
MissingPrereqs += TEXT(", ");
|
||||
}
|
||||
MissingPrereqs += PrereqData.NodeData.DisplayName.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return FText::Format(FText::FromString(TEXT("Requires: {0}")), FText::FromString(MissingPrereqs));
|
||||
}
|
||||
return FText::FromString(TEXT("Locked"));
|
||||
}
|
||||
default:
|
||||
return FText::FromString(TEXT("Unknown State"));
|
||||
}
|
||||
}
|
||||
|
||||
FText USkillTreeBlueprintLibrary::FormatSkillDescription(const FSkillNodeData& NodeData)
|
||||
{
|
||||
FString Description = NodeData.Description.ToString();
|
||||
|
||||
// Add effect descriptions
|
||||
FString Effects;
|
||||
|
||||
if (NodeData.HealthBonus > 0)
|
||||
{
|
||||
Effects += FString::Printf(TEXT("\n+%.0f Health"), NodeData.HealthBonus);
|
||||
}
|
||||
if (NodeData.HealthBonusPercent > 0)
|
||||
{
|
||||
Effects += FString::Printf(TEXT("\n+%.0f%% Health"), NodeData.HealthBonusPercent);
|
||||
}
|
||||
if (NodeData.DamageBonus > 0)
|
||||
{
|
||||
Effects += FString::Printf(TEXT("\n+%.0f Damage"), NodeData.DamageBonus);
|
||||
}
|
||||
if (NodeData.DamageBonusPercent > 0)
|
||||
{
|
||||
Effects += FString::Printf(TEXT("\n+%.0f%% Damage"), NodeData.DamageBonusPercent);
|
||||
}
|
||||
if (NodeData.SpeedBonus > 0)
|
||||
{
|
||||
Effects += FString::Printf(TEXT("\n+%.0f Speed"), NodeData.SpeedBonus);
|
||||
}
|
||||
if (NodeData.SpeedBonusPercent > 0)
|
||||
{
|
||||
Effects += FString::Printf(TEXT("\n+%.0f%% Speed"), NodeData.SpeedBonusPercent);
|
||||
}
|
||||
|
||||
if (!Effects.IsEmpty())
|
||||
{
|
||||
Description += TEXT("\n\nEffects:") + Effects;
|
||||
}
|
||||
|
||||
return FText::FromString(Description);
|
||||
}
|
||||
|
||||
|
||||
float USkillTreeBlueprintLibrary::GetPreviewTotalDamageBonus(USkillTreeComponent* SkillTreeComponent)
|
||||
{
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float Total = SkillTreeComponent->GetTotalDamageBonus();
|
||||
|
||||
// Add selected (but not purchased) skills
|
||||
TArray<FName> SelectedSkills = SkillTreeComponent->GetSelectedSkills();
|
||||
for (const FName& SkillID : SelectedSkills)
|
||||
{
|
||||
FSkillNodeRuntime NodeData;
|
||||
if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData))
|
||||
{
|
||||
Total += NodeData.NodeData.DamageBonus;
|
||||
}
|
||||
}
|
||||
|
||||
return Total;
|
||||
}
|
||||
|
||||
|
||||
float USkillTreeBlueprintLibrary::GetPreviewTotalSpeedBonus(USkillTreeComponent* SkillTreeComponent)
|
||||
{
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float Total = SkillTreeComponent->GetTotalSpeedBonus();
|
||||
|
||||
// Add selected (but not purchased) skills
|
||||
TArray<FName> SelectedSkills = SkillTreeComponent->GetSelectedSkills();
|
||||
for (const FName& SkillID : SelectedSkills)
|
||||
{
|
||||
FSkillNodeRuntime NodeData;
|
||||
if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData))
|
||||
{
|
||||
Total += NodeData.NodeData.SpeedBonus;
|
||||
}
|
||||
}
|
||||
|
||||
return Total;
|
||||
}
|
||||
|
||||
float USkillTreeBlueprintLibrary::GetPreviewTotalHealthBonus(USkillTreeComponent* SkillTreeComponent)
|
||||
{
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float Total = SkillTreeComponent->GetTotalHealthBonus();
|
||||
|
||||
// Add selected (but not purchased) skills
|
||||
TArray<FName> SelectedSkills = SkillTreeComponent->GetSelectedSkills();
|
||||
for (const FName& SkillID : SelectedSkills)
|
||||
{
|
||||
FSkillNodeRuntime NodeData;
|
||||
if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData))
|
||||
{
|
||||
Total += NodeData.NodeData.HealthBonus;
|
||||
}
|
||||
}
|
||||
|
||||
return Total;
|
||||
}
|
||||
|
||||
bool USkillTreeBlueprintLibrary::HasSelectedSkills(USkillTreeComponent* SkillTreeComponent)
|
||||
{
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return SkillTreeComponent->GetSelectedSkills().Num() > 0;
|
||||
}
|
||||
|
||||
bool USkillTreeBlueprintLibrary::CanPlayerRespec(USkillTreeComponent* SkillTreeComponent)
|
||||
{
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if player has any purchased skills to reset
|
||||
TArray<FSkillNodeRuntime> AllNodes = SkillTreeComponent->GetAllSkillNodes();
|
||||
for (const FSkillNodeRuntime& Node : AllNodes)
|
||||
{
|
||||
if (Node.CurrentState == ESkillNodeState::Purchased)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FName> USkillTreeBlueprintLibrary::GetSkillConnections(USkillTreeComponent* SkillTreeComponent, FName SkillID)
|
||||
{
|
||||
TArray<FName> Connections;
|
||||
|
||||
if (!SkillTreeComponent)
|
||||
{
|
||||
return Connections;
|
||||
}
|
||||
|
||||
FSkillNodeRuntime NodeData;
|
||||
if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData))
|
||||
{
|
||||
// Return the prerequisites as connections
|
||||
Connections = NodeData.NodeData.Prerequisites;
|
||||
}
|
||||
|
||||
return Connections;
|
||||
}
|
||||
56
Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.h
Normal file
56
Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "SkillTreeTypes.h"
|
||||
#include "SkillTreeBlueprintLibrary.generated.h"
|
||||
|
||||
// Forward declarations
|
||||
class USkillTreeComponent;
|
||||
|
||||
UCLASS()
|
||||
class UTAD_UI_API USkillTreeBlueprintLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// UI Helper Functions
|
||||
|
||||
// Get state color for UI display
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill State Color"))
|
||||
static FLinearColor GetSkillStateColor(ESkillNodeState State);
|
||||
|
||||
// Get state text for UI display
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill State Text"))
|
||||
static FText GetSkillStateText(ESkillNodeState State);
|
||||
|
||||
// Check if a skill can be selected with detailed reason
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill Availability Text"))
|
||||
static FText GetSkillAvailabilityText(USkillTreeComponent* SkillTreeComponent, FName SkillID);
|
||||
|
||||
// Get formatted description with current values
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Format Skill Description"))
|
||||
static FText FormatSkillDescription(const FSkillNodeData& NodeData);
|
||||
|
||||
// Calculate total stats preview (including selected but not purchased skills)
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Preview Total Health Bonus"))
|
||||
static float GetPreviewTotalHealthBonus(USkillTreeComponent* SkillTreeComponent);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Preview Total Damage Bonus"))
|
||||
static float GetPreviewTotalDamageBonus(USkillTreeComponent* SkillTreeComponent);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Preview Total Speed Bonus"))
|
||||
static float GetPreviewTotalSpeedBonus(USkillTreeComponent* SkillTreeComponent);
|
||||
|
||||
// Check if any skills are selected
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Has Selected Skills"))
|
||||
static bool HasSelectedSkills(USkillTreeComponent* SkillTreeComponent);
|
||||
|
||||
// Check if player can respec
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Can Player Respec"))
|
||||
static bool CanPlayerRespec(USkillTreeComponent* SkillTreeComponent);
|
||||
|
||||
// Get the connections between skills for line rendering
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill Connections"))
|
||||
static TArray<FName> GetSkillConnections(USkillTreeComponent* SkillTreeComponent, FName SkillID);
|
||||
};
|
||||
557
Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp
Normal file
557
Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp
Normal file
@@ -0,0 +1,557 @@
|
||||
#include "SkillTreeComponent.h"
|
||||
#include "Engine/DataTable.h"
|
||||
|
||||
USkillTreeComponent::USkillTreeComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
|
||||
AvailableSkillPoints = 5; // Start with 5 skill points for testing
|
||||
TotalSkillPointsEarned = 5;
|
||||
CurrentSelectionCost = 0;
|
||||
}
|
||||
|
||||
void USkillTreeComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
InitializeSkillTree();
|
||||
}
|
||||
|
||||
void USkillTreeComponent::InitializeSkillTree()
|
||||
{
|
||||
AllSkills.Empty();
|
||||
|
||||
if (!SkillDataTable)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("SkillTreeComponent: No skill data table assigned!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all skills from the data table using RowNames as IDs
|
||||
TArray<FName> RowNames = SkillDataTable->GetRowNames();
|
||||
|
||||
for (const FName& RowName : RowNames)
|
||||
{
|
||||
FSkillNodeData* Row = SkillDataTable->FindRow<FSkillNodeData>(RowName, TEXT("SkillTreeComponent"));
|
||||
if (Row)
|
||||
{
|
||||
FSkillNodeRuntime RuntimeNode;
|
||||
RuntimeNode.NodeData = *Row;
|
||||
RuntimeNode.CurrentState = ESkillNodeState::Locked;
|
||||
RuntimeNode.bIsSelected = false;
|
||||
|
||||
// Use RowName as the skill ID
|
||||
AllSkills.Add(RowName, RuntimeNode);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSkillStates();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("SkillTreeComponent: Initialized with %d skills"), AllSkills.Num());
|
||||
}
|
||||
|
||||
void USkillTreeComponent::AddSkillPoints(int32 Amount)
|
||||
{
|
||||
if (Amount > 0)
|
||||
{
|
||||
AvailableSkillPoints += Amount;
|
||||
TotalSkillPointsEarned += Amount;
|
||||
OnSkillPointsChanged.Broadcast(AvailableSkillPoints);
|
||||
UpdateSkillStates();
|
||||
}
|
||||
}
|
||||
|
||||
void USkillTreeComponent::RemoveSkillPoints(int32 Amount)
|
||||
{
|
||||
if (Amount > 0)
|
||||
{
|
||||
AvailableSkillPoints = FMath::Max(0, AvailableSkillPoints - Amount);
|
||||
OnSkillPointsChanged.Broadcast(AvailableSkillPoints);
|
||||
UpdateSkillStates();
|
||||
}
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::SelectSkill(FName SkillID)
|
||||
{
|
||||
FText ErrorMessage;
|
||||
|
||||
// Check if skill exists
|
||||
if (!AllSkills.Contains(SkillID))
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT("Skill does not exist"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
|
||||
// Check if already purchased
|
||||
if (SkillNode->CurrentState == ESkillNodeState::Purchased)
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT("Skill already purchased"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if already selected
|
||||
if (SkillNode->bIsSelected)
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT("Skill already selected"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
// For locked skills, we allow selection but will validate on purchase
|
||||
// This allows players to "plan" their build
|
||||
if (SkillNode->CurrentState == ESkillNodeState::Locked)
|
||||
{
|
||||
// Check if prerequisites would be met with current selection
|
||||
bool bWouldBeAvailable = true;
|
||||
for (const FName& PrereqID : SkillNode->NodeData.Prerequisites)
|
||||
{
|
||||
if (!IsSkillPurchased(PrereqID) && !IsSkillSelected(PrereqID))
|
||||
{
|
||||
bWouldBeAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bWouldBeAvailable)
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT("Prerequisites not selected. Select prerequisite skills first."));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
SkillNode->bIsSelected = true;
|
||||
SelectedSkills.Add(SkillID);
|
||||
UpdateSelectionCost();
|
||||
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
OnSkillStateChanged.Broadcast(SkillID);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::DeselectSkill(FName SkillID)
|
||||
{
|
||||
if (!AllSkills.Contains(SkillID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
|
||||
if (!SkillNode->bIsSelected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any selected skills depend on this one
|
||||
for (const FName& SelectedID : SelectedSkills)
|
||||
{
|
||||
if (SelectedID != SkillID)
|
||||
{
|
||||
FSkillNodeRuntime* SelectedNode = AllSkills.Find(SelectedID);
|
||||
if (SelectedNode && SelectedNode->NodeData.Prerequisites.Contains(SkillID))
|
||||
{
|
||||
// Can't deselect if other selected skills depend on it
|
||||
FText ErrorMessage = FText::FromString(TEXT("Other selected skills depend on this one"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from selection
|
||||
SkillNode->bIsSelected = false;
|
||||
SelectedSkills.Remove(SkillID);
|
||||
UpdateSelectionCost();
|
||||
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
OnSkillStateChanged.Broadcast(SkillID);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void USkillTreeComponent::ClearSelection()
|
||||
{
|
||||
for (const FName& SkillID : SelectedSkills)
|
||||
{
|
||||
if (FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
SkillNode->bIsSelected = false;
|
||||
OnSkillStateChanged.Broadcast(SkillID);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedSkills.Empty();
|
||||
UpdateSelectionCost();
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::ConfirmPurchase()
|
||||
{
|
||||
// Validate we can afford it
|
||||
if (!CanAffordSelection())
|
||||
{
|
||||
FText ErrorMessage = FText::FromString(TEXT("Not enough skill points"));
|
||||
OnSelectionError.Broadcast(NAME_None, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort selected skills by purchase order to ensure prerequisites are purchased first
|
||||
TArray<FName> SortedSelection = SelectedSkills;
|
||||
SortedSelection.Sort([this](const FName& A, const FName& B) {
|
||||
FSkillNodeRuntime* NodeA = AllSkills.Find(A);
|
||||
FSkillNodeRuntime* NodeB = AllSkills.Find(B);
|
||||
if (NodeA && NodeB)
|
||||
{
|
||||
return NodeA->NodeData.PurchaseOrder < NodeB->NodeData.PurchaseOrder;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Validate all skills can be purchased in order
|
||||
for (const FName& SkillID : SortedSelection)
|
||||
{
|
||||
FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
if (!SkillNode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check prerequisites (considering already processed skills in this batch)
|
||||
bool bPrereqsMet = true;
|
||||
for (const FName& PrereqID : SkillNode->NodeData.Prerequisites)
|
||||
{
|
||||
if (!IsSkillPurchased(PrereqID))
|
||||
{
|
||||
// Check if it's in our current batch and would be purchased before this
|
||||
int32 PrereqIndex = SortedSelection.IndexOfByKey(PrereqID);
|
||||
int32 CurrentIndex = SortedSelection.IndexOfByKey(SkillID);
|
||||
|
||||
if (PrereqIndex == INDEX_NONE || PrereqIndex >= CurrentIndex)
|
||||
{
|
||||
bPrereqsMet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bPrereqsMet)
|
||||
{
|
||||
FText ErrorMessage = FText::Format(
|
||||
FText::FromString(TEXT("Cannot purchase {0}: prerequisites not met")),
|
||||
SkillNode->NodeData.DisplayName
|
||||
);
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Purchase all selected skills
|
||||
for (const FName& SkillID : SortedSelection)
|
||||
{
|
||||
FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
if (SkillNode)
|
||||
{
|
||||
// Mark as purchased
|
||||
SkillNode->CurrentState = ESkillNodeState::Purchased;
|
||||
SkillNode->bIsSelected = false;
|
||||
PurchasedSkills.Add(SkillID);
|
||||
|
||||
// Apply effects
|
||||
ApplySkillEffects(SkillNode->NodeData);
|
||||
|
||||
// Broadcast purchase event
|
||||
OnSkillPurchased.Broadcast(SkillID);
|
||||
OnSkillStateChanged.Broadcast(SkillID);
|
||||
}
|
||||
}
|
||||
|
||||
// Deduct skill points
|
||||
RemoveSkillPoints(CurrentSelectionCost);
|
||||
|
||||
// Clear selection
|
||||
SelectedSkills.Empty();
|
||||
UpdateSelectionCost();
|
||||
UpdateSkillStates();
|
||||
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::IsSkillPurchased(FName SkillID) const
|
||||
{
|
||||
return PurchasedSkills.Contains(SkillID);
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::IsSkillSelected(FName SkillID) const
|
||||
{
|
||||
return SelectedSkills.Contains(SkillID);
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::IsSkillAvailable(FName SkillID) const
|
||||
{
|
||||
const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
if (!SkillNode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return SkillNode->CurrentState == ESkillNodeState::Available;
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::CanSelectSkill(FName SkillID, FText& OutErrorMessage) const
|
||||
{
|
||||
const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
if (!SkillNode)
|
||||
{
|
||||
OutErrorMessage = FText::FromString(TEXT("Skill does not exist"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SkillNode->CurrentState == ESkillNodeState::Purchased)
|
||||
{
|
||||
OutErrorMessage = FText::FromString(TEXT("Skill already purchased"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SkillNode->bIsSelected)
|
||||
{
|
||||
OutErrorMessage = FText::FromString(TEXT("Skill already selected"));
|
||||
return false;
|
||||
}
|
||||
|
||||
OutErrorMessage = FText::GetEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
ESkillNodeState USkillTreeComponent::GetSkillState(FName SkillID) const
|
||||
{
|
||||
const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
if (SkillNode)
|
||||
{
|
||||
return SkillNode->CurrentState;
|
||||
}
|
||||
|
||||
return ESkillNodeState::Locked;
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::GetSkillNodeData(FName SkillID, FSkillNodeRuntime& OutNodeData) const
|
||||
{
|
||||
const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
if (SkillNode)
|
||||
{
|
||||
OutNodeData = *SkillNode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FSkillNodeRuntime> USkillTreeComponent::GetAllSkillNodes() const
|
||||
{
|
||||
TArray<FSkillNodeRuntime> Result;
|
||||
for (const auto& Pair : AllSkills)
|
||||
{
|
||||
Result.Add(Pair.Value);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
float USkillTreeComponent::GetTotalHealthBonus() const
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
Total += SkillNode->NodeData.HealthBonus;
|
||||
}
|
||||
}
|
||||
return Total;
|
||||
}
|
||||
|
||||
float USkillTreeComponent::GetTotalDamageBonus() const
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
Total += SkillNode->NodeData.DamageBonus;
|
||||
}
|
||||
}
|
||||
return Total;
|
||||
}
|
||||
|
||||
float USkillTreeComponent::GetTotalSpeedBonus() const
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
Total += SkillNode->NodeData.SpeedBonus;
|
||||
}
|
||||
}
|
||||
return Total;
|
||||
}
|
||||
|
||||
float USkillTreeComponent::GetTotalHealthBonusPercent() const
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
Total += SkillNode->NodeData.HealthBonusPercent;
|
||||
}
|
||||
}
|
||||
return Total;
|
||||
}
|
||||
|
||||
float USkillTreeComponent::GetTotalDamageBonusPercent() const
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
Total += SkillNode->NodeData.DamageBonusPercent;
|
||||
}
|
||||
}
|
||||
return Total;
|
||||
}
|
||||
|
||||
float USkillTreeComponent::GetTotalSpeedBonusPercent() const
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
Total += SkillNode->NodeData.SpeedBonusPercent;
|
||||
}
|
||||
}
|
||||
return Total;
|
||||
}
|
||||
|
||||
void USkillTreeComponent::ResetAllSkills()
|
||||
{
|
||||
// Refund all skill points
|
||||
int32 RefundAmount = 0;
|
||||
for (const FName& SkillID : PurchasedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
RefundAmount += SkillNode->NodeData.Cost;
|
||||
RemoveSkillEffects(SkillNode->NodeData);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear purchases
|
||||
PurchasedSkills.Empty();
|
||||
|
||||
// Reset all skill states
|
||||
for (auto& Pair : AllSkills)
|
||||
{
|
||||
Pair.Value.CurrentState = ESkillNodeState::Locked;
|
||||
Pair.Value.bIsSelected = false;
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
SelectedSkills.Empty();
|
||||
CurrentSelectionCost = 0;
|
||||
|
||||
// Add refunded points
|
||||
AddSkillPoints(RefundAmount);
|
||||
|
||||
// Update states
|
||||
UpdateSkillStates();
|
||||
|
||||
// Broadcast events
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
}
|
||||
|
||||
void USkillTreeComponent::UpdateSkillStates()
|
||||
{
|
||||
for (auto& Pair : AllSkills)
|
||||
{
|
||||
FSkillNodeRuntime& SkillNode = Pair.Value;
|
||||
|
||||
// Skip if already purchased
|
||||
if (SkillNode.CurrentState == ESkillNodeState::Purchased)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if prerequisites are met
|
||||
if (ArePrerequisitesMet(SkillNode.NodeData))
|
||||
{
|
||||
SkillNode.CurrentState = SkillNode.bIsSelected ?
|
||||
ESkillNodeState::Selected : ESkillNodeState::Available;
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillNode.CurrentState = SkillNode.bIsSelected ?
|
||||
ESkillNodeState::Selected : ESkillNodeState::Locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USkillTreeComponent::UpdateSelectionCost()
|
||||
{
|
||||
CurrentSelectionCost = 0;
|
||||
for (const FName& SkillID : SelectedSkills)
|
||||
{
|
||||
if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID))
|
||||
{
|
||||
CurrentSelectionCost += SkillNode->NodeData.Cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::ArePrerequisitesMet(const FSkillNodeData& SkillData) const
|
||||
{
|
||||
for (const FName& PrereqID : SkillData.Prerequisites)
|
||||
{
|
||||
if (!IsSkillPurchased(PrereqID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::HasChildrenPurchased(FName SkillID) const
|
||||
{
|
||||
for (const auto& Pair : AllSkills)
|
||||
{
|
||||
// Pair.Key is the RowName (SkillID)
|
||||
if (Pair.Value.NodeData.Prerequisites.Contains(SkillID) &&
|
||||
IsSkillPurchased(Pair.Key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void USkillTreeComponent::ApplySkillEffects(const FSkillNodeData& SkillData)
|
||||
{
|
||||
// This is where you would apply the actual skill effects to the character
|
||||
// For now, we just log it
|
||||
UE_LOG(LogTemp, Log, TEXT("Applied skill effects for: %s"), *SkillData.DisplayName.ToString());
|
||||
}
|
||||
|
||||
void USkillTreeComponent::RemoveSkillEffects(const FSkillNodeData& SkillData)
|
||||
{
|
||||
// This is where you would remove skill effects from the character
|
||||
// For now, we just log it
|
||||
UE_LOG(LogTemp, Log, TEXT("Removed skill effects for: %s"), *SkillData.DisplayName.ToString());
|
||||
}
|
||||
163
Source/UTAD_UI/SkillTree/SkillTreeComponent.h
Normal file
163
Source/UTAD_UI/SkillTree/SkillTreeComponent.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "SkillTreeTypes.h"
|
||||
#include "SkillTreeComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillPointsChanged, int32, NewSkillPoints);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillPurchased, FName, SkillID);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillSelectionChanged, int32, TotalCost);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillStateChanged, FName, SkillID);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSelectionError, FName, SkillID, FText, ErrorMessage);
|
||||
|
||||
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
|
||||
class UTAD_UI_API USkillTreeComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
USkillTreeComponent();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
// Current skill points available
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill Points", SaveGame)
|
||||
int32 AvailableSkillPoints;
|
||||
|
||||
// Total skill points earned
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Points", SaveGame)
|
||||
int32 TotalSkillPointsEarned;
|
||||
|
||||
// Data table containing all skill definitions
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Skill Tree")
|
||||
class UDataTable* SkillDataTable;
|
||||
|
||||
// All skills loaded from the data table
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Tree")
|
||||
TMap<FName, FSkillNodeRuntime> AllSkills;
|
||||
|
||||
// Currently purchased skills
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Tree", SaveGame)
|
||||
TArray<FName> PurchasedSkills;
|
||||
|
||||
// Currently selected skills for purchase
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Tree")
|
||||
TArray<FName> SelectedSkills;
|
||||
|
||||
// Cache the total cost of selected skills
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Tree")
|
||||
int32 CurrentSelectionCost;
|
||||
|
||||
public:
|
||||
// Initialize the skill tree from data table
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Tree")
|
||||
void InitializeSkillTree();
|
||||
|
||||
// Skill Points Management
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Points")
|
||||
void AddSkillPoints(int32 Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Points")
|
||||
void RemoveSkillPoints(int32 Amount);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Points")
|
||||
int32 GetAvailableSkillPoints() const { return AvailableSkillPoints; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Points")
|
||||
int32 GetTotalSkillPointsEarned() const { return TotalSkillPointsEarned; }
|
||||
|
||||
// Selection Management
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Selection")
|
||||
bool SelectSkill(FName SkillID);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Selection")
|
||||
bool DeselectSkill(FName SkillID);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Selection")
|
||||
void ClearSelection();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Selection")
|
||||
bool ConfirmPurchase();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Selection")
|
||||
TArray<FName> GetSelectedSkills() const { return SelectedSkills; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Selection")
|
||||
int32 GetSelectionCost() const { return CurrentSelectionCost; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Selection")
|
||||
bool CanAffordSelection() const { return AvailableSkillPoints >= CurrentSelectionCost; }
|
||||
|
||||
// Skill State Queries
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool IsSkillPurchased(FName SkillID) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool IsSkillSelected(FName SkillID) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool IsSkillAvailable(FName SkillID) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool CanSelectSkill(FName SkillID, FText& OutErrorMessage) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
ESkillNodeState GetSkillState(FName SkillID) const;
|
||||
|
||||
// Get skill data
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Data")
|
||||
bool GetSkillNodeData(FName SkillID, FSkillNodeRuntime& OutNodeData) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Data")
|
||||
TArray<FSkillNodeRuntime> GetAllSkillNodes() const;
|
||||
|
||||
// Calculate total bonuses from purchased skills
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalHealthBonus() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalDamageBonus() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalSpeedBonus() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalHealthBonusPercent() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalDamageBonusPercent() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalSpeedBonusPercent() const;
|
||||
|
||||
// Reset skills (for respec feature)
|
||||
UFUNCTION(BlueprintCallable, Category = "Skill Tree")
|
||||
void ResetAllSkills();
|
||||
|
||||
// Events
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillPointsChanged OnSkillPointsChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillPurchased OnSkillPurchased;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillSelectionChanged OnSkillSelectionChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillStateChanged OnSkillStateChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSelectionError OnSelectionError;
|
||||
|
||||
private:
|
||||
// Internal helper functions
|
||||
void UpdateSkillStates();
|
||||
void UpdateSelectionCost();
|
||||
bool ArePrerequisitesMet(const FSkillNodeData& SkillData) const;
|
||||
bool HasChildrenPurchased(FName SkillID) const;
|
||||
void ApplySkillEffects(const FSkillNodeData& SkillData);
|
||||
void RemoveSkillEffects(const FSkillNodeData& SkillData);
|
||||
};
|
||||
104
Source/UTAD_UI/SkillTree/SkillTreeTypes.h
Normal file
104
Source/UTAD_UI/SkillTree/SkillTreeTypes.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "SkillTreeTypes.generated.h"
|
||||
|
||||
// Enum for skill node state
|
||||
UENUM(BlueprintType)
|
||||
enum class ESkillNodeState : uint8
|
||||
{
|
||||
Locked UMETA(DisplayName = "Locked"), // Can't be purchased yet (missing prerequisites)
|
||||
Available UMETA(DisplayName = "Available"), // Can be purchased
|
||||
Selected UMETA(DisplayName = "Selected"), // Currently selected for purchase
|
||||
Purchased UMETA(DisplayName = "Purchased") // Already owned
|
||||
};
|
||||
|
||||
// Struct for individual skill node data
|
||||
// Note: RowName in the DataTable serves as the unique SkillID
|
||||
USTRUCT(BlueprintType)
|
||||
struct FSkillNodeData : public FTableRowBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Display name for UI
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill")
|
||||
FText DisplayName;
|
||||
|
||||
// Description of what the skill does
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill")
|
||||
FText Description;
|
||||
|
||||
// Icon for the skill
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill")
|
||||
class UTexture2D* Icon;
|
||||
|
||||
// Skill point cost
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill")
|
||||
int32 Cost;
|
||||
|
||||
// Purchase order priority (lower = buy first, used when confirming multiple skills)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill")
|
||||
int32 PurchaseOrder;
|
||||
|
||||
// Prerequisites - must have these skills first (RowNames of required skills)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill")
|
||||
TArray<FName> Prerequisites;
|
||||
|
||||
// Effects this skill provides (actual stat bonuses)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||
float HealthBonus;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||
float DamageBonus;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||
float SpeedBonus;
|
||||
|
||||
// Percentage bonuses (multiplicative)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||
float HealthBonusPercent;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||
float DamageBonusPercent;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||
float SpeedBonusPercent;
|
||||
|
||||
FSkillNodeData()
|
||||
{
|
||||
DisplayName = FText::GetEmpty();
|
||||
Description = FText::GetEmpty();
|
||||
Icon = nullptr;
|
||||
Cost = 1;
|
||||
PurchaseOrder = 0;
|
||||
HealthBonus = 0.0f;
|
||||
DamageBonus = 0.0f;
|
||||
SpeedBonus = 0.0f;
|
||||
HealthBonusPercent = 0.0f;
|
||||
DamageBonusPercent = 0.0f;
|
||||
SpeedBonusPercent = 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
// Runtime struct for tracking skill state
|
||||
USTRUCT(BlueprintType)
|
||||
struct FSkillNodeRuntime
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill")
|
||||
FSkillNodeData NodeData;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill")
|
||||
ESkillNodeState CurrentState;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill")
|
||||
bool bIsSelected;
|
||||
|
||||
FSkillNodeRuntime()
|
||||
{
|
||||
CurrentState = ESkillNodeState::Locked;
|
||||
bIsSelected = false;
|
||||
}
|
||||
};
|
||||
@@ -17,7 +17,10 @@ public class UTAD_UI : ModuleRules
|
||||
"NavigationSystem",
|
||||
"AIModule",
|
||||
"Niagara",
|
||||
"EnhancedInput"
|
||||
"EnhancedInput",
|
||||
"UMG",
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "Engine/World.h"
|
||||
#include "SkillTree/SkillTreeComponent.h"
|
||||
#include "SkillTree/SkillTreeSubsystem.h"
|
||||
#include "SkillTree/CharacterStatsComponent.h"
|
||||
|
||||
AUTAD_UICharacter::AUTAD_UICharacter()
|
||||
{
|
||||
@@ -50,9 +53,46 @@ AUTAD_UICharacter::AUTAD_UICharacter()
|
||||
// Activate ticking in order to update the cursor every frame.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
PrimaryActorTick.bStartWithTickEnabled = true;
|
||||
|
||||
// Create the skill tree component
|
||||
SkillTreeComponent = CreateDefaultSubobject<USkillTreeComponent>(TEXT("SkillTreeComponent"));
|
||||
|
||||
// Create the character stats component
|
||||
CharacterStatsComponent = CreateDefaultSubobject<UCharacterStatsComponent>(TEXT("CharacterStatsComponent"));
|
||||
|
||||
// Initialize character level
|
||||
CharacterLevel = 1;
|
||||
}
|
||||
|
||||
void AUTAD_UICharacter::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Register the skill tree component with the manager
|
||||
if (SkillTreeComponent)
|
||||
{
|
||||
if (USkillTreeSubsystem* Manager = USkillTreeSubsystem::GetSkillTreeManager(this))
|
||||
{
|
||||
Manager->RegisterPlayerSkillTree(SkillTreeComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AUTAD_UICharacter::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
}
|
||||
|
||||
void AUTAD_UICharacter::LevelUp()
|
||||
{
|
||||
CharacterLevel++;
|
||||
|
||||
// Grant skill points on level up
|
||||
if (SkillTreeComponent)
|
||||
{
|
||||
// Grant 1 skill point per level (can be configured)
|
||||
SkillTreeComponent->AddSkillPoints(1);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("Character leveled up to %d! Gained 1 skill point."), CharacterLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ public:
|
||||
// Called every frame.
|
||||
virtual void Tick(float DeltaSeconds) override;
|
||||
|
||||
// Called when the game starts
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** Returns TopDownCameraComponent subobject **/
|
||||
FORCEINLINE class UCameraComponent* GetTopDownCameraComponent() const
|
||||
{
|
||||
@@ -28,6 +31,24 @@ public:
|
||||
return CameraBoom;
|
||||
}
|
||||
|
||||
/** Returns SkillTreeComponent subobject **/
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Tree")
|
||||
class USkillTreeComponent* GetSkillTreeComponent() const
|
||||
{
|
||||
return SkillTreeComponent;
|
||||
}
|
||||
|
||||
/** Returns CharacterStatsComponent subobject **/
|
||||
UFUNCTION(BlueprintPure, Category = "Character Stats")
|
||||
class UCharacterStatsComponent* GetStatsComponent() const
|
||||
{
|
||||
return CharacterStatsComponent;
|
||||
}
|
||||
|
||||
// Level up function to grant skill points
|
||||
UFUNCTION(BlueprintCallable, Category = "Character")
|
||||
void LevelUp();
|
||||
|
||||
private:
|
||||
/** Top down camera */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera,
|
||||
@@ -38,4 +59,19 @@ private:
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera,
|
||||
meta = (AllowPrivateAccess = "true"))
|
||||
class USpringArmComponent* CameraBoom;
|
||||
|
||||
/** Skill Tree Component for managing character skills */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Skill Tree",
|
||||
meta = (AllowPrivateAccess = "true"))
|
||||
class USkillTreeComponent* SkillTreeComponent;
|
||||
|
||||
/** Character Stats Component for managing health, damage, speed */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character Stats",
|
||||
meta = (AllowPrivateAccess = "true"))
|
||||
class UCharacterStatsComponent* CharacterStatsComponent;
|
||||
|
||||
// Current character level
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Character",
|
||||
meta = (AllowPrivateAccess = "true"))
|
||||
int32 CharacterLevel;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ AUTAD_UIGameMode::AUTAD_UIGameMode()
|
||||
|
||||
// set default pawn class to our Blueprinted character
|
||||
static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(
|
||||
TEXT("/Game/TopDown/Blueprints/BP_TopDownCharacter"));
|
||||
TEXT("/Game/Blueprints/BP_TopDownCharacter"));
|
||||
if (PlayerPawnBPClass.Class != nullptr)
|
||||
{
|
||||
DefaultPawnClass = PlayerPawnBPClass.Class;
|
||||
@@ -21,7 +21,7 @@ AUTAD_UIGameMode::AUTAD_UIGameMode()
|
||||
// set default controller to our Blueprinted controller
|
||||
static ConstructorHelpers::FClassFinder<APlayerController>
|
||||
PlayerControllerBPClass(
|
||||
TEXT("/Game/TopDown/Blueprints/BP_TopDownPlayerController"));
|
||||
TEXT("/Game/Blueprints/BP_TopDownPlayerController"));
|
||||
if (PlayerControllerBPClass.Class != NULL)
|
||||
{
|
||||
PlayerControllerClass = PlayerControllerBPClass.Class;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "InputActionValue.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogTemplateCharacter);
|
||||
|
||||
@@ -26,6 +27,9 @@ void AUTAD_UIPlayerController::BeginPlay()
|
||||
{
|
||||
// Call the base class
|
||||
Super::BeginPlay();
|
||||
|
||||
// Create and show the main HUD
|
||||
ShowMainHUD();
|
||||
}
|
||||
|
||||
void AUTAD_UIPlayerController::SetupInputComponent()
|
||||
@@ -72,6 +76,14 @@ void AUTAD_UIPlayerController::SetupInputComponent()
|
||||
EnhancedInputComponent->BindAction(
|
||||
SetDestinationTouchAction, ETriggerEvent::Canceled, this,
|
||||
&AUTAD_UIPlayerController::OnTouchReleased);
|
||||
|
||||
// Setup skill tree input
|
||||
if (OpenSkillTreeAction)
|
||||
{
|
||||
EnhancedInputComponent->BindAction(
|
||||
OpenSkillTreeAction, ETriggerEvent::Triggered, this,
|
||||
&AUTAD_UIPlayerController::OnOpenSkillTree);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -154,3 +166,66 @@ void AUTAD_UIPlayerController::OnTouchReleased()
|
||||
bIsTouch = false;
|
||||
OnSetDestinationReleased();
|
||||
}
|
||||
|
||||
void AUTAD_UIPlayerController::OnOpenSkillTree()
|
||||
{
|
||||
ToggleSkillTree();
|
||||
}
|
||||
|
||||
void AUTAD_UIPlayerController::ShowMainHUD()
|
||||
{
|
||||
if (MainHUDClass && !MainHUDWidget)
|
||||
{
|
||||
MainHUDWidget = CreateWidget<UUserWidget>(this, MainHUDClass);
|
||||
if (MainHUDWidget)
|
||||
{
|
||||
MainHUDWidget->AddToViewport(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AUTAD_UIPlayerController::ShowSkillTree()
|
||||
{
|
||||
if (!SkillTreeWidget && SkillTreeWidgetClass)
|
||||
{
|
||||
SkillTreeWidget = CreateWidget<UUserWidget>(this, SkillTreeWidgetClass);
|
||||
}
|
||||
|
||||
if (SkillTreeWidget && !SkillTreeWidget->IsInViewport())
|
||||
{
|
||||
SkillTreeWidget->AddToViewport(1);
|
||||
|
||||
// Set input mode to Game and UI
|
||||
FInputModeGameAndUI InputMode;
|
||||
InputMode.SetWidgetToFocus(SkillTreeWidget->TakeWidget());
|
||||
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
|
||||
SetInputMode(InputMode);
|
||||
bShowMouseCursor = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AUTAD_UIPlayerController::HideSkillTree()
|
||||
{
|
||||
if (SkillTreeWidget && SkillTreeWidget->IsInViewport())
|
||||
{
|
||||
SkillTreeWidget->RemoveFromParent();
|
||||
|
||||
// Set input mode back to game only
|
||||
FInputModeGameOnly InputMode;
|
||||
SetInputMode(InputMode);
|
||||
// Keep mouse cursor visible for top-down game
|
||||
bShowMouseCursor = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AUTAD_UIPlayerController::ToggleSkillTree()
|
||||
{
|
||||
if (SkillTreeWidget && SkillTreeWidget->IsInViewport())
|
||||
{
|
||||
HideSkillTree();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSkillTree();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,19 @@ class AUTAD_UIPlayerController : public APlayerController
|
||||
public:
|
||||
AUTAD_UIPlayerController();
|
||||
|
||||
// UI Management
|
||||
UFUNCTION(BlueprintCallable, Category = "UI")
|
||||
void ShowMainHUD();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "UI")
|
||||
void ShowSkillTree();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "UI")
|
||||
void HideSkillTree();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "UI")
|
||||
void ToggleSkillTree();
|
||||
|
||||
/** Time Threshold to know if it was a short press */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
|
||||
float ShortPressThreshold;
|
||||
@@ -45,6 +58,11 @@ public:
|
||||
meta = (AllowPrivateAccess = "true"))
|
||||
UInputAction* SetDestinationTouchAction;
|
||||
|
||||
/** Open Skill Tree Input Action */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input,
|
||||
meta = (AllowPrivateAccess = "true"))
|
||||
UInputAction* OpenSkillTreeAction;
|
||||
|
||||
protected:
|
||||
/** True if the controlled character should navigate to the mouse cursor. */
|
||||
uint32 bMoveToMouseCursor : 1;
|
||||
@@ -61,6 +79,23 @@ protected:
|
||||
void OnTouchTriggered();
|
||||
void OnTouchReleased();
|
||||
|
||||
/** Input handler for opening skill tree */
|
||||
void OnOpenSkillTree();
|
||||
|
||||
// Widget Classes
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
|
||||
TSubclassOf<class UUserWidget> MainHUDClass;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
|
||||
TSubclassOf<class UUserWidget> SkillTreeWidgetClass;
|
||||
|
||||
// Widget Instances
|
||||
UPROPERTY()
|
||||
class UUserWidget* MainHUDWidget;
|
||||
|
||||
UPROPERTY()
|
||||
class UUserWidget* SkillTreeWidget;
|
||||
|
||||
private:
|
||||
FVector CachedDestination;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user