239 lines
12 KiB
C++
239 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
||
#pragma once
|
||
|
||
#include "CoreMinimal.h"
|
||
#include "Components/ActorComponent.h"
|
||
#include "Morph/MorphPresetDataAsset.h"
|
||
#include "MorphControllerComponent.generated.h"
|
||
|
||
class USkeletalMeshComponent;
|
||
class UAnimMontage;
|
||
|
||
UENUM(BlueprintType)
|
||
enum class EMorphLayerBlendMode : uint8
|
||
{
|
||
Max UMETA(DisplayName = "Max"),
|
||
Additive UMETA(DisplayName = "Additive"),
|
||
Override UMETA(DisplayName = "Override")
|
||
};
|
||
|
||
/** Active preset playback state. */
|
||
USTRUCT(BlueprintType)
|
||
struct FUNNYGIRL_API FActiveMorphPresetSlot
|
||
{
|
||
GENERATED_BODY()
|
||
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||
TObjectPtr<UMorphPresetDataAsset> Preset = nullptr;
|
||
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "1"))
|
||
float TargetWeight = 1.0f;
|
||
|
||
/** Current blended-in preset weight. */
|
||
float CurrentWeight = 0.0f;
|
||
|
||
/** Current time inside this playback instance. */
|
||
float PlaybackTime = 0.0f;
|
||
|
||
/** Remaining delay before the next loop iteration. */
|
||
float LoopDelayRemaining = 0.0f;
|
||
|
||
/** Resolved timeline length for this playback instance. */
|
||
float TimelineDuration = 0.0f;
|
||
|
||
bool IsValid() const { return Preset != nullptr && Preset->IsValidPreset(); }
|
||
};
|
||
|
||
/**
|
||
* Компонент управления морф таргетами персонажа.
|
||
* Поддерживает несколько пресетов одновременно, плавные переходы, блендинг.
|
||
*/
|
||
UCLASS(ClassGroup = (FunnyGirl), meta = (BlueprintSpawnableComponent))
|
||
class FUNNYGIRL_API UMorphControllerComponent : public UActorComponent
|
||
{
|
||
GENERATED_BODY()
|
||
|
||
public:
|
||
UMorphControllerComponent();
|
||
|
||
// --- Конфигурация ---
|
||
|
||
/** Компонент скелетного меша. Если не задан — берётся GetMesh() у Character. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller")
|
||
TObjectPtr<USkeletalMeshComponent> TargetMeshComponent;
|
||
|
||
/** Mesh that owns body animation montages. Use this when the visible girl mesh retargets/copies motion from a separate animation mesh. Falls back to Target Mesh Component when unset. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Montage", meta = (
|
||
DisplayName = "Montage Mesh Component",
|
||
ToolTip = "Skeletal mesh component where preset montages are played. Leave empty to use the morph target mesh."))
|
||
TObjectPtr<USkeletalMeshComponent> AnimationMeshComponent;
|
||
|
||
/** Automatically play a preset's optional Montage on Animation Mesh Component when the preset starts. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Montage", meta = (
|
||
DisplayName = "Play Preset Montages",
|
||
ToolTip = "When enabled, Add Preset, Play Morph Preset, and Set Active Presets play the montage stored in each preset."))
|
||
bool bPlayPresetMontages = true;
|
||
|
||
/** Speed used when blending preset weight toward the requested weight. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller", meta = (ClampMin = "0.1"))
|
||
float BlendSpeed = 4.0f;
|
||
|
||
/** Обновлять морфы каждый кадр (выключить для экономии, если управляете вручную) */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller")
|
||
bool bAutoUpdate = true;
|
||
|
||
/** Print warnings when a preset cannot play because of missing mesh, empty curves, or invalid data. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller")
|
||
bool bLogWarnings = true;
|
||
|
||
/** Keep layered presets from overdriving the face when several expressions are active at once. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Layering")
|
||
bool bNormalizePresetLayerWeights = true;
|
||
|
||
/** Maximum total expression weight shared by active presets when normalization is enabled. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Layering", meta = (ClampMin = "0", ClampMax = "5"))
|
||
float MaxCombinedPresetWeight = 1.0f;
|
||
|
||
/** Emotion presets available for AI/director prompting. Use these as the character's facial expression vocabulary. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Emotions")
|
||
TArray<TObjectPtr<UMorphPresetDataAsset>> EmotionPresets;
|
||
|
||
/** Prompt text placed before the generated emotion preset list. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Emotions", meta = (MultiLine = "true"))
|
||
FText EmotionPresetsPromptHeader;
|
||
|
||
/** Build a system prompt that explains available emotion morph presets to an LLM. */
|
||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FunnyGirl|Morph Controller|Emotions")
|
||
FString BuildEmotionPresetsPrompt() const;
|
||
|
||
/** Find an emotion preset by its Display Name. */
|
||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FunnyGirl|Morph Controller|Emotions", meta = (DisplayName = "Find Emotion Preset By Name"))
|
||
UMorphPresetDataAsset* FindEmotionPresetByName(const FString& PresetName) const;
|
||
|
||
/** Play an emotion preset from Emotion Presets by its Display Name. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Emotions", meta = (DisplayName = "Play Emotion Preset By Name"))
|
||
bool PlayEmotionPresetByName(const FString& PresetName, float Weight = 1.0f);
|
||
|
||
// --- API: Пресеты ---
|
||
|
||
/** Play or restart a preset. Looping presets stay active forever until removed. Non-looping presets are removed when playback reaches the end. Returns false if the preset cannot be played. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Add Preset"))
|
||
bool AddPreset(UMorphPresetDataAsset* Preset, float Weight = 1.0f);
|
||
|
||
/** Play or restart a preset. Alias for Add Preset with a clearer Blueprint name. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Play Morph Preset"))
|
||
bool PlayPreset(UMorphPresetDataAsset* Preset, float Weight = 1.0f);
|
||
|
||
/** Play the montage assigned inside a morph preset on the Animation Mesh Component. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Montage", meta = (DisplayName = "Play Preset Montage"))
|
||
bool PlayPresetMontage(UMorphPresetDataAsset* Preset);
|
||
|
||
/** Set the mesh component used for preset montage playback. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Montage", meta = (DisplayName = "Set Montage Mesh Component"))
|
||
void SetMontageMeshComponent(USkeletalMeshComponent* InMontageMeshComponent);
|
||
|
||
/** Get the explicit montage mesh component, or the resolved fallback mesh when none is set. */
|
||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FunnyGirl|Morph Controller|Montage", meta = (DisplayName = "Get Montage Mesh Component"))
|
||
USkeletalMeshComponent* GetMontageMeshComponent() const;
|
||
|
||
/** Remove a preset from active playback immediately. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Remove Preset"))
|
||
void RemovePreset(UMorphPresetDataAsset* Preset);
|
||
|
||
/** Установить вес пресета. Пресет должен быть уже добавлен. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Set Preset Weight"))
|
||
void SetPresetWeight(UMorphPresetDataAsset* Preset, float Weight);
|
||
|
||
/** Применить несколько пресетов сразу (заменяет текущие активные) */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Set Active Presets"))
|
||
void SetActivePresets(const TArray<FActiveMorphPresetSlot>& Presets);
|
||
|
||
/** Clear all active preset playback immediately. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Clear All Presets"))
|
||
void ClearAllPresets();
|
||
|
||
/** Проверить, активен ли пресет */
|
||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FunnyGirl|Morph Controller")
|
||
bool IsPresetActive(UMorphPresetDataAsset* Preset) const;
|
||
|
||
/** Получить текущие активные пресеты */
|
||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FunnyGirl|Morph Controller")
|
||
TArray<FActiveMorphPresetSlot> GetActivePresets() const;
|
||
|
||
// --- API: Прямое управление морфами ---
|
||
|
||
/** Установить морф таргет напрямую (добавляется к пресетам) */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Set Morph Target"))
|
||
void SetMorphTarget(FName MorphTargetName, float Value);
|
||
|
||
/** Remove one direct morph override. Preset playback can still drive this morph afterwards. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Remove Morph Target Override"))
|
||
void RemoveMorphTargetOverride(FName MorphTargetName);
|
||
|
||
/** Clear only direct morph overrides, without stopping active presets. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Clear Direct Morph Overrides"))
|
||
void ClearDirectMorphOverrides();
|
||
|
||
// --- API: Lip sync layer ---
|
||
|
||
/** Enable applying transient lip sync morph values on top of active emotion presets. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Lip Sync")
|
||
bool bEnableLipSyncLayer = true;
|
||
|
||
/** How lip sync morphs are mixed with emotion presets and direct overrides. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Lip Sync")
|
||
EMorphLayerBlendMode LipSyncBlendMode = EMorphLayerBlendMode::Max;
|
||
|
||
/** Overall multiplier for lip sync values before they are mixed into the final morph set. */
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Lip Sync", meta = (ClampMin = "0", ClampMax = "2"))
|
||
float LipSyncLayerWeight = 1.0f;
|
||
|
||
/** Set transient lip sync morph values. These values are blended during the next morph update. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Lip Sync", meta = (DisplayName = "Set Lip Sync Morph Values"))
|
||
void SetLipSyncMorphValues(const TMap<FName, float>& MorphValues);
|
||
|
||
/** Clear transient lip sync morph values without stopping emotion presets. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Lip Sync", meta = (DisplayName = "Clear Lip Sync Morph Values"))
|
||
void ClearLipSyncMorphValues();
|
||
|
||
/** Update morph targets immediately. Useful when Auto Update is disabled. */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Update Morph Targets Now"))
|
||
void UpdateMorphTargetsNow(float DeltaTime = 0.0f);
|
||
|
||
/** Очистить все морфы мгновенно */
|
||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Clear Morphs Immediately"))
|
||
void ClearMorphsImmediately();
|
||
|
||
// --- Внутреннее ---
|
||
|
||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||
|
||
protected:
|
||
virtual void BeginPlay() override;
|
||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||
|
||
private:
|
||
UPROPERTY()
|
||
TArray<FActiveMorphPresetSlot> ActivePresets;
|
||
|
||
/** Прямые значения морфов (дополнительно к пресетам) */
|
||
UPROPERTY()
|
||
TMap<FName, float> DirectMorphOverrides;
|
||
|
||
UPROPERTY()
|
||
TMap<FName, float> LipSyncMorphValues;
|
||
|
||
UPROPERTY()
|
||
TSet<FName> LastAppliedMorphNames;
|
||
|
||
bool bMissingMeshWarningIssued = false;
|
||
|
||
USkeletalMeshComponent* ResolveMeshComponent() const;
|
||
USkeletalMeshComponent* ResolveAnimationMeshComponent() const;
|
||
bool PresetHasAnyPositiveCurveValue(const UMorphPresetDataAsset& Preset) const;
|
||
void UpdateMorphTargets(float DeltaTime);
|
||
void BlendMorphLayer(const TMap<FName, float>& LayerValues, float LayerWeight, EMorphLayerBlendMode BlendMode, TMap<FName, float>& InOutFinalValues) const;
|
||
void ApplyFinalMorphValues(const TMap<FName, float>& FinalValues);
|
||
};
|