(Feat) Add Animation Fps Charater
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "CraftingWidget.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
#include "Components/Image.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Components/UniformGridPanel.h"
|
||||
#include "CraftingComponent.h"
|
||||
#include "CraftingIngredientRowWidget.h"
|
||||
#include "CraftingRecipeSlotWidget.h"
|
||||
#include "FpsPlayer.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "ItemDataAsset.h"
|
||||
|
||||
void UCraftingWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
if (CraftButton)
|
||||
{
|
||||
CraftButton->OnClicked.AddDynamic(this, &UCraftingWidget::HandleCraftClicked);
|
||||
}
|
||||
|
||||
BindToOwningPawn();
|
||||
}
|
||||
|
||||
void UCraftingWidget::NativeDestruct()
|
||||
{
|
||||
UnbindDelegates();
|
||||
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
UCraftingComponent* UCraftingWidget::ResolveCrafting() const
|
||||
{
|
||||
const APlayerController* OwningController = GetOwningPlayer();
|
||||
AFpsPlayer* PlayerPawn = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
|
||||
return PlayerPawn ? PlayerPawn->GetCraftingComponent() : nullptr;
|
||||
}
|
||||
|
||||
void UCraftingWidget::UnbindDelegates()
|
||||
{
|
||||
if (BoundInventory.IsValid())
|
||||
{
|
||||
BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UCraftingWidget::HandleInventoryChanged);
|
||||
}
|
||||
BoundInventory.Reset();
|
||||
|
||||
if (BoundCrafting.IsValid())
|
||||
{
|
||||
BoundCrafting->OnAvailableStationsChanged.RemoveDynamic(this, &UCraftingWidget::HandleStationsChanged);
|
||||
}
|
||||
BoundCrafting.Reset();
|
||||
}
|
||||
|
||||
void UCraftingWidget::BindToOwningPawn()
|
||||
{
|
||||
UCraftingComponent* NewCrafting = ResolveCrafting();
|
||||
if (BoundCrafting.Get() == NewCrafting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UnbindDelegates();
|
||||
|
||||
BoundCrafting = NewCrafting;
|
||||
|
||||
if (BoundCrafting.IsValid())
|
||||
{
|
||||
BoundCrafting->OnAvailableStationsChanged.AddDynamic(this, &UCraftingWidget::HandleStationsChanged);
|
||||
|
||||
// L'inventaire est la vraie source des couleurs et de l'etat du bouton :
|
||||
// c'est lui qui change quand le joueur ramasse ou consomme.
|
||||
if (const AActor* Owner = BoundCrafting->GetOwner())
|
||||
{
|
||||
BoundInventory = Owner->FindComponentByClass<UInventoryComponent>();
|
||||
if (BoundInventory.IsValid())
|
||||
{
|
||||
BoundInventory->OnInventoryChanged.AddDynamic(this, &UCraftingWidget::HandleInventoryChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : aucun UCraftingComponent trouve sur le pawn possede."));
|
||||
}
|
||||
|
||||
// Les cases gardent l'ancienne recette et l'ancien parent en memoire : on
|
||||
// force leur recreation en vidant la grille.
|
||||
for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
|
||||
{
|
||||
if (SlotWidget)
|
||||
{
|
||||
SlotWidget->RemoveFromParent();
|
||||
}
|
||||
}
|
||||
RecipeSlots.Reset();
|
||||
|
||||
// Les lignes de cout aussi : elles affichent l'inventaire du pawn precedent.
|
||||
for (UCraftingIngredientRowWidget* Row : IngredientRows)
|
||||
{
|
||||
if (Row)
|
||||
{
|
||||
Row->RemoveFromParent();
|
||||
}
|
||||
}
|
||||
IngredientRows.Reset();
|
||||
|
||||
SelectedRecipe = nullptr;
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void UCraftingWidget::HandleInventoryChanged()
|
||||
{
|
||||
// Seules les couleurs et l'etat du bouton bougent : la liste des recettes,
|
||||
// elle, ne depend que des postes accessibles.
|
||||
RefreshStates();
|
||||
}
|
||||
|
||||
void UCraftingWidget::HandleStationsChanged()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void UCraftingWidget::SetStationFilter(ECraftingStation InFilter)
|
||||
{
|
||||
if (StationFilter == InFilter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StationFilter = InFilter;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void UCraftingWidget::Refresh()
|
||||
{
|
||||
if (!RecipeGrid)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget::Refresh : le widget nomme 'RecipeGrid' manque dans le Blueprint."));
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<UCraftingRecipeDataAsset*> Recipes;
|
||||
if (BoundCrafting.IsValid())
|
||||
{
|
||||
BoundCrafting->GatherRecipes(Recipes, StationFilter);
|
||||
}
|
||||
|
||||
RebuildGrid(Recipes);
|
||||
|
||||
if (EmptyGridMessage)
|
||||
{
|
||||
EmptyGridMessage->SetVisibility(Recipes.Num() > 0 ? ESlateVisibility::Collapsed : ESlateVisibility::Visible);
|
||||
}
|
||||
|
||||
// La recette choisie a pu disparaitre de la liste : on s'est eloigne de
|
||||
// l'etabli, ou le filtre a change.
|
||||
if (SelectedRecipe && !Recipes.Contains(SelectedRecipe))
|
||||
{
|
||||
SelectedRecipe = nullptr;
|
||||
}
|
||||
|
||||
RefreshStates();
|
||||
}
|
||||
|
||||
void UCraftingWidget::RebuildGrid(const TArray<UCraftingRecipeDataAsset*>& NewRecipes)
|
||||
{
|
||||
// La liste ne change qu'en s'approchant d'un poste ou en changeant de
|
||||
// filtre. Recreer les cases a chaque ramassage serait du gaspillage pur,
|
||||
// d'ou cette comparaison prealable.
|
||||
bool bSameList = RecipeSlots.Num() == NewRecipes.Num();
|
||||
if (bSameList)
|
||||
{
|
||||
for (int32 Index = 0; Index < RecipeSlots.Num(); ++Index)
|
||||
{
|
||||
if (!RecipeSlots[Index] || RecipeSlots[Index]->GetRecipe() != NewRecipes[Index])
|
||||
{
|
||||
bSameList = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bSameList)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
|
||||
{
|
||||
if (SlotWidget)
|
||||
{
|
||||
SlotWidget->RemoveFromParent();
|
||||
}
|
||||
}
|
||||
RecipeSlots.Reset();
|
||||
|
||||
if (NewRecipes.Num() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RecipeSlotClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : RecipeSlotClass n'est pas assignee, la grille restera vide."));
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 Columns = FMath::Max(1, ColumnCount);
|
||||
RecipeSlots.Reserve(NewRecipes.Num());
|
||||
|
||||
for (int32 Index = 0; Index < NewRecipes.Num(); ++Index)
|
||||
{
|
||||
UCraftingRecipeSlotWidget* NewSlot = CreateWidget<UCraftingRecipeSlotWidget>(this, RecipeSlotClass);
|
||||
if (!NewSlot)
|
||||
{
|
||||
// Arrive typiquement quand le Widget Blueprint de case ne compile
|
||||
// plus, apres un changement de BindWidget cote C++ par exemple.
|
||||
UE_LOG(LogTemp, Error, TEXT("UCraftingWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."),
|
||||
*GetNameSafe(RecipeSlotClass));
|
||||
break;
|
||||
}
|
||||
|
||||
NewSlot->InitSlot(NewRecipes[Index], this);
|
||||
RecipeGrid->AddChildToUniformGrid(NewSlot, Index / Columns, Index % Columns);
|
||||
RecipeSlots.Add(NewSlot);
|
||||
}
|
||||
}
|
||||
|
||||
void UCraftingWidget::RefreshStates()
|
||||
{
|
||||
UCraftingComponent* Crafting = BoundCrafting.Get();
|
||||
|
||||
for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
|
||||
{
|
||||
if (!SlotWidget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ECraftFailureReason Reason = ECraftFailureReason::None;
|
||||
const bool bCraftable = Crafting && Crafting->CanCraft(SlotWidget->GetRecipe(), Reason);
|
||||
|
||||
SlotWidget->SetCraftable(bCraftable);
|
||||
SlotWidget->SetSelected(SlotWidget->GetRecipe() == SelectedRecipe);
|
||||
}
|
||||
|
||||
RefreshDetails();
|
||||
}
|
||||
|
||||
void UCraftingWidget::RefreshDetails()
|
||||
{
|
||||
UCraftingComponent* Crafting = BoundCrafting.Get();
|
||||
|
||||
if (DetailsPanel)
|
||||
{
|
||||
DetailsPanel->SetVisibility(SelectedRecipe ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
|
||||
}
|
||||
|
||||
if (!SelectedRecipe)
|
||||
{
|
||||
if (CraftButton)
|
||||
{
|
||||
CraftButton->SetIsEnabled(false);
|
||||
}
|
||||
|
||||
// Le panneau est masque, mais on vide quand meme : le contenu de
|
||||
// l'ancienne recette reapparaitrait un instant a la selection suivante.
|
||||
RefreshIngredients();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ResultIcon)
|
||||
{
|
||||
if (UTexture2D* Icon = SelectedRecipe->GetIcon())
|
||||
{
|
||||
ResultIcon->SetBrushFromTexture(Icon);
|
||||
ResultIcon->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResultIcon->SetVisibility(ESlateVisibility::Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
if (ResultNameText)
|
||||
{
|
||||
ResultNameText->SetText(SelectedRecipe->GetDisplayName());
|
||||
}
|
||||
|
||||
if (ResultQuantityText)
|
||||
{
|
||||
const bool bMultiple = SelectedRecipe->ResultQuantity > 1;
|
||||
ResultQuantityText->SetVisibility(bMultiple ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
||||
if (bMultiple)
|
||||
{
|
||||
ResultQuantityText->SetText(FText::AsNumber(SelectedRecipe->ResultQuantity));
|
||||
}
|
||||
}
|
||||
|
||||
if (ResultDescriptionText)
|
||||
{
|
||||
ResultDescriptionText->SetText(SelectedRecipe->ResultItem ? SelectedRecipe->ResultItem->Description : FText::GetEmpty());
|
||||
}
|
||||
|
||||
RefreshIngredients();
|
||||
|
||||
if (CraftButton)
|
||||
{
|
||||
ECraftFailureReason Reason = ECraftFailureReason::None;
|
||||
CraftButton->SetIsEnabled(Crafting && Crafting->CanCraft(SelectedRecipe, Reason));
|
||||
}
|
||||
}
|
||||
|
||||
void UCraftingWidget::RefreshIngredients()
|
||||
{
|
||||
if (!IngredientList)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FCraftIngredientStatus> Statuses;
|
||||
if (SelectedRecipe && BoundCrafting.IsValid())
|
||||
{
|
||||
BoundCrafting->GatherIngredientStatus(SelectedRecipe, Statuses);
|
||||
}
|
||||
|
||||
// Trop de lignes : on retire le surplus par la fin.
|
||||
while (IngredientRows.Num() > Statuses.Num())
|
||||
{
|
||||
const int32 Last = IngredientRows.Num() - 1;
|
||||
if (IngredientRows[Last])
|
||||
{
|
||||
IngredientRows[Last]->RemoveFromParent();
|
||||
}
|
||||
IngredientRows.RemoveAt(Last);
|
||||
}
|
||||
|
||||
if (IngredientRows.Num() < Statuses.Num() && !IngredientRowClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : IngredientRowClass n'est pas assignee, la liste de cout restera vide."));
|
||||
}
|
||||
|
||||
// Pas assez : on en cree.
|
||||
while (IngredientRows.Num() < Statuses.Num() && IngredientRowClass)
|
||||
{
|
||||
UCraftingIngredientRowWidget* NewRow = CreateWidget<UCraftingIngredientRowWidget>(this, IngredientRowClass);
|
||||
if (!NewRow)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("UCraftingWidget : impossible de creer une ligne a partir de %s. Recompile ce Widget Blueprint."),
|
||||
*GetNameSafe(IngredientRowClass));
|
||||
break;
|
||||
}
|
||||
|
||||
IngredientList->AddChild(NewRow);
|
||||
IngredientRows.Add(NewRow);
|
||||
}
|
||||
|
||||
for (int32 Index = 0; Index < IngredientRows.Num(); ++Index)
|
||||
{
|
||||
if (IngredientRows[Index] && Statuses.IsValidIndex(Index))
|
||||
{
|
||||
IngredientRows[Index]->SetIngredient(Statuses[Index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UCraftingWidget::NotifyRecipeClicked(UCraftingRecipeDataAsset* InRecipe)
|
||||
{
|
||||
if (SelectedRecipe == InRecipe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedRecipe = InRecipe;
|
||||
RefreshStates();
|
||||
}
|
||||
|
||||
void UCraftingWidget::HandleCraftClicked()
|
||||
{
|
||||
if (!SelectedRecipe || !BoundCrafting.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Pas de re-verification ici : Craft() refait CanCraft de toute facon, et
|
||||
// dupliquer la condition finirait par la faire diverger.
|
||||
BoundCrafting->Craft(SelectedRecipe);
|
||||
|
||||
// L'inventaire diffuse son changement, donc RefreshStates est deja passe.
|
||||
// Rien a rappeler.
|
||||
}
|
||||
Reference in New Issue
Block a user