300 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			300 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "SkillTreeConnectionsWidget.h"
 | |
| #include "SkillTreeComponent.h"
 | |
| #include "Blueprint/WidgetTree.h"
 | |
| #include "Components/PanelWidget.h"
 | |
| #include "Rendering/DrawElements.h"
 | |
| #include "Blueprint/WidgetLayoutLibrary.h"
 | |
| 
 | |
| USkillTreeConnectionsWidget::USkillTreeConnectionsWidget(
 | |
| 	const FObjectInitializer& ObjectInitializer)
 | |
| 	: Super(ObjectInitializer)
 | |
| {
 | |
| 	bNeedsRedraw = true;
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::NativeConstruct()
 | |
| {
 | |
| 	Super::NativeConstruct();
 | |
| 
 | |
| 	// Bind to skill tree component events for automatic refresh
 | |
| 	if (SkillTreeComponent)
 | |
| 	{
 | |
| 		// Remove any existing bindings first to prevent double-binding
 | |
| 		SkillTreeComponent->OnSkillStateChanged.RemoveDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillStateChangedHandler);
 | |
| 		SkillTreeComponent->OnSkillSelectionChanged.RemoveDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillSelectionChangedHandler);
 | |
| 		SkillTreeComponent->OnSkillPurchased.RemoveDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillPurchasedHandler);
 | |
| 
 | |
| 		// Now bind
 | |
| 		SkillTreeComponent->OnSkillStateChanged.AddDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillStateChangedHandler);
 | |
| 		SkillTreeComponent->OnSkillSelectionChanged.AddDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillSelectionChangedHandler);
 | |
| 		SkillTreeComponent->OnSkillPurchased.AddDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillPurchasedHandler);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::NativeDestruct()
 | |
| {
 | |
| 	// Unbind from events to prevent memory leaks
 | |
| 	if (SkillTreeComponent)
 | |
| 	{
 | |
| 		SkillTreeComponent->OnSkillStateChanged.RemoveDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillStateChangedHandler);
 | |
| 		SkillTreeComponent->OnSkillSelectionChanged.RemoveDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillSelectionChangedHandler);
 | |
| 		SkillTreeComponent->OnSkillPurchased.RemoveDynamic(
 | |
| 			this, &USkillTreeConnectionsWidget::OnSkillPurchasedHandler);
 | |
| 	}
 | |
| 
 | |
| 	Super::NativeDestruct();
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::RefreshConnections()
 | |
| {
 | |
| 	bNeedsRedraw = true;
 | |
| 	// Force widget to repaint
 | |
| 	if (IsValid(this))
 | |
| 	{
 | |
| 		Invalidate(EInvalidateWidgetReason::Paint);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::OnSkillStateChangedHandler(FName SkillID)
 | |
| {
 | |
| 	RefreshConnections();
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::OnSkillSelectionChangedHandler(
 | |
| 	int32 TotalCost)
 | |
| {
 | |
| 	RefreshConnections();
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::OnSkillPurchasedHandler(FName SkillID)
 | |
| {
 | |
| 	RefreshConnections();
 | |
| }
 | |
| 
 | |
| bool USkillTreeConnectionsWidget::GetSkillIDFromWidget(UUserWidget* Widget,
 | |
| 													   FName& OutSkillID) const
 | |
| {
 | |
| 	if (!Widget)
 | |
| 		return false;
 | |
| 
 | |
| 	// Try to find a property named "SkillID" on the widget
 | |
| 	for (TFieldIterator<FProperty> PropIt(Widget->GetClass()); PropIt; ++PropIt)
 | |
| 	{
 | |
| 		FProperty* Property = *PropIt;
 | |
| 		if (Property && Property->GetFName() == FName(TEXT("SkillID")))
 | |
| 		{
 | |
| 			// Check if it's a FName property
 | |
| 			if (FNameProperty* NameProperty =
 | |
| 					CastField<FNameProperty>(Property))
 | |
| 			{
 | |
| 				const void* ValuePtr =
 | |
| 					Property->ContainerPtrToValuePtr<void>(Widget);
 | |
| 				OutSkillID = NameProperty->GetPropertyValue(ValuePtr);
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::FindWidgetsOfClass(
 | |
| 	UWidget* Root, TSubclassOf<UUserWidget> WidgetClass,
 | |
| 	TArray<UUserWidget*>& OutWidgets) const
 | |
| {
 | |
| 	if (!Root || !WidgetClass)
 | |
| 		return;
 | |
| 
 | |
| 	// Check if this widget is a UserWidget of the desired class
 | |
| 	if (UUserWidget* UserWidget = Cast<UUserWidget>(Root))
 | |
| 	{
 | |
| 		if (UserWidget->IsA(WidgetClass))
 | |
| 		{
 | |
| 			OutWidgets.Add(UserWidget);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If this is a panel widget, recursively search its children
 | |
| 	if (UPanelWidget* PanelWidget = Cast<UPanelWidget>(Root))
 | |
| 	{
 | |
| 		for (int32 i = 0; i < PanelWidget->GetChildrenCount(); ++i)
 | |
| 		{
 | |
| 			UWidget* Child = PanelWidget->GetChildAt(i);
 | |
| 			FindWidgetsOfClass(Child, WidgetClass, OutWidgets);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If this is a UserWidget, search its widget tree
 | |
| 	if (UUserWidget* UserWidget = Cast<UUserWidget>(Root))
 | |
| 	{
 | |
| 		if (UserWidget->WidgetTree)
 | |
| 		{
 | |
| 			TArray<UWidget*> AllWidgets;
 | |
| 			UserWidget->WidgetTree->GetAllWidgets(AllWidgets);
 | |
| 
 | |
| 			for (UWidget* Widget : AllWidgets)
 | |
| 			{
 | |
| 				if (Widget != Root) // Avoid infinite recursion
 | |
| 				{
 | |
| 					FindWidgetsOfClass(Widget, WidgetClass, OutWidgets);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void USkillTreeConnectionsWidget::BuildNodeInfoList(
 | |
| 	TArray<FSkillNodeInfo>& OutNodeInfo) const
 | |
| {
 | |
| 	OutNodeInfo.Empty();
 | |
| 
 | |
| 	if (!SkillTreeComponent || !SkillNodeWidgetClass)
 | |
| 		return;
 | |
| 
 | |
| 	// Determine root widget to search from
 | |
| 	UWidget* RootWidget = SkillTreeWidget;
 | |
| 	if (!RootWidget)
 | |
| 	{
 | |
| 		// If no parent widget is set, try to get the outer widget
 | |
| 		RootWidget = Cast<UWidget>(GetOuter());
 | |
| 		if (!RootWidget)
 | |
| 		{
 | |
| 			// As last resort, try to get root widget from widget tree
 | |
| 			if (WidgetTree)
 | |
| 			{
 | |
| 				RootWidget = WidgetTree->RootWidget;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (!RootWidget)
 | |
| 	{
 | |
| 		UE_LOG(
 | |
| 			LogTemp, Error,
 | |
| 			TEXT(
 | |
| 				"SkillTreeConnectionsWidget: No valid root widget found to search for skill nodes"));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// Find all skill node widgets recursively
 | |
| 	TArray<UUserWidget*> SkillNodeWidgets;
 | |
| 	FindWidgetsOfClass(RootWidget, SkillNodeWidgetClass, SkillNodeWidgets);
 | |
| 
 | |
| 	// Build info for each skill node
 | |
| 	for (UUserWidget* SkillNodeWidget : SkillNodeWidgets)
 | |
| 	{
 | |
| 		if (!SkillNodeWidget)
 | |
| 			continue;
 | |
| 
 | |
| 		// Try to get the SkillID from the widget
 | |
| 		FName SkillID;
 | |
| 		if (!GetSkillIDFromWidget(SkillNodeWidget, SkillID))
 | |
| 			continue;
 | |
| 
 | |
| 		// Get the skill node data from the component
 | |
| 		FSkillNodeRuntime NodeData;
 | |
| 		if (!SkillTreeComponent->GetSkillNodeData(SkillID, NodeData))
 | |
| 			continue;
 | |
| 
 | |
| 		// Get the widget's geometry to determine its position
 | |
| 		const FGeometry& Geometry  = SkillNodeWidget->GetCachedGeometry();
 | |
| 		FVector2D		 LocalSize = Geometry.GetLocalSize();
 | |
| 		FVector2D		 AbsolutePosition = Geometry.GetAbsolutePosition();
 | |
| 
 | |
| 		// Calculate center position (in absolute/screen space)
 | |
| 		FVector2D CenterPosition = AbsolutePosition + (LocalSize * 0.5f);
 | |
| 
 | |
| 		// Build the info struct
 | |
| 		FSkillNodeInfo NodeInfo;
 | |
| 		NodeInfo.SkillID	   = SkillID;
 | |
| 		NodeInfo.Position	   = CenterPosition;
 | |
| 		NodeInfo.State		   = NodeData.CurrentState;
 | |
| 		NodeInfo.Prerequisites = NodeData.NodeData.Prerequisites;
 | |
| 
 | |
| 		OutNodeInfo.Add(NodeInfo);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| FLinearColor
 | |
| USkillTreeConnectionsWidget::GetLineColorForState(ESkillNodeState State) const
 | |
| {
 | |
| 	switch (State)
 | |
| 	{
 | |
| 	case ESkillNodeState::Locked:
 | |
| 		return LockedColor;
 | |
| 	case ESkillNodeState::Available:
 | |
| 		return AvailableColor;
 | |
| 	case ESkillNodeState::Selected:
 | |
| 		return SelectedColor;
 | |
| 	case ESkillNodeState::Purchased:
 | |
| 		return PurchasedColor;
 | |
| 	default:
 | |
| 		return LockedColor;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int32 USkillTreeConnectionsWidget::NativePaint(
 | |
| 	const FPaintArgs& Args, const FGeometry& AllottedGeometry,
 | |
| 	const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements,
 | |
| 	int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
 | |
| {
 | |
| 	int32 MaxLayerId = Super::NativePaint(Args, AllottedGeometry, MyCullingRect,
 | |
| 										  OutDrawElements, LayerId,
 | |
| 										  InWidgetStyle, bParentEnabled);
 | |
| 
 | |
| 	if (!SkillTreeComponent || !SkillNodeWidgetClass)
 | |
| 		return MaxLayerId;
 | |
| 
 | |
| 	// Build node information
 | |
| 	TArray<FSkillNodeInfo> NodeInfoList;
 | |
| 	BuildNodeInfoList(NodeInfoList);
 | |
| 
 | |
| 	// Get this widget's absolute position to convert coordinates
 | |
| 	FVector2D ConnectionsWidgetAbsolutePosition = AllottedGeometry.GetAbsolutePosition();
 | |
| 
 | |
| 	// Draw lines for each node's prerequisites
 | |
| 	for (const FSkillNodeInfo& DependentNode : NodeInfoList)
 | |
| 	{
 | |
| 		// Get the color for this node's connections
 | |
| 		FLinearColor LineColor = GetLineColorForState(DependentNode.State);
 | |
| 
 | |
| 		// Draw lines to each prerequisite
 | |
| 		for (const FName& PrerequisiteID : DependentNode.Prerequisites)
 | |
| 		{
 | |
| 			// Find the prerequisite node in our list
 | |
| 			const FSkillNodeInfo* PrereqNode = NodeInfoList.FindByPredicate(
 | |
| 				[&PrerequisiteID](const FSkillNodeInfo& Info)
 | |
| 				{ return Info.SkillID == PrerequisiteID; });
 | |
| 
 | |
| 			if (!PrereqNode)
 | |
| 				continue;
 | |
| 
 | |
| 			// Convert absolute positions to local coordinates relative to this widget
 | |
| 			FVector2D PrereqLocalPos = PrereqNode->Position - ConnectionsWidgetAbsolutePosition;
 | |
| 			FVector2D DependentLocalPos = DependentNode.Position - ConnectionsWidgetAbsolutePosition;
 | |
| 
 | |
| 			// Draw line from prerequisite to dependent
 | |
| 			TArray<FVector2D> LinePoints;
 | |
| 			LinePoints.Add(PrereqLocalPos);
 | |
| 			LinePoints.Add(DependentLocalPos);
 | |
| 
 | |
| 			FSlateDrawElement::MakeLines(OutDrawElements, MaxLayerId + 1,
 | |
| 										 AllottedGeometry.ToPaintGeometry(),
 | |
| 										 LinePoints, ESlateDrawEffect::None,
 | |
| 										 LineColor,
 | |
| 										 true, // bAntialias
 | |
| 										 LineThickness);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return MaxLayerId + 1;
 | |
| }
 |