Files
FunnyGirl/Source/FunnyGirl/Public/Morph/MorphControllerComponent.h
T
Ivan Anikin 46cf6a4396 Refactoring
2026-05-21 12:02:28 +07:00

239 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
};