Refactoring
This commit is contained in:
@@ -8,7 +8,7 @@ public class FunnyGirl : ModuleRules
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "HTTP", "Json", "JsonUtilities", "AssetRegistry", "UMG" });
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "HTTP", "Json", "JsonUtilities", "AssetRegistry", "MediaAssets", "UMG" });
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||
|
||||
|
||||
@@ -169,6 +169,87 @@ bool UPersonaBrainComponent::GetLastSavedLLMMessage(FString& OutMessage) const
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FPersonaChatMessage> UPersonaBrainComponent::GetSavedChatMessages() const
|
||||
{
|
||||
TArray<FPersonaChatMessage> ChatMessages;
|
||||
|
||||
const FString FilePath = GetChatFilePath();
|
||||
FString ChatJson;
|
||||
if (!FFileHelper::LoadFileToString(ChatJson, *FilePath))
|
||||
{
|
||||
return ChatMessages;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> RootObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ChatJson);
|
||||
if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
|
||||
{
|
||||
return ChatMessages;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* SavedMessages = nullptr;
|
||||
if (!RootObject->TryGetArrayField(TEXT("messages"), SavedMessages) || !SavedMessages)
|
||||
{
|
||||
return ChatMessages;
|
||||
}
|
||||
|
||||
const FString GirlSpeaker = Identity && !Identity->Name.IsEmpty() && !Identity->Name.ToString().TrimStartAndEnd().IsEmpty()
|
||||
? Identity->Name.ToString().TrimStartAndEnd()
|
||||
: FString(TEXT("Girl"));
|
||||
|
||||
for (const TSharedPtr<FJsonValue>& MessageValue : *SavedMessages)
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* MessageObject = nullptr;
|
||||
if (!MessageValue.IsValid() || !MessageValue->TryGetObject(MessageObject) || !MessageObject || !MessageObject->IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FString Role;
|
||||
FString Content;
|
||||
if (!(*MessageObject)->TryGetStringField(TEXT("role"), Role) || !(*MessageObject)->TryGetStringField(TEXT("content"), Content))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((Role != TEXT("user") && Role != TEXT("assistant")) || Content.TrimStartAndEnd().IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FPersonaChatMessage ChatMessage;
|
||||
(*MessageObject)->TryGetStringField(TEXT("timestamp"), ChatMessage.Timestamp);
|
||||
ChatMessage.Role = Role;
|
||||
ChatMessage.Speaker = Role == TEXT("assistant") ? GirlSpeaker : FString(TEXT("Player"));
|
||||
ChatMessage.Content = Content;
|
||||
|
||||
const TSharedPtr<FJsonValue> InteractActionValue = (*MessageObject)->TryGetField(TEXT("interact_action"));
|
||||
if (InteractActionValue.IsValid() && InteractActionValue->Type != EJson::Null)
|
||||
{
|
||||
if (InteractActionValue->Type == EJson::Object)
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* InteractActionObject = nullptr;
|
||||
if (InteractActionValue->TryGetObject(InteractActionObject) && InteractActionObject && InteractActionObject->IsValid())
|
||||
{
|
||||
FString InteractActionJson;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&InteractActionJson);
|
||||
FJsonSerializer::Serialize(InteractActionObject->ToSharedRef(), Writer);
|
||||
ChatMessage.InteractActionJson = InteractActionJson;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InteractActionValue->TryGetString(ChatMessage.InteractActionJson);
|
||||
}
|
||||
}
|
||||
|
||||
(*MessageObject)->TryGetStringField(TEXT("emotion"), ChatMessage.Emotion);
|
||||
ChatMessages.Add(ChatMessage);
|
||||
}
|
||||
|
||||
return ChatMessages;
|
||||
}
|
||||
|
||||
void UPersonaBrainComponent::BroadcastBrainError(const FString& ErrorMessage) const
|
||||
{
|
||||
OnLLMError.Broadcast(ErrorMessage);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "Morph/MorphControllerComponent.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Engine/World.h"
|
||||
@@ -37,6 +35,12 @@ void UMorphControllerComponent::BeginPlay()
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
ClearMorphsImmediately();
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
@@ -212,7 +216,6 @@ bool UMorphControllerComponent::AddPreset(UMorphPresetDataAsset* Preset, float W
|
||||
if (Slot.Preset == Preset)
|
||||
{
|
||||
Slot.TargetWeight = FMath::Clamp(Weight, 0.0f, 1.0f);
|
||||
Slot.CurrentWeight = Slot.TargetWeight;
|
||||
Slot.PlaybackTime = 0.0f;
|
||||
Slot.LoopDelayRemaining = 0.0f;
|
||||
Slot.TimelineDuration = TimelineDuration;
|
||||
@@ -224,7 +227,7 @@ bool UMorphControllerComponent::AddPreset(UMorphPresetDataAsset* Preset, float W
|
||||
FActiveMorphPresetSlot NewSlot;
|
||||
NewSlot.Preset = Preset;
|
||||
NewSlot.TargetWeight = FMath::Clamp(Weight, 0.0f, 1.0f);
|
||||
NewSlot.CurrentWeight = NewSlot.TargetWeight;
|
||||
NewSlot.CurrentWeight = 0.0f;
|
||||
NewSlot.PlaybackTime = 0.0f;
|
||||
NewSlot.LoopDelayRemaining = 0.0f;
|
||||
NewSlot.TimelineDuration = TimelineDuration;
|
||||
@@ -239,78 +242,32 @@ bool UMorphControllerComponent::PlayPreset(UMorphPresetDataAsset* Preset, float
|
||||
{
|
||||
if (bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("PlayPreset ignored on %s: preset is null or has no morph clips or animation montage."), *GetNameSafe(GetOwner()));
|
||||
UE_LOG(LogMorphController, Warning, TEXT("PlayPreset ignored on %s: preset is null or has no morph clips."), *GetNameSafe(GetOwner()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool bPlayedMorphs = Preset->IsValidPreset() ? AddPreset(Preset, Weight) : false;
|
||||
const bool bPlayedAnimation = PlayPresetAnimation(Preset);
|
||||
return bPlayedMorphs || bPlayedAnimation;
|
||||
}
|
||||
|
||||
bool UMorphControllerComponent::PlayPresetAnimation(UMorphPresetDataAsset* Preset)
|
||||
{
|
||||
if (!Preset || !Preset->AnimationMontage)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* Mesh = ResolveMeshComponent();
|
||||
if (!Mesh)
|
||||
{
|
||||
if (bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("Preset animation ignored on %s: no SkeletalMeshComponent was found."), *GetNameSafe(GetOwner()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UAnimInstance* AnimInstance = Mesh->GetAnimInstance();
|
||||
if (!AnimInstance)
|
||||
{
|
||||
if (bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("Preset animation ignored on %s: mesh %s has no AnimInstance."), *GetNameSafe(GetOwner()), *GetNameSafe(Mesh));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UAnimMontage* Montage = Preset->AnimationMontage;
|
||||
if (Preset->bRestartAnimationOnReplay && AnimInstance->Montage_IsPlaying(Montage))
|
||||
{
|
||||
AnimInstance->Montage_Stop(0.0f, Montage);
|
||||
}
|
||||
|
||||
const float MontageLength = AnimInstance->Montage_Play(Montage, FMath::Max(Preset->AnimationPlayRate, 0.01f));
|
||||
if (MontageLength <= 0.0f)
|
||||
{
|
||||
if (bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("Preset animation failed on %s: montage %s could not play. Check the Anim Blueprint slot setup."), *GetNameSafe(GetOwner()), *GetNameSafe(Montage));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Preset->AnimationStartSection.IsNone())
|
||||
{
|
||||
AnimInstance->Montage_JumpToSection(Preset->AnimationStartSection, Montage);
|
||||
}
|
||||
|
||||
return true;
|
||||
return AddPreset(Preset, Weight);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::RemovePreset(UMorphPresetDataAsset* Preset)
|
||||
{
|
||||
if (!Preset) return;
|
||||
|
||||
bool bRemovedAny = false;
|
||||
for (int32 i = ActivePresets.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
if (ActivePresets[i].Preset == Preset)
|
||||
{
|
||||
ActivePresets.RemoveAt(i);
|
||||
bRemovedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bRemovedAny)
|
||||
{
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::SetPresetWeight(UMorphPresetDataAsset* Preset, float Weight)
|
||||
@@ -336,6 +293,7 @@ void UMorphControllerComponent::SetActivePresets(const TArray<FActiveMorphPreset
|
||||
if (Slot.Preset && Slot.Preset->IsValidPreset())
|
||||
{
|
||||
FActiveMorphPresetSlot NewSlot = Slot;
|
||||
NewSlot.TargetWeight = FMath::Clamp(NewSlot.TargetWeight, 0.0f, 1.0f);
|
||||
NewSlot.CurrentWeight = 0.0f;
|
||||
NewSlot.PlaybackTime = 0.0f;
|
||||
NewSlot.LoopDelayRemaining = 0.0f;
|
||||
@@ -346,11 +304,14 @@ void UMorphControllerComponent::SetActivePresets(const TArray<FActiveMorphPreset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::ClearAllPresets()
|
||||
{
|
||||
ActivePresets.Empty();
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
|
||||
bool UMorphControllerComponent::IsPresetActive(UMorphPresetDataAsset* Preset) const
|
||||
@@ -382,17 +343,27 @@ TArray<FActiveMorphPresetSlot> UMorphControllerComponent::GetActivePresets() con
|
||||
|
||||
void UMorphControllerComponent::SetMorphTarget(FName MorphTargetName, float Value)
|
||||
{
|
||||
if (MorphTargetName.IsNone())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DirectMorphOverrides.Add(MorphTargetName, ClampMorphTargetValue(Value));
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::RemoveMorphTargetOverride(FName MorphTargetName)
|
||||
{
|
||||
DirectMorphOverrides.Remove(MorphTargetName);
|
||||
if (DirectMorphOverrides.Remove(MorphTargetName) > 0)
|
||||
{
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::ClearDirectMorphOverrides()
|
||||
{
|
||||
DirectMorphOverrides.Empty();
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::SetLipSyncMorphValues(const TMap<FName, float>& MorphValues)
|
||||
@@ -405,11 +376,13 @@ void UMorphControllerComponent::SetLipSyncMorphValues(const TMap<FName, float>&
|
||||
LipSyncMorphValues.Add(Pair.Key, ClampMorphTargetValue(Pair.Value));
|
||||
}
|
||||
}
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::ClearLipSyncMorphValues()
|
||||
{
|
||||
LipSyncMorphValues.Empty();
|
||||
UpdateMorphTargets(0.0f);
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::UpdateMorphTargetsNow(float DeltaTime)
|
||||
@@ -501,6 +474,19 @@ void UMorphControllerComponent::UpdateMorphTargets(float DeltaTime)
|
||||
|
||||
// 2. Gather final morph values. Overlapping clips use the strongest contribution.
|
||||
TMap<FName, float> FinalValues;
|
||||
float ActivePresetWeightSum = 0.0f;
|
||||
for (const FActiveMorphPresetSlot& Slot : ActivePresets)
|
||||
{
|
||||
if (Slot.Preset && Slot.CurrentWeight >= 0.001f && Slot.LoopDelayRemaining <= 0.0f)
|
||||
{
|
||||
ActivePresetWeightSum += Slot.CurrentWeight;
|
||||
}
|
||||
}
|
||||
|
||||
const float PresetLayerScale = bNormalizePresetLayerWeights && MaxCombinedPresetWeight > 0.0f && ActivePresetWeightSum > MaxCombinedPresetWeight
|
||||
? MaxCombinedPresetWeight / ActivePresetWeightSum
|
||||
: 1.0f;
|
||||
|
||||
for (const FActiveMorphPresetSlot& Slot : ActivePresets)
|
||||
{
|
||||
if (!Slot.Preset || Slot.CurrentWeight < 0.001f || Slot.LoopDelayRemaining > 0.0f)
|
||||
@@ -508,7 +494,7 @@ void UMorphControllerComponent::UpdateMorphTargets(float DeltaTime)
|
||||
continue;
|
||||
}
|
||||
|
||||
MorphPresetRuntime::AccumulatePresetMorphValuesAtTime(*Slot.Preset, Slot.PlaybackTime, Slot.CurrentWeight, FinalValues);
|
||||
MorphPresetRuntime::AccumulatePresetMorphValuesAtTime(*Slot.Preset, Slot.PlaybackTime, Slot.CurrentWeight * PresetLayerScale, FinalValues);
|
||||
}
|
||||
|
||||
// 3. Apply direct overrides with the same max-based priority rule.
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "Misc/Base64.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "StreamMediaSource.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -358,3 +360,15 @@ bool UFunnyGirlUtilityBlueprintLibrary::DeleteLightString(const FString& Field,
|
||||
SaveObject->RemoveField(EncodedField);
|
||||
return SaveLightSaveObject(SaveObject.ToSharedRef(), OutError);
|
||||
}
|
||||
|
||||
bool UFunnyGirlUtilityBlueprintLibrary::OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url)
|
||||
{
|
||||
if (!MediaPlayer || !StreamMediaSource || !Url.Contains(TEXT("://")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StreamMediaSource->StreamUrl = Url;
|
||||
MediaPlayer->Close();
|
||||
return MediaPlayer->OpenSource(StreamMediaSource);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,30 @@ struct FUNNYGIRL_API FLLMReplyResponse
|
||||
FString Emotion;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FUNNYGIRL_API FPersonaChatMessage
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History")
|
||||
FString Timestamp;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History")
|
||||
FString Role;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History")
|
||||
FString Speaker;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History")
|
||||
FString Content;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History|Interaction")
|
||||
FString InteractActionJson;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LLM|History|Emotion")
|
||||
FString Emotion;
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLLMResponseDelegate, const FLLMReplyResponse&, Response);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLLMReplyStreamChunkDelegate, const FString&, ReplyChunk);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLLMErrorDelegate, const FString&, ErrorMessage);
|
||||
@@ -85,6 +109,9 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "LLM|History")
|
||||
bool GetLastSavedLLMMessage(FString& OutMessage) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "LLM|History")
|
||||
TArray<FPersonaChatMessage> GetSavedChatMessages() const;
|
||||
|
||||
virtual void CancelRequest() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -74,6 +74,14 @@ public:
|
||||
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;
|
||||
@@ -178,6 +186,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
@@ -197,7 +206,6 @@ private:
|
||||
|
||||
USkeletalMeshComponent* ResolveMeshComponent() const;
|
||||
bool PresetHasAnyPositiveCurveValue(const UMorphPresetDataAsset& Preset) const;
|
||||
bool PlayPresetAnimation(UMorphPresetDataAsset* Preset);
|
||||
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);
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "MorphPresetDataAsset.generated.h"
|
||||
|
||||
class UAnimMontage;
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FUNNYGIRL_API FMorphTimelineClip
|
||||
{
|
||||
@@ -77,26 +75,6 @@ public:
|
||||
ClampMin = "0"))
|
||||
float BlendSpeedOverride = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = (
|
||||
DisplayName = "Animation Montage",
|
||||
ToolTip = "Optional animation montage played when this preset starts. The montage must use a slot that is present in the character Anim Blueprint."))
|
||||
TObjectPtr<UAnimMontage> AnimationMontage = nullptr;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = (
|
||||
DisplayName = "Animation Play Rate",
|
||||
ClampMin = "0.01"))
|
||||
float AnimationPlayRate = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = (
|
||||
DisplayName = "Start Section",
|
||||
ToolTip = "Optional montage section to jump to after playback starts."))
|
||||
FName AnimationStartSection;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Animation", meta = (
|
||||
DisplayName = "Restart Animation On Replay",
|
||||
ToolTip = "If enabled, an already playing montage is stopped and restarted when this preset is triggered again."))
|
||||
bool bRestartAnimationOnReplay = true;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset")
|
||||
int32 GetMorphCount() const;
|
||||
|
||||
@@ -104,10 +82,7 @@ public:
|
||||
float GetTimelineLength() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset")
|
||||
bool HasAnimation() const { return AnimationMontage != nullptr; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset")
|
||||
bool HasPlayableContent() const { return IsValidPreset() || HasAnimation(); }
|
||||
bool HasPlayableContent() const { return IsValidPreset(); }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset")
|
||||
bool IsValidPreset() const { return GetMorphCount() > 0; }
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
class USkeletalMesh;
|
||||
class UButton;
|
||||
class UMediaPlayer;
|
||||
class UStreamMediaSource;
|
||||
class UTexture2D;
|
||||
|
||||
UENUM(BlueprintType)
|
||||
@@ -53,4 +55,7 @@ public:
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Save")
|
||||
static bool DeleteLightString(const FString& Field, FString& OutError);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Media")
|
||||
static bool OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url);
|
||||
};
|
||||
|
||||
@@ -64,10 +64,6 @@ namespace
|
||||
TargetPreset.bLoop = SourcePreset.bLoop;
|
||||
TargetPreset.LoopDelay = SourcePreset.LoopDelay;
|
||||
TargetPreset.BlendSpeedOverride = SourcePreset.BlendSpeedOverride;
|
||||
TargetPreset.AnimationMontage = SourcePreset.AnimationMontage;
|
||||
TargetPreset.AnimationPlayRate = SourcePreset.AnimationPlayRate;
|
||||
TargetPreset.AnimationStartSection = SourcePreset.AnimationStartSection;
|
||||
TargetPreset.bRestartAnimationOnReplay = SourcePreset.bRestartAnimationOnReplay;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
#include "Animation/AnimData/IAnimationDataController.h"
|
||||
#include "AnimPreviewInstance.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Animation/AnimSequence.h"
|
||||
#include "Animation/AnimSequenceBase.h"
|
||||
#include "Animation/AnimationAsset.h"
|
||||
#include "Animation/DebugSkelMeshComponent.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
@@ -115,15 +112,7 @@ float FMorphPresetEditorController::GetTimelineLength() const
|
||||
{
|
||||
if (const UMorphPresetDataAsset* PresetAsset = Preset.Get())
|
||||
{
|
||||
float TimelineLength = PresetAsset->GetTimelineLength();
|
||||
if (PresetAsset->AnimationMontage)
|
||||
{
|
||||
const float PlayRate = FMath::Max(PresetAsset->AnimationPlayRate, 0.01f);
|
||||
const UAnimSequenceBase* PreviewSequence = PresetAsset->AnimationMontage->GetFirstAnimReference();
|
||||
const float AnimationLength = PreviewSequence ? PreviewSequence->GetPlayLength() : PresetAsset->AnimationMontage->GetPlayLength();
|
||||
TimelineLength = FMath::Max(TimelineLength, AnimationLength / PlayRate);
|
||||
}
|
||||
return FMath::Max(TimelineLength, 0.1f);
|
||||
return FMath::Max(PresetAsset->GetTimelineLength(), 0.1f);
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
@@ -145,7 +134,7 @@ void FMorphPresetEditorController::SetCurrentTime(float InTime)
|
||||
if (PreviewMeshComponent->PreviewInstance)
|
||||
{
|
||||
PreviewMeshComponent->PreviewInstance->SetPlaying(false);
|
||||
PreviewMeshComponent->PreviewInstance->SetPosition(GetPreviewAnimationPosition(), false);
|
||||
PreviewMeshComponent->PreviewInstance->SetPosition(CurrentTime, false);
|
||||
RefreshPreviewAnimationPose();
|
||||
}
|
||||
}
|
||||
@@ -225,20 +214,21 @@ void FMorphPresetEditorController::NotifyPresetEdited(bool bBroadcastPresetChang
|
||||
PresetAsset->MarkPackageDirty();
|
||||
}
|
||||
|
||||
UAnimationAsset* DesiredPreviewAsset = ResolvePreviewAnimationAsset();
|
||||
UAnimationAsset* CurrentPreviewAsset = nullptr;
|
||||
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene())
|
||||
if (ActivePreviewMesh.Get() != GetPreviewMesh())
|
||||
{
|
||||
CurrentPreviewAsset = PreviewScene->GetPreviewAnimationAsset();
|
||||
RebuildPreviewScene();
|
||||
if (bBroadcastPresetChanged)
|
||||
{
|
||||
PresetChanged.Broadcast();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DesiredPreviewAsset != CurrentPreviewAsset ||
|
||||
(DesiredPreviewAsset && PreviewAnimation.IsValid()) ||
|
||||
(!DesiredPreviewAsset && !PreviewAnimation.IsValid()))
|
||||
if (!PreviewAnimation.IsValid())
|
||||
{
|
||||
RebuildPreviewAnimation();
|
||||
}
|
||||
else if (PreviewAnimation.IsValid())
|
||||
else
|
||||
{
|
||||
SetAnimSequencePlayLength(*PreviewAnimation, GetTimelineLength(), false);
|
||||
}
|
||||
@@ -436,6 +426,7 @@ void FMorphPresetEditorController::RebuildPreviewScene()
|
||||
PreviewAnimation.Reset();
|
||||
|
||||
USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
ActivePreviewMesh = PreviewMesh;
|
||||
if (PreviewMesh == nullptr)
|
||||
{
|
||||
PreviewSceneChanged.Broadcast();
|
||||
@@ -464,56 +455,29 @@ void FMorphPresetEditorController::RebuildPreviewAnimation()
|
||||
PreviewAnimation.Reset();
|
||||
PreviewStatusText = FText::GetEmpty();
|
||||
|
||||
UAnimationAsset* PreviewAsset = ResolvePreviewAnimationAsset();
|
||||
if (PreviewAsset && !IsPreviewAnimationCompatible(PreviewAsset))
|
||||
PreviewAnimation = TStrongObjectPtr<UAnimSequence>(AnimSequence);
|
||||
SetAnimSequencePlayLength(*AnimSequence, GetTimelineLength(), false);
|
||||
if (USkeletalMesh* PreviewMesh = GetPreviewMesh())
|
||||
{
|
||||
const USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
const FString PreviewMeshSkeletonName = PreviewMesh && PreviewMesh->GetSkeleton()
|
||||
? PreviewMesh->GetSkeleton()->GetPathName()
|
||||
: TEXT("None");
|
||||
const FString AnimationSkeletonName = PreviewAsset->GetSkeleton()
|
||||
? PreviewAsset->GetSkeleton()->GetPathName()
|
||||
: TEXT("None");
|
||||
|
||||
PreviewStatusText = FText::FromString(FString::Printf(
|
||||
TEXT("Animation preview is disabled: montage sequence '%s' uses skeleton '%s', but Preview Mesh uses '%s'. Retarget the animation to the Preview Mesh skeleton."),
|
||||
*GetNameSafe(PreviewAsset),
|
||||
*AnimationSkeletonName,
|
||||
*PreviewMeshSkeletonName));
|
||||
UE_LOG(LogMorphPresetEditorPreview, Warning, TEXT("%s"), *PreviewStatusText.ToString());
|
||||
PreviewAsset = nullptr;
|
||||
}
|
||||
|
||||
if (!PreviewAsset)
|
||||
{
|
||||
PreviewAnimation = TStrongObjectPtr<UAnimSequence>(AnimSequence);
|
||||
PreviewAsset = AnimSequence;
|
||||
|
||||
SetAnimSequencePlayLength(*AnimSequence, GetTimelineLength(), false);
|
||||
if (USkeletalMesh* PreviewMesh = GetPreviewMesh())
|
||||
if (USkeleton* Skeleton = PreviewMesh->GetSkeleton())
|
||||
{
|
||||
if (USkeleton* Skeleton = PreviewMesh->GetSkeleton())
|
||||
{
|
||||
AnimSequence->SetSkeleton(Skeleton);
|
||||
}
|
||||
AnimSequence->SetSkeleton(Skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
PreviewScene->SetPreviewAnimationAsset(PreviewAsset, true);
|
||||
PreviewScene->SetPreviewAnimationAsset(AnimSequence, true);
|
||||
if (UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent())
|
||||
{
|
||||
PreviewMeshComponent->SetAnimationMode(EAnimationMode::AnimationSingleNode);
|
||||
if (PreviewMeshComponent->PreviewInstance)
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
const float PlayRate = PresetAsset ? FMath::Max(PresetAsset->AnimationPlayRate, 0.01f) : 1.0f;
|
||||
PreviewMeshComponent->PreviewInstance->SetAnimationAsset(PreviewAsset, false, PlayRate);
|
||||
PreviewMeshComponent->PreviewInstance->SetAnimationAsset(AnimSequence, false, 1.0f);
|
||||
PreviewMeshComponent->PreviewInstance->EnableControllers(true);
|
||||
PreviewMeshComponent->PreviewInstance->SetSkeletalControlAlpha(1.0f);
|
||||
PreviewMeshComponent->PreviewInstance->SetPlaying(false);
|
||||
PreviewMeshComponent->PreviewInstance->SetLooping(false);
|
||||
|
||||
PreviewMeshComponent->PreviewInstance->SetPosition(GetPreviewAnimationPosition(), false);
|
||||
PreviewMeshComponent->PreviewInstance->SetPosition(CurrentTime, false);
|
||||
RefreshPreviewAnimationPose();
|
||||
}
|
||||
}
|
||||
@@ -539,52 +503,6 @@ void FMorphPresetEditorController::RefreshPreviewAnimationPose()
|
||||
PreviewScene->InvalidateViews();
|
||||
}
|
||||
|
||||
UAnimationAsset* FMorphPresetEditorController::ResolvePreviewAnimationAsset() const
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
if (PresetAsset && PresetAsset->AnimationMontage)
|
||||
{
|
||||
if (UAnimSequenceBase* FirstAnimReference = PresetAsset->AnimationMontage->GetFirstAnimReference())
|
||||
{
|
||||
return FirstAnimReference;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
float FMorphPresetEditorController::GetPreviewAnimationPosition() const
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
UAnimationAsset* PreviewAsset = ResolvePreviewAnimationAsset();
|
||||
if (!PresetAsset || !PreviewAsset)
|
||||
{
|
||||
return CurrentTime;
|
||||
}
|
||||
|
||||
const float PlayRate = FMath::Max(PresetAsset->AnimationPlayRate, 0.01f);
|
||||
float PreviewPosition = CurrentTime * PlayRate;
|
||||
if (const UAnimSequenceBase* PreviewSequence = Cast<UAnimSequenceBase>(PreviewAsset))
|
||||
{
|
||||
return FMath::Clamp(PreviewPosition, 0.0f, PreviewSequence->GetPlayLength());
|
||||
}
|
||||
|
||||
return PreviewPosition;
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::IsPreviewAnimationCompatible(UAnimationAsset* PreviewAsset) const
|
||||
{
|
||||
if (!PreviewAsset)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
const USkeleton* PreviewSkeleton = PreviewMesh ? PreviewMesh->GetSkeleton() : nullptr;
|
||||
const USkeleton* AnimationSkeleton = PreviewAsset->GetSkeleton();
|
||||
return PreviewSkeleton && AnimationSkeleton && PreviewSkeleton->IsCompatibleForEditor(AnimationSkeleton);
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::ApplyPresetToPreview()
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
#include "PropertyCustomizationHelpers.h"
|
||||
#include "SEditorViewport.h"
|
||||
#include "Sound/SoundBase.h"
|
||||
#include "ToolMenus.h"
|
||||
#include "Toolkits/AssetEditorToolkit.h"
|
||||
#include "ViewportToolbar/UnrealEdViewportToolbar.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Input/SEditableTextBox.h"
|
||||
#include "Widgets/Input/SSlider.h"
|
||||
@@ -118,11 +120,13 @@ namespace
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMorphPresetPreviewViewport) {}
|
||||
SLATE_ARGUMENT(TSharedPtr<FMorphPresetEditorController>, Controller)
|
||||
SLATE_ARGUMENT(TWeakPtr<FAssetEditorToolkit>, AssetEditorToolkit)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs)
|
||||
{
|
||||
Controller = InArgs._Controller;
|
||||
AssetEditorToolkit = InArgs._AssetEditorToolkit;
|
||||
SEditorViewport::Construct(SEditorViewport::FArguments());
|
||||
}
|
||||
|
||||
@@ -146,11 +150,45 @@ namespace
|
||||
|
||||
virtual TSharedPtr<SWidget> BuildViewportToolbar() override
|
||||
{
|
||||
return SNullWidget::NullWidget;
|
||||
const FName ViewportToolbarName = TEXT("MorphPresetEditor.ViewportToolbar");
|
||||
if (!UToolMenus::Get()->IsMenuRegistered(ViewportToolbarName))
|
||||
{
|
||||
UToolMenu* const ViewportToolbarMenu = UToolMenus::Get()->RegisterMenu(
|
||||
ViewportToolbarName,
|
||||
NAME_None,
|
||||
EMultiBoxType::SlimHorizontalToolBar);
|
||||
|
||||
ViewportToolbarMenu->StyleName = TEXT("ViewportToolbar");
|
||||
|
||||
FToolMenuSection& LeftSection = ViewportToolbarMenu->AddSection(TEXT("Left"));
|
||||
LeftSection.AddEntry(UE::UnrealEd::CreateTransformsSubmenu());
|
||||
LeftSection.AddEntry(UE::UnrealEd::CreateSnappingSubmenu());
|
||||
|
||||
FToolMenuSection& RightSection = ViewportToolbarMenu->AddSection(TEXT("Right"));
|
||||
RightSection.Alignment = EToolMenuSectionAlign::Last;
|
||||
RightSection.AddEntry(UE::UnrealEd::CreateCameraSubmenu(UE::UnrealEd::FViewportCameraMenuOptions().ShowAll()));
|
||||
RightSection.AddEntry(UE::UnrealEd::CreateViewModesSubmenu());
|
||||
RightSection.AddEntry(UE::UnrealEd::CreatePerformanceAndScalabilitySubmenu());
|
||||
}
|
||||
|
||||
FToolMenuContext ViewportToolbarContext;
|
||||
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = Controller.IsValid() ? Controller->GetPreviewScene() : nullptr)
|
||||
{
|
||||
ViewportToolbarContext.AppendCommandList(PreviewScene->GetCommandList());
|
||||
}
|
||||
ViewportToolbarContext.AppendCommandList(GetCommandList());
|
||||
|
||||
UUnrealEdViewportToolbarContext* const ContextObject =
|
||||
UE::UnrealEd::CreateViewportToolbarDefaultContext(SharedThis(this));
|
||||
ContextObject->AssetEditorToolkit = AssetEditorToolkit;
|
||||
ViewportToolbarContext.AddObject(ContextObject);
|
||||
|
||||
return UToolMenus::Get()->GenerateWidget(ViewportToolbarName, ViewportToolbarContext);
|
||||
}
|
||||
|
||||
private:
|
||||
TSharedPtr<FMorphPresetEditorController> Controller;
|
||||
TWeakPtr<FAssetEditorToolkit> AssetEditorToolkit;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -330,6 +368,7 @@ void SMorphPresetViewportPanel::RebuildContent()
|
||||
[
|
||||
SNew(SMorphPresetPreviewViewport)
|
||||
.Controller(Controller)
|
||||
.AssetEditorToolkit(AssetEditorToolkit)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ class FUICommandList;
|
||||
class AActor;
|
||||
class IPersonaPreviewScene;
|
||||
class IPersonaToolkit;
|
||||
class UAnimationAsset;
|
||||
class UAnimSequence;
|
||||
class ULipSyncComponent;
|
||||
class ULipSyncMappingDataAsset;
|
||||
@@ -68,9 +67,6 @@ private:
|
||||
void RebuildPreviewAnimation();
|
||||
void ApplyPresetToPreview();
|
||||
void RefreshPreviewAnimationPose();
|
||||
UAnimationAsset* ResolvePreviewAnimationAsset() const;
|
||||
float GetPreviewAnimationPosition() const;
|
||||
bool IsPreviewAnimationCompatible(UAnimationAsset* PreviewAsset) const;
|
||||
void RebuildLipSyncPreviewComponents();
|
||||
void DestroyLipSyncPreviewComponents();
|
||||
void TickLipSyncPreviewComponents(float DeltaTime);
|
||||
@@ -82,6 +78,7 @@ private:
|
||||
TSharedPtr<FUICommandList> CommandList;
|
||||
TStrongObjectPtr<UAnimSequence> PreviewAnimation;
|
||||
TStrongObjectPtr<ULipSyncMappingDataAsset> DefaultLipSyncPreviewMapping;
|
||||
TWeakObjectPtr<USkeletalMesh> ActivePreviewMesh;
|
||||
TWeakObjectPtr<ULipSyncMappingDataAsset> LipSyncPreviewMapping;
|
||||
TWeakObjectPtr<USoundBase> LipSyncPreviewSound;
|
||||
TWeakObjectPtr<AActor> LipSyncPreviewActor;
|
||||
|
||||
Reference in New Issue
Block a user