// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "CharacterAppearanceTypes.h" #include "CharacterPreviewActor.h" #include "CharacterCustomizationWidget.generated.h" class ACharacterPreviewActor; class UButton; class UCharacterPartsDataAsset; class UCustomizationCategoryWidget; class UCustomizationOptionWidget; class UPanelWidget; DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCustomizationConfirmed); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCustomizationCancelled); /** * Ce que le joueur peut regler, dans l'ordre d'affichage de la colonne. * * Pieces et couleurs sont dans le MEME enum : ce sont onze rangees de la meme * colonne, et les distinguer par deux listes obligerait a dessiner deux fois la * meme chose. La difference ne vit que dans BuildOptions, qui remplit la grille * de vignettes ou de pastilles selon le cas. */ UENUM(BlueprintType) enum class ECustomizationCategory : uint8 { Body, Head, Hair, Eyebrows, Beard, Mustache, Eyes, SkinColor, HairColor, EyeColor, UnderwearColor, Count UMETA(Hidden) }; /** * L'ecran de customisation : la colonne de reglages a gauche, le personnage a * droite. * * Le widget ne monte rien lui-meme -- il pousse une FCharacterAppearance sur * ACharacterPreviewActor et lit le catalogue pour savoir quoi proposer. C'est * le meme partage des roles que partout ailleurs dans le projet : l'interface * affiche et demande, le systeme decide. * * Classe Abstract : on cree un WBP_CharacterCustomization qui en herite. */ UCLASS(Abstract) class SURVIVAL_PROJET_API UCharacterCustomizationWidget : public UUserWidget { GENERATED_BODY() public: UCharacterCustomizationWidget(const FObjectInitializer& ObjectInitializer); virtual void NativeOnInitialized() override; virtual void NativeConstruct() override; /** * Echap = Retour, comme le bouton. * * Obligatoire ici et pas seulement confortable : dans la map du menu, le * controller ne binde AUCUNE action d'input. Tout Echap qui fonctionne passe * donc par le NativeOnKeyDown d'un widget -- il n'y a pas d'autre chemin. */ virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override; /** Le joueur valide : l'ecran a fini, l'apparence est dans GetAppearance(). */ UPROPERTY(BlueprintAssignable, Category = "Customization") FOnCustomizationConfirmed OnConfirmed; /** Le joueur renonce : rien n'a ete valide, on rend la main au menu. */ UPROPERTY(BlueprintAssignable, Category = "Customization") FOnCustomizationCancelled OnCancelled; /** * Branche l'ecran sur son mannequin et son point de depart. * * A appeler AVANT d'ajouter le widget au viewport : NativeConstruct * reconstruit les listes, et il les batirait vides sans catalogue. * * Surtout PAS nommee Initialize : UUserWidget en a deja une, virtuelle, et * une surcharge de signature differente la masquerait au lieu de la * surcharger (C4263/C4264). Meme famille de piege que les membres nommes * Player ou Slot dans une classe derivee du moteur. */ UFUNCTION(BlueprintCallable, Category = "Customization") void SetupCustomization(ACharacterPreviewActor* InPreview, const FCharacterAppearance& InAppearance); UFUNCTION(BlueprintPure, Category = "Customization") const FCharacterAppearance& GetAppearance() const { return Appearance; } protected: /** * Le catalogue. Assigne-lui le meme DA_CharacterParts que BP_FpsPlayer. * * Sur le widget et pas pris sur le mannequin : l'ecran doit pouvoir batir * ses listes meme si le mannequin manque encore, et un catalogue different * des deux cotes se verrait immediatement a l'ecran. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Customization") TObjectPtr Catalog; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Customization") TSubclassOf CategoryWidgetClass; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Customization") TSubclassOf OptionWidgetClass; /** La colonne des categories. Un Vertical Box nomme exactement ainsi. */ UPROPERTY(meta = (BindWidget)) TObjectPtr CategoryList; /** La grille d'options. Un Wrap Box va tres bien. */ UPROPERTY(meta = (BindWidget)) TObjectPtr OptionGrid; /** Valide et lance la partie. */ UPROPERTY(meta = (BindWidget)) TObjectPtr PlayButton; /** Retour au menu principal, sans rien valider. */ UPROPERTY(meta = (BindWidget)) TObjectPtr BackButton; // ------------------------------------------------------------------ // Rotation du personnage au clic-glisser // ------------------------------------------------------------------ virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override; private: /** Reconstruit la colonne de gauche. Une seule fois, les categories sont fixes. */ void BuildCategories(); /** Reconstruit la grille selon la categorie active. */ void BuildOptions(); /** Repose l'apparence sur le mannequin et rafraichit la selection affichee. */ void RefreshPreview(); /** Encadre la case qui porte le choix courant, efface les autres. */ void RefreshSelection(); /** L'index courant dans la categorie active, pour l'encadrement. */ int32 GetCurrentIndexForCategory(ECustomizationCategory Category) const; /** Ecrit un choix dans l'apparence. Le seul endroit qui la modifie. */ void SetIndexForCategory(ECustomizationCategory Category, int32 NewIndex); /** Combien d'options a proposer, catalogue en main. */ int32 GetOptionCountForCategory(ECustomizationCategory Category) const; UFUNCTION() void HandleCategoryClicked(int32 CategoryIndex); UFUNCTION() void HandleOptionClicked(int32 OptionIndex); UFUNCTION() void HandlePlayClicked(); UFUNCTION() void HandleBackClicked(); UPROPERTY(Transient) TObjectPtr Preview; UPROPERTY(Transient) TArray> CategoryWidgets; UPROPERTY(Transient) TArray> OptionWidgets; FCharacterAppearance Appearance; ECustomizationCategory ActiveCategory = ECustomizationCategory::Body; /** Vrai entre l'appui et le relachement, pendant qu'on fait tourner. */ bool bDragging = false; };