init
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.19.0",
|
||||
"FriendlyName": "KawaiiPhysics",
|
||||
"Description": "Simple Bone Physics for UnrealEngine4 & 5",
|
||||
"Category": "Animation",
|
||||
"CreatedBy": "pafuhana1213",
|
||||
"CreatedByURL": "https://twitter.com/pafuhana1213",
|
||||
"DocsURL": "https://github.com/pafuhana1213/KawaiiPhysics",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "https://github.com/pafuhana1213/KawaiiPhysics/issues",
|
||||
"CanContainContent": false,
|
||||
"IsBetaVersion": false,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "KawaiiPhysics",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PostConfigInit"
|
||||
},
|
||||
{
|
||||
"Name": "KawaiiPhysicsEd",
|
||||
"Type": "UncookedOnly",
|
||||
"LoadingPhase": "PreDefault"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
+81
@@ -0,0 +1,81 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
|
||||
#include "AnimNotifyState_KawaiiPhysics.h"
|
||||
#include "KawaiiPhysicsLibrary.h"
|
||||
#include "Misc/UObjectToken.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNotifyState_KawaiiPhysics)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "KawaiiPhysics_AnimNotifyState"
|
||||
|
||||
UAnimNotifyState_KawaiiPhysicsAddExternalForce::UAnimNotifyState_KawaiiPhysicsAddExternalForce(
|
||||
const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
NotifyColor = FColor(255, 170, 0, 255);
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
}
|
||||
|
||||
FString UAnimNotifyState_KawaiiPhysicsAddExternalForce::GetNotifyName_Implementation() const
|
||||
{
|
||||
return FString(TEXT("KP: Add ExternalForce"));
|
||||
}
|
||||
|
||||
void UAnimNotifyState_KawaiiPhysicsAddExternalForce::NotifyBegin(USkeletalMeshComponent* MeshComp,
|
||||
UAnimSequenceBase* Animation,
|
||||
float TotalDuration,
|
||||
const FAnimNotifyEventReference& EventReference)
|
||||
{
|
||||
UKawaiiPhysicsLibrary::AddExternalForcesToComponent(MeshComp, AdditionalExternalForces, this,
|
||||
FilterTags, bFilterExactMatch);
|
||||
Super::NotifyBegin(MeshComp, Animation, TotalDuration, EventReference);
|
||||
}
|
||||
|
||||
void UAnimNotifyState_KawaiiPhysicsAddExternalForce::NotifyEnd(USkeletalMeshComponent* MeshComp,
|
||||
UAnimSequenceBase* Animation,
|
||||
const FAnimNotifyEventReference& EventReference)
|
||||
{
|
||||
UKawaiiPhysicsLibrary::RemoveExternalForcesFromComponent(MeshComp, this, FilterTags, bFilterExactMatch);
|
||||
|
||||
Super::NotifyEnd(MeshComp, Animation, EventReference);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UAnimNotifyState_KawaiiPhysicsAddExternalForce::ValidateAssociatedAssets()
|
||||
{
|
||||
static const FName NAME_AssetCheck("AssetCheck");
|
||||
|
||||
if (const UAnimSequenceBase* ContainingAsset = Cast<UAnimSequenceBase>(GetContainingAsset()))
|
||||
{
|
||||
for (auto& ForceInstancedStruct : AdditionalExternalForces)
|
||||
{
|
||||
if (!ForceInstancedStruct.IsValid())
|
||||
{
|
||||
FMessageLog AssetCheckLog(NAME_AssetCheck);
|
||||
|
||||
const FText MessageLooping = FText::Format(
|
||||
NSLOCTEXT("AnimNotify", "ExternalForce_ShouldSet",
|
||||
" AnimNotifyState(KawaiiPhysics_AddExternalForce) doesn't have a valid ExternalForce in {0}"),
|
||||
FText::AsCultureInvariant(ContainingAsset->GetPathName()));
|
||||
|
||||
AssetCheckLog.Warning()
|
||||
->AddToken(FUObjectToken::Create(ContainingAsset))
|
||||
->AddToken(FTextToken::Create(MessageLooping));
|
||||
|
||||
if (GIsEditor)
|
||||
{
|
||||
constexpr bool bForce = true;
|
||||
AssetCheckLog.Notify(MessageLooping, EMessageSeverity::Warning, bForce);
|
||||
}
|
||||
}
|
||||
|
||||
//const auto& ExternalForce = ForceInstancedStruct.Get<FKawaiiPhysics_ExternalForce>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Animation/AnimNotifies/AnimNotifyState.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#else
|
||||
#include "InstancedStruct.h"
|
||||
#endif
|
||||
|
||||
#include "AnimNotifyState_KawaiiPhysics.generated.h"
|
||||
|
||||
/**
|
||||
* UAnimNotifyState_KawaiiPhysicsAddExternalForce
|
||||
*
|
||||
* This class represents an animation notify state that adds external forces to a skeletal mesh component
|
||||
* during an animation sequence. It inherits from UAnimNotifyState and provides functionality to add and remove
|
||||
* external forces at the beginning and end of the animation notify state.
|
||||
*/
|
||||
UCLASS(Blueprintable, meta = (DisplayName = "KawaiiPhyiscs: Add ExternalForce"))
|
||||
class KAWAIIPHYSICS_API UAnimNotifyState_KawaiiPhysicsAddExternalForce : public UAnimNotifyState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for UAnimNotifyState_KawaiiPhysicsAddExternalForce.
|
||||
*
|
||||
* @param ObjectInitializer - The object initializer for this class.
|
||||
*/
|
||||
UAnimNotifyState_KawaiiPhysicsAddExternalForce(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Gets the name of the notify state.
|
||||
*
|
||||
* @return The name of the notify state as a string.
|
||||
*/
|
||||
virtual FString GetNotifyName_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Called when the animation notify state begins.
|
||||
*
|
||||
* @param MeshComp - The skeletal mesh component.
|
||||
* @param Animation - The animation sequence.
|
||||
* @param TotalDuration - The total duration of the notify state.
|
||||
* @param EventReference - The event reference.
|
||||
*/
|
||||
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration,
|
||||
const FAnimNotifyEventReference& EventReference) override;
|
||||
|
||||
/**
|
||||
* Called when the animation notify state ends.
|
||||
*
|
||||
* @param MeshComp - The skeletal mesh component.
|
||||
* @param Animation - The animation sequence.
|
||||
* @param EventReference - The event reference.
|
||||
*/
|
||||
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
|
||||
const FAnimNotifyEventReference& EventReference) override;
|
||||
|
||||
/**
|
||||
* Additional external forces to be applied to the skeletal mesh component.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce",
|
||||
meta = (BaseStruct = "/Script/KawaiiPhysics.KawaiiPhysics_ExternalForce", ExcludeBaseStruct))
|
||||
TArray<FInstancedStruct> AdditionalExternalForces;
|
||||
|
||||
/**
|
||||
* Tags used to filter which external forces are applied.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce")
|
||||
FGameplayTagContainer FilterTags;
|
||||
|
||||
/**
|
||||
* Whether to filter tags to exact matches (if False, parent tags will also be included).
|
||||
* Tagのフィルタリングにて完全一致にするか否か(Falseの場合は親Tagも含めます)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce")
|
||||
bool bFilterExactMatch;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates the associated assets in the editor.
|
||||
*/
|
||||
virtual void ValidateAssociatedAssets() override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "AnimNotify_KawaiiPhysics.h"
|
||||
#include "KawaiiPhysicsLibrary.h"
|
||||
#include "Misc/UObjectToken.h"
|
||||
#include "Logging/MessageLog.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNotify_KawaiiPhysics)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "KawaiiPhysics_AnimNotify"
|
||||
|
||||
UAnimNotify_KawaiiPhysicsAddExternalForce::UAnimNotify_KawaiiPhysicsAddExternalForce(
|
||||
const FObjectInitializer& ObjectInitializer)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
NotifyColor = FColor(255, 170, 0, 255);
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
}
|
||||
|
||||
FString UAnimNotify_KawaiiPhysicsAddExternalForce::GetNotifyName_Implementation() const
|
||||
{
|
||||
return FString(TEXT("KP: Add ExternalForce"));
|
||||
}
|
||||
|
||||
void UAnimNotify_KawaiiPhysicsAddExternalForce::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
|
||||
const FAnimNotifyEventReference& EventReference)
|
||||
{
|
||||
UKawaiiPhysicsLibrary::AddExternalForcesToComponent(MeshComp, AdditionalExternalForces, this,
|
||||
FilterTags, bFilterExactMatch, true);
|
||||
|
||||
Super::Notify(MeshComp, Animation, EventReference);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UAnimNotify_KawaiiPhysicsAddExternalForce::ValidateAssociatedAssets()
|
||||
{
|
||||
static const FName NAME_AssetCheck("AssetCheck");
|
||||
|
||||
if (const UAnimSequenceBase* ContainingAsset = Cast<UAnimSequenceBase>(GetContainingAsset()))
|
||||
{
|
||||
for (auto& ForceInstancedStruct : AdditionalExternalForces)
|
||||
{
|
||||
if (!ForceInstancedStruct.IsValid())
|
||||
{
|
||||
FMessageLog AssetCheckLog(NAME_AssetCheck);
|
||||
|
||||
const FText MessageLooping = FText::Format(
|
||||
NSLOCTEXT("AnimNotify", "ExternalForce_ShouldSet",
|
||||
" AnimNotify(KawaiiPhysics_AddExternalForce) doesn't have a valid ExternalForce in {0}"),
|
||||
FText::AsCultureInvariant(ContainingAsset->GetPathName()));
|
||||
|
||||
AssetCheckLog.Warning()
|
||||
->AddToken(FUObjectToken::Create(ContainingAsset))
|
||||
->AddToken(FTextToken::Create(MessageLooping));
|
||||
|
||||
if (GIsEditor)
|
||||
{
|
||||
constexpr bool bForce = true;
|
||||
AssetCheckLog.Notify(MessageLooping, EMessageSeverity::Warning, bForce);
|
||||
}
|
||||
}
|
||||
|
||||
//const auto& ExternalForce = ForceInstancedStruct.Get<FKawaiiPhysics_ExternalForce>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,80 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Animation/AnimNotifies/AnimNotify.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#else
|
||||
#include "InstancedStruct.h"
|
||||
#endif
|
||||
|
||||
#include "AnimNotify_KawaiiPhysics.generated.h"
|
||||
|
||||
/**
|
||||
* UAnimNotify_KawaiiPhysicsAddExternalForce
|
||||
*
|
||||
* This class represents an animation notify that adds external forces to a skeletal mesh component
|
||||
* during an animation sequence. It inherits from UAnimNotify and provides functionality to add and remove
|
||||
* external forces when the notify is triggered.
|
||||
*/
|
||||
UCLASS(Blueprintable, meta = (DisplayName = "KawaiiPhyiscs: Add ExternalForce"))
|
||||
class KAWAIIPHYSICS_API UAnimNotify_KawaiiPhysicsAddExternalForce : public UAnimNotify
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for UAnimNotify_KawaiiPhysicsAddExternalForce.
|
||||
*
|
||||
* @param ObjectInitializer - The object initializer for this class.
|
||||
*/
|
||||
UAnimNotify_KawaiiPhysicsAddExternalForce(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Gets the name of the notify.
|
||||
*
|
||||
* @return The name of the notify as a string.
|
||||
*/
|
||||
virtual FString GetNotifyName_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Called when the animation notify is triggered.
|
||||
*
|
||||
* @param MeshComp - The skeletal mesh component.
|
||||
* @param Animation - The animation sequence.
|
||||
* @param EventReference - The event reference.
|
||||
*/
|
||||
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
|
||||
const FAnimNotifyEventReference& EventReference) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Additional external forces to be applied to the skeletal mesh component.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce",
|
||||
meta = (BaseStruct = "/Script/KawaiiPhysics.KawaiiPhysics_ExternalForce", ExcludeBaseStruct))
|
||||
TArray<FInstancedStruct> AdditionalExternalForces;
|
||||
|
||||
/**
|
||||
* Tags used to filter which external forces are applied.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce")
|
||||
FGameplayTagContainer FilterTags;
|
||||
|
||||
/**
|
||||
* Whether to filter tags to exact matches (if False, parent tags will also be included).
|
||||
* Tagのフィルタリングにて完全一致にするか否か(Falseの場合は親Tagも含めます)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce")
|
||||
bool bFilterExactMatch;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates the associated assets in the editor.
|
||||
*/
|
||||
virtual void ValidateAssociatedAssets() override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class KawaiiPhysics : ModuleRules
|
||||
{
|
||||
public KawaiiPhysics(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"Core",
|
||||
"AnimGraphRuntime",
|
||||
"GameplayTags"
|
||||
}
|
||||
);
|
||||
|
||||
// StructUtils plugin has been integrated into the engine starting from 5.5
|
||||
if (Target.Version.MajorVersion == 5 && Target.Version.MinorVersion <= 4)
|
||||
{
|
||||
PublicDependencyModuleNames.Add("StructUtils");
|
||||
}
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
}
|
||||
);
|
||||
|
||||
if (Target.bBuildEditor)
|
||||
{
|
||||
PublicDependencyModuleNames.Add("UnrealEd");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "KawaiiPhysics.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FKawaiiPhysicsModule"
|
||||
|
||||
void FKawaiiPhysicsModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
}
|
||||
|
||||
void FKawaiiPhysicsModule::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
// we call this function before unloading the module.
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FKawaiiPhysicsModule, KawaiiPhysics)
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
|
||||
#include "KawaiiPhysicsBoneConstraintsDataAsset.h"
|
||||
|
||||
#include "KawaiiPhysics.h"
|
||||
#include "Internationalization/Regex.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor.h"
|
||||
#endif
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsBoneConstraintsDataAsset)
|
||||
|
||||
struct FBoneConstraintDataCustomVersion
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
// FNameからFBoneReferenceに移行
|
||||
ChangeToBoneReference = 0,
|
||||
|
||||
// ------------------------------------------------------
|
||||
VersionPlusOne,
|
||||
LatestVersion = VersionPlusOne - 1
|
||||
};
|
||||
|
||||
// The GUID for this custom version number
|
||||
const static FGuid GUID;
|
||||
|
||||
private:
|
||||
FBoneConstraintDataCustomVersion()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
const FGuid FBoneConstraintDataCustomVersion::GUID(0xA1C4D3F6, 0x5B2E7A8D, 0x9F6E4B3C, 0xD7E1A8B2);
|
||||
FCustomVersionRegistration GRegisterBoneConstraintDataCustomVersion(FBoneConstraintDataCustomVersion::GUID,
|
||||
FBoneConstraintDataCustomVersion::LatestVersion,
|
||||
TEXT("BoneConstraintData"));
|
||||
|
||||
void FModifyBoneConstraintData::Update(const FModifyBoneConstraint& BoneConstraint)
|
||||
{
|
||||
BoneReference1 = BoneConstraint.Bone1;
|
||||
BoneReference2 = BoneConstraint.Bone2;
|
||||
bOverrideCompliance = BoneConstraint.bOverrideCompliance;
|
||||
ComplianceType = BoneConstraint.ComplianceType;
|
||||
}
|
||||
|
||||
TArray<FModifyBoneConstraint> UKawaiiPhysicsBoneConstraintsDataAsset::GenerateBoneConstraints()
|
||||
{
|
||||
TArray<FModifyBoneConstraint> BoneConstraints;
|
||||
|
||||
for (const FModifyBoneConstraintData& BoneConstraintData : BoneConstraintsData)
|
||||
{
|
||||
FModifyBoneConstraint BoneConstraint;
|
||||
BoneConstraint.Bone1 = BoneConstraintData.BoneReference1;
|
||||
BoneConstraint.Bone2 = BoneConstraintData.BoneReference2;
|
||||
BoneConstraint.bOverrideCompliance = BoneConstraintData.bOverrideCompliance;
|
||||
BoneConstraint.ComplianceType = BoneConstraintData.ComplianceType;
|
||||
|
||||
BoneConstraints.Add(BoneConstraint);
|
||||
}
|
||||
|
||||
return BoneConstraints;
|
||||
}
|
||||
|
||||
void UKawaiiPhysicsBoneConstraintsDataAsset::Serialize(FStructuredArchiveRecord Record)
|
||||
{
|
||||
Super::Serialize(Record);
|
||||
|
||||
Record.GetUnderlyingArchive().UsingCustomVersion(FBoneConstraintDataCustomVersion::GUID);
|
||||
}
|
||||
|
||||
void UKawaiiPhysicsBoneConstraintsDataAsset::PostLoad()
|
||||
{
|
||||
Super::PostLoad();
|
||||
|
||||
if (GetLinkerCustomVersion(FBoneConstraintDataCustomVersion::GUID) <
|
||||
FBoneConstraintDataCustomVersion::ChangeToBoneReference)
|
||||
{
|
||||
for (auto& Data : BoneConstraintsData)
|
||||
{
|
||||
Data.BoneReference1 = FBoneReference(Data.BoneName1);
|
||||
Data.BoneReference2 = FBoneReference(Data.BoneName2);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
UpdatePreviewBoneList();
|
||||
#endif
|
||||
UE_LOG(LogKawaiiPhysics, Log, TEXT("Update : BoneName -> BoneReference (%s)"), *this->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
USkeleton* UKawaiiPhysicsBoneConstraintsDataAsset::GetSkeleton(bool& bInvalidSkeletonIsError,
|
||||
const IPropertyHandle* PropertyHandle)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
return PreviewSkeleton.LoadSynchronous();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
#define LOCTEXT_NAMESPACE "KawaiiPhysicsBoneConstraintsDataAsset"
|
||||
|
||||
void UKawaiiPhysicsBoneConstraintsDataAsset::ApplyRegex()
|
||||
{
|
||||
GEditor->BeginTransaction(FText::FromString("ApplyRegex"));
|
||||
Modify();
|
||||
|
||||
UpdatePreviewBoneList();
|
||||
|
||||
for (FRegexPatternBoneSet& Pattern : RegexPatternList)
|
||||
{
|
||||
const FRegexPattern Pattern1 = FRegexPattern(Pattern.RegexPatternBone1);
|
||||
const FRegexPattern Pattern2 = FRegexPattern(Pattern.RegexPatternBone2);
|
||||
|
||||
FRegexMatcher Matcher1(Pattern1, PreviewBoneListString);
|
||||
FRegexMatcher Matcher2(Pattern2, PreviewBoneListString);
|
||||
|
||||
while (Matcher1.FindNext() && Matcher2.FindNext())
|
||||
{
|
||||
FModifyBoneConstraintData BoneConstraintData;
|
||||
BoneConstraintData.BoneReference1 = FBoneReference(FName(*Matcher1.GetCaptureGroup(0)));
|
||||
BoneConstraintData.BoneReference2 = FBoneReference(FName(*Matcher2.GetCaptureGroup(0)));
|
||||
BoneConstraintsData.Add(BoneConstraintData);
|
||||
}
|
||||
}
|
||||
|
||||
GEditor->EndTransaction();
|
||||
}
|
||||
|
||||
|
||||
void UKawaiiPhysicsBoneConstraintsDataAsset::UpdatePreviewBoneList()
|
||||
{
|
||||
PreviewBoneList.Empty();
|
||||
PreviewBoneListString.Empty();
|
||||
|
||||
if (!PreviewSkeleton.IsValid())
|
||||
{
|
||||
PreviewSkeleton.LoadSynchronous();
|
||||
}
|
||||
|
||||
if (PreviewSkeleton.IsValid())
|
||||
{
|
||||
const FReferenceSkeleton& RefSkeleton = PreviewSkeleton->GetReferenceSkeleton();
|
||||
const TArray<FMeshBoneInfo>& RefBoneInfo = RefSkeleton.GetRefBoneInfo();
|
||||
|
||||
for (const FMeshBoneInfo& BoneInfo : RefBoneInfo)
|
||||
{
|
||||
PreviewBoneList.Add(BoneInfo.Name);
|
||||
PreviewBoneListString.Append(BoneInfo.Name.ToString());
|
||||
PreviewBoneListString.Append(TEXT(", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UKawaiiPhysicsBoneConstraintsDataAsset::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
const FName PropertyName = PropertyChangedEvent.MemberProperty
|
||||
? PropertyChangedEvent.MemberProperty->GetFName()
|
||||
: NAME_None;
|
||||
|
||||
if (PropertyName == FName(TEXT("PreviewSkeleton")))
|
||||
{
|
||||
UpdatePreviewBoneList();
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
#endif
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "KawaiiPhysicsCustomExternalForce.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsCustomExternalForce)
|
||||
@@ -0,0 +1,322 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "KawaiiPhysicsExternalForce.h"
|
||||
|
||||
#include "SceneInterface.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsExternalForce)
|
||||
|
||||
DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Basic_Apply"), STAT_KawaiiPhysics_ExternalForce_Basic_Apply,
|
||||
STATGROUP_Anim);
|
||||
DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Gravity_Apply"), STAT_KawaiiPhysics_ExternalForce_Gravity_Apply,
|
||||
STATGROUP_Anim);
|
||||
DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Curve_Apply"), STAT_KawaiiPhysics_ExternalForce_Curve_Apply,
|
||||
STATGROUP_Anim);
|
||||
DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Wind_Apply"), STAT_KawaiiPhysics_ExternalForce_Wind_Apply,
|
||||
STATGROUP_Anim);
|
||||
|
||||
///
|
||||
/// Basic
|
||||
///
|
||||
void FKawaiiPhysics_ExternalForce_Basic::PreApply(FAnimNode_KawaiiPhysics& Node,
|
||||
const USkeletalMeshComponent* SkelComp)
|
||||
{
|
||||
Super::PreApply(Node, SkelComp);
|
||||
|
||||
PrevTime = Time;
|
||||
Time += Node.DeltaTime;
|
||||
if (Interval > 0.0f)
|
||||
{
|
||||
if (Time > Interval)
|
||||
{
|
||||
Force = ForceDir * RandomizedForceScale;
|
||||
Time = FMath::Fmod(Time, Interval);
|
||||
}
|
||||
else
|
||||
{
|
||||
Force = FVector::ZeroVector;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Force = ForceDir * RandomizedForceScale;
|
||||
}
|
||||
|
||||
if (ExternalForceSpace == EExternalForceSpace::WorldSpace)
|
||||
{
|
||||
Force = ComponentTransform.InverseTransformVector(Force);
|
||||
}
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Basic::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM)
|
||||
{
|
||||
if (!CanApply(Bone))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Basic_Apply);
|
||||
|
||||
float ForceRate = 1.0f;
|
||||
if (const auto Curve = ForceRateByBoneLengthRate.GetRichCurve(); !Curve->IsEmpty())
|
||||
{
|
||||
ForceRate = Curve->Eval(Bone.LengthRateFromRoot);
|
||||
}
|
||||
|
||||
if (ExternalForceSpace == EExternalForceSpace::BoneSpace)
|
||||
{
|
||||
const FVector BoneForce = BoneTM.TransformVector(Force);
|
||||
Bone.Location += BoneForce * ForceRate * Node.DeltaTime;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
BoneForceMap.Add(Bone.BoneRef.BoneName, BoneForce);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Bone.Location += Force * ForceRate * Node.DeltaTime;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
BoneForceMap.Add(Bone.BoneRef.BoneName, Force * ForceRate);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Gravity
|
||||
///
|
||||
void FKawaiiPhysics_ExternalForce_Gravity::PreApply(FAnimNode_KawaiiPhysics& Node,
|
||||
const USkeletalMeshComponent* SkelComp)
|
||||
{
|
||||
Super::PreApply(Node, SkelComp);
|
||||
|
||||
Force = bUseOverrideGravityDirection ? OverrideGravityDirection : FVector(0, 0, -1.0f);
|
||||
|
||||
// For Character's Custom Gravity Direction
|
||||
if (const ACharacter* Character = Cast<ACharacter>(SkelComp->GetOwner()))
|
||||
{
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
|
||||
if (bUseCharacterGravityDirection)
|
||||
{
|
||||
Force = Character->GetGravityDirection();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bUseCharacterGravityScale)
|
||||
{
|
||||
if (const UCharacterMovementComponent* CharacterMovementComponent = Character->
|
||||
GetCharacterMovement())
|
||||
{
|
||||
Force *= CharacterMovementComponent->GetGravityZ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Force *= RandomizedForceScale;
|
||||
Force = ComponentTransform.InverseTransformVector(Force);
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Gravity::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext,
|
||||
const FTransform& BoneTM)
|
||||
{
|
||||
if (!CanApply(Bone))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Gravity_Apply);
|
||||
|
||||
float ForceRate = 1.0f;
|
||||
if (const auto Curve = ForceRateByBoneLengthRate.GetRichCurve(); !Curve->IsEmpty())
|
||||
{
|
||||
ForceRate = Curve->Eval(Bone.LengthRateFromRoot);
|
||||
}
|
||||
|
||||
Bone.Location += 0.5f * Force * ForceRate * Node.DeltaTime * Node.DeltaTime;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
BoneForceMap.Add(Bone.BoneRef.BoneName, Force * ForceRate);
|
||||
AnimDrawDebug(Bone, Node, PoseContext);
|
||||
#endif
|
||||
}
|
||||
|
||||
///
|
||||
/// Curve
|
||||
///
|
||||
void FKawaiiPhysics_ExternalForce_Curve::InitMaxCurveTime()
|
||||
{
|
||||
if (const FRichCurve* CurveX = ForceCurve.GetRichCurve(0); CurveX && !CurveX->IsEmpty())
|
||||
{
|
||||
MaxCurveTime = FMath::Max(MaxCurveTime, CurveX->GetLastKey().Time);
|
||||
}
|
||||
if (const FRichCurve* CurveY = ForceCurve.GetRichCurve(1); CurveY && !CurveY->IsEmpty())
|
||||
{
|
||||
MaxCurveTime = FMath::Max(MaxCurveTime, CurveY->GetLastKey().Time);
|
||||
}
|
||||
if (const FRichCurve* CurveZ = ForceCurve.GetRichCurve(2); CurveZ && !CurveZ->IsEmpty())
|
||||
{
|
||||
MaxCurveTime = FMath::Max(MaxCurveTime, CurveZ->GetLastKey().Time);
|
||||
}
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Curve::Initialize(const FAnimationInitializeContext& Context)
|
||||
{
|
||||
FKawaiiPhysics_ExternalForce::Initialize(Context);
|
||||
|
||||
InitMaxCurveTime();
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Curve::PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp)
|
||||
{
|
||||
Super::PreApply(Node, SkelComp);
|
||||
|
||||
#if WITH_EDITOR
|
||||
InitMaxCurveTime();
|
||||
#endif
|
||||
|
||||
PrevTime = Time;
|
||||
|
||||
if (CurveEvaluateType == EExternalForceCurveEvaluateType::Single)
|
||||
{
|
||||
Time += Node.DeltaTime * TimeScale;
|
||||
if (MaxCurveTime > 0 && Time > MaxCurveTime)
|
||||
{
|
||||
Time = FMath::Fmod(Time, MaxCurveTime);
|
||||
}
|
||||
Force = ForceCurve.GetValue(Time) * RandomizedForceScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
TArray<FVector> CurveValues;
|
||||
const float SubStep = Node.DeltaTime * TimeScale / SubstepCount;
|
||||
|
||||
for (int i = 0; i < SubstepCount; ++i)
|
||||
{
|
||||
Time += SubStep;
|
||||
if (MaxCurveTime > 0 && Time > MaxCurveTime)
|
||||
{
|
||||
Time = FMath::Fmod(Time, MaxCurveTime);
|
||||
}
|
||||
CurveValues.Add(ForceCurve.GetValue(Time));
|
||||
}
|
||||
|
||||
Force = FVector::ZeroVector;
|
||||
switch (CurveEvaluateType)
|
||||
{
|
||||
case EExternalForceCurveEvaluateType::Average:
|
||||
for (const auto& CurveValue : CurveValues)
|
||||
{
|
||||
Force += CurveValue;
|
||||
}
|
||||
Force /= static_cast<float>(SubstepCount);
|
||||
|
||||
break;
|
||||
|
||||
case EExternalForceCurveEvaluateType::Max:
|
||||
Force = FVector(FLT_MIN);
|
||||
for (const auto& CurveValue : CurveValues)
|
||||
{
|
||||
Force = FVector::Max(Force, CurveValue);
|
||||
}
|
||||
break;
|
||||
case EExternalForceCurveEvaluateType::Min:
|
||||
Force = FVector(FLT_MAX);
|
||||
for (const auto& CurveValue : CurveValues)
|
||||
{
|
||||
Force = FVector::Min(Force, CurveValue);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Force *= RandomizedForceScale;
|
||||
}
|
||||
|
||||
if (ExternalForceSpace == EExternalForceSpace::WorldSpace)
|
||||
{
|
||||
Force = ComponentTransform.InverseTransformVector(Force);
|
||||
}
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Curve::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM)
|
||||
{
|
||||
if (!CanApply(Bone))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Curve_Apply);
|
||||
|
||||
float ForceRate = 1.0f;
|
||||
if (const auto Curve = ForceRateByBoneLengthRate.GetRichCurve(); !Curve->IsEmpty())
|
||||
{
|
||||
ForceRate = Curve->Eval(Bone.LengthRateFromRoot);
|
||||
}
|
||||
|
||||
if (ExternalForceSpace == EExternalForceSpace::BoneSpace)
|
||||
{
|
||||
const FVector BoneForce = BoneTM.TransformVector(Force);
|
||||
Bone.Location += BoneForce * ForceRate * Node.DeltaTime;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
BoneForceMap.Add(Bone.BoneRef.BoneName, BoneForce * ForceRate);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Bone.Location += Force * ForceRate * Node.DeltaTime;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
BoneForceMap.Add(Bone.BoneRef.BoneName, Force * ForceRate);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
AnimDrawDebug(Bone, Node, PoseContext);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Wind::PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp)
|
||||
{
|
||||
Super::PreApply(Node, SkelComp);
|
||||
|
||||
World = SkelComp ? SkelComp->GetWorld() : nullptr;
|
||||
}
|
||||
|
||||
void FKawaiiPhysics_ExternalForce_Wind::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM)
|
||||
{
|
||||
const FSceneInterface* Scene = World && World->Scene ? World->Scene : nullptr;
|
||||
if (!CanApply(Bone) || !Scene)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Wind_Apply);
|
||||
|
||||
float ForceRate = 1.0f;
|
||||
if (const auto Curve = ForceRateByBoneLengthRate.GetRichCurve(); !Curve->IsEmpty())
|
||||
{
|
||||
ForceRate = Curve->Eval(Bone.LengthRateFromRoot);
|
||||
}
|
||||
|
||||
FVector WindDirection = FVector::ZeroVector;
|
||||
float WindSpeed, WindMinGust, WindMaxGust = 0.0f;
|
||||
Scene->GetWindParameters(ComponentTransform.TransformPosition(Bone.PoseLocation), WindDirection,
|
||||
WindSpeed, WindMinGust, WindMaxGust);
|
||||
WindDirection = ComponentTransform.InverseTransformVector(WindDirection);
|
||||
WindDirection *= WindSpeed;
|
||||
|
||||
Bone.Location += WindDirection * ForceRate * RandomizedForceScale * Node.DeltaTime;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
BoneForceMap.Add(Bone.BoneRef.BoneName, WindDirection * ForceRate * RandomizedForceScale);
|
||||
AnimDrawDebug(Bone, Node, PoseContext);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "KawaiiPhysicsLibrary.h"
|
||||
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "BlueprintGameplayTagLibrary.h"
|
||||
#include "KawaiiPhysicsExternalForce.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsLibrary)
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogKawaiiPhysicsLibrary, Verbose, All);
|
||||
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::ConvertToKawaiiPhysics(const FAnimNodeReference& Node,
|
||||
EAnimNodeReferenceConversionResult& Result)
|
||||
{
|
||||
return FAnimNodeReference::ConvertToType<FKawaiiPhysicsReference>(Node, Result);
|
||||
}
|
||||
|
||||
bool UKawaiiPhysicsLibrary::CollectKawaiiPhysicsNodes(TArray<FKawaiiPhysicsReference>& Nodes,
|
||||
UAnimInstance* AnimInstance,
|
||||
const FGameplayTagContainer& FilterTags, bool bFilterExactMatch)
|
||||
{
|
||||
if (!ensure(AnimInstance && AnimInstance->GetClass()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bResult = false;
|
||||
if (const IAnimClassInterface* AnimClassInterface =
|
||||
IAnimClassInterface::GetFromClass((AnimInstance->GetClass())))
|
||||
{
|
||||
const TArray<FStructProperty*>& AnimNodeProperties = AnimClassInterface->GetAnimNodeProperties();
|
||||
for (int i = 0; i < AnimNodeProperties.Num(); ++i)
|
||||
{
|
||||
if (AnimNodeProperties[i]->Struct->
|
||||
IsChildOf(FKawaiiPhysicsReference::FInternalNodeType::StaticStruct()))
|
||||
{
|
||||
EAnimNodeReferenceConversionResult Result;
|
||||
FKawaiiPhysicsReference KawaiiPhysicsReference = ConvertToKawaiiPhysics(
|
||||
FAnimNodeReference(AnimInstance, i), Result);
|
||||
|
||||
if (Result == EAnimNodeReferenceConversionResult::Succeeded)
|
||||
{
|
||||
auto& Tag = KawaiiPhysicsReference.GetAnimNode<FAnimNode_KawaiiPhysics>().KawaiiPhysicsTag;
|
||||
if (FilterTags.IsEmpty() || UBlueprintGameplayTagLibrary::MatchesAnyTags(
|
||||
Tag, FilterTags, bFilterExactMatch))
|
||||
{
|
||||
Nodes.Add(KawaiiPhysicsReference);
|
||||
bResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool UKawaiiPhysicsLibrary::CollectKawaiiPhysicsNodes(TArray<FKawaiiPhysicsReference>& Nodes,
|
||||
USkeletalMeshComponent* MeshComp,
|
||||
const FGameplayTagContainer& FilterTags, bool bFilterExactMatch)
|
||||
{
|
||||
if (!ensure(MeshComp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int NodeNum = Nodes.Num();
|
||||
|
||||
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||
{
|
||||
CollectKawaiiPhysicsNodes(Nodes, AnimInstance, FilterTags,
|
||||
bFilterExactMatch);
|
||||
}
|
||||
|
||||
const TArray<UAnimInstance*>& LinkedInstances =
|
||||
const_cast<const USkeletalMeshComponent*>(MeshComp)->GetLinkedAnimInstances();
|
||||
for (UAnimInstance* LinkedInstance : LinkedInstances)
|
||||
{
|
||||
CollectKawaiiPhysicsNodes(Nodes, LinkedInstance, FilterTags,
|
||||
bFilterExactMatch);
|
||||
}
|
||||
|
||||
if (UAnimInstance* PostProcessAnimInstance = MeshComp->GetPostProcessInstance())
|
||||
{
|
||||
CollectKawaiiPhysicsNodes(Nodes, PostProcessAnimInstance, FilterTags,
|
||||
bFilterExactMatch);
|
||||
}
|
||||
|
||||
return NodeNum != Nodes.Num();
|
||||
}
|
||||
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::ResetDynamics(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("ResetDynamics"),
|
||||
[](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
InKawaiiPhysics.ResetDynamics(ETeleportType::ResetPhysics);
|
||||
});
|
||||
|
||||
return KawaiiPhysics;
|
||||
}
|
||||
|
||||
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
FName& RootBoneName)
|
||||
{
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("SetRootBoneName"),
|
||||
[RootBoneName](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
InKawaiiPhysics.RootBone = FBoneReference(RootBoneName);
|
||||
});
|
||||
|
||||
return KawaiiPhysics;
|
||||
}
|
||||
|
||||
FName UKawaiiPhysicsLibrary::GetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
FName RootBoneName;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("GetRootBoneName"),
|
||||
[&RootBoneName](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
RootBoneName = InKawaiiPhysics.RootBone.BoneName;
|
||||
});
|
||||
|
||||
return RootBoneName;
|
||||
}
|
||||
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
TArray<FName>& ExcludeBoneNames)
|
||||
{
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("SetExcludeBoneNames"),
|
||||
[&ExcludeBoneNames](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
InKawaiiPhysics.ExcludeBones.Empty();
|
||||
for (auto& ExcludeBoneName : ExcludeBoneNames)
|
||||
{
|
||||
InKawaiiPhysics.ExcludeBones.Add(FBoneReference(ExcludeBoneName));
|
||||
}
|
||||
});
|
||||
|
||||
return KawaiiPhysics;
|
||||
}
|
||||
|
||||
TArray<FName> UKawaiiPhysicsLibrary::GetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
TArray<FName> ExcludeBoneNames;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("GetExcludeBoneNames"),
|
||||
[&ExcludeBoneNames](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
for (auto& ExcludeBone : InKawaiiPhysics.ExcludeBones)
|
||||
{
|
||||
ExcludeBoneNames.Add(ExcludeBone.BoneName);
|
||||
}
|
||||
});
|
||||
|
||||
return ExcludeBoneNames;
|
||||
}
|
||||
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::AddExternalForceWithExecResult(
|
||||
EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
FInstancedStruct& ExternalForce, UObject* Owner)
|
||||
{
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
if (AddExternalForce(KawaiiPhysics, ExternalForce, Owner))
|
||||
{
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
|
||||
return KawaiiPhysics;
|
||||
}
|
||||
|
||||
bool UKawaiiPhysicsLibrary::AddExternalForce(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
FInstancedStruct& ExternalForce, UObject* Owner, bool bIsOneShot)
|
||||
{
|
||||
bool bResult = false;
|
||||
|
||||
if (ExternalForce.IsValid())
|
||||
{
|
||||
if (auto* ExternalForcePtr = ExternalForce.GetMutablePtr<FKawaiiPhysics_ExternalForce>())
|
||||
{
|
||||
ExternalForcePtr->ExternalOwner = Owner;
|
||||
ExternalForcePtr->bIsOneShot = bIsOneShot;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("AddExternalForce"),
|
||||
[&](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
InKawaiiPhysics.ExternalForces.Add(ExternalForce);
|
||||
});
|
||||
|
||||
bResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool UKawaiiPhysicsLibrary::AddExternalForcesToComponent(USkeletalMeshComponent* MeshComp,
|
||||
TArray<FInstancedStruct>& ExternalForces,
|
||||
UObject* Owner,
|
||||
FGameplayTagContainer& FilterTags,
|
||||
bool bFilterExactMatch, bool bIsOneShot)
|
||||
{
|
||||
bool bResult = false;
|
||||
|
||||
TArray<FKawaiiPhysicsReference> KawaiiPhysicsReferences;
|
||||
CollectKawaiiPhysicsNodes(KawaiiPhysicsReferences, MeshComp, FilterTags, bFilterExactMatch);
|
||||
for (auto& KawaiiPhysicsReference : KawaiiPhysicsReferences)
|
||||
{
|
||||
for (auto& AExternalForce : ExternalForces)
|
||||
{
|
||||
if (AExternalForce.IsValid())
|
||||
{
|
||||
if (AddExternalForce(KawaiiPhysicsReference, AExternalForce, Owner, bIsOneShot))
|
||||
{
|
||||
bResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool UKawaiiPhysicsLibrary::RemoveExternalForcesFromComponent(USkeletalMeshComponent* MeshComp, UObject* Owner,
|
||||
FGameplayTagContainer& FilterTags, bool bFilterExactMatch)
|
||||
{
|
||||
bool bResult = false;
|
||||
|
||||
TArray<FKawaiiPhysicsReference> KawaiiPhysicsReferences;
|
||||
CollectKawaiiPhysicsNodes(KawaiiPhysicsReferences, MeshComp, FilterTags, bFilterExactMatch);
|
||||
for (auto& KawaiiPhysicsReference : KawaiiPhysicsReferences)
|
||||
{
|
||||
KawaiiPhysicsReference.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("RemoveExternalForce"),
|
||||
[&](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
const int32 NumRemoved = InKawaiiPhysics.ExternalForces.RemoveAll([&](FInstancedStruct& InstancedStruct)
|
||||
{
|
||||
const auto* ExternalForcePtr = InstancedStruct.GetMutablePtr<FKawaiiPhysics_ExternalForce>();
|
||||
return ExternalForcePtr && ExternalForcePtr->ExternalOwner == Owner;
|
||||
});
|
||||
|
||||
if (NumRemoved > 0)
|
||||
{
|
||||
bResult = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
DEFINE_FUNCTION(UKawaiiPhysicsLibrary::execSetExternalForceWildcardProperty)
|
||||
{
|
||||
P_GET_ENUM_REF(EKawaiiPhysicsAccessExternalForceResult, ExecResult);
|
||||
P_GET_STRUCT_REF(FKawaiiPhysicsReference, KawaiiPhysics);
|
||||
P_GET_PROPERTY(FIntProperty, ExternalForceIndex);
|
||||
P_GET_STRUCT_REF(FName, PropertyName);
|
||||
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
// Read wildcard Value input.
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FStructProperty>(nullptr);
|
||||
|
||||
const FProperty* ValueProp = CastField<FProperty>(Stack.MostRecentProperty);
|
||||
void* ValuePtr = Stack.MostRecentPropertyAddress;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("GetExternalForceWildcardProperty"),
|
||||
[&ExecResult, &ExternalForceIndex, &PropertyName, &ValuePtr](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) &&
|
||||
InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid())
|
||||
{
|
||||
const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct();
|
||||
auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable<
|
||||
FKawaiiPhysics_ExternalForce>();
|
||||
|
||||
if (const FProperty* Property = FindFProperty<FProperty>(ScriptStruct, PropertyName))
|
||||
{
|
||||
Property->CopyCompleteValue(Property->ContainerPtrToValuePtr<uint8>(&Force), ValuePtr);
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
P_FINISH;
|
||||
}
|
||||
|
||||
DEFINE_FUNCTION(UKawaiiPhysicsLibrary::execGetExternalForceWildcardProperty)
|
||||
{
|
||||
P_GET_ENUM_REF(EKawaiiPhysicsAccessExternalForceResult, ExecResult);
|
||||
P_GET_STRUCT_REF(FKawaiiPhysicsReference, KawaiiPhysics);
|
||||
P_GET_PROPERTY(FIntProperty, ExternalForceIndex);
|
||||
P_GET_STRUCT_REF(FName, PropertyName);
|
||||
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
// Read wildcard Value input.
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FStructProperty>(nullptr);
|
||||
|
||||
const FProperty* ValueProp = CastField<FProperty>(Stack.MostRecentProperty);
|
||||
void* ValuePtr = Stack.MostRecentPropertyAddress;
|
||||
|
||||
void* Result = nullptr;
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("GetExternalForceWildcardProperty"),
|
||||
[&Result, &ExecResult, &ExternalForceIndex, &PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) &&
|
||||
InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid())
|
||||
{
|
||||
const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct();
|
||||
auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable<
|
||||
FKawaiiPhysics_ExternalForce>();
|
||||
|
||||
if (const FProperty* Property = FindFProperty<FProperty>(ScriptStruct, PropertyName))
|
||||
{
|
||||
Result = Property->ContainerPtrToValuePtr<void>(&Force);
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
P_FINISH;
|
||||
|
||||
if (ValuePtr && Result)
|
||||
{
|
||||
P_NATIVE_BEGIN;
|
||||
ValueProp->CopyCompleteValue(ValuePtr, Result);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
|
||||
#include "KawaiiPhysicsLimitsDataAsset.h"
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "KawaiiPhysics.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsLimitsDataAsset)
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogKawaiiPhysics);
|
||||
|
||||
struct FCollisionLimitDataCustomVersion
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
// FNameからFBoneReferenceに移行
|
||||
ChangeToBoneReference = 0,
|
||||
DeprecateLimitData,
|
||||
|
||||
// ------------------------------------------------------
|
||||
VersionPlusOne,
|
||||
LatestVersion = VersionPlusOne - 1
|
||||
};
|
||||
|
||||
// The GUID for this custom version number
|
||||
const static FGuid GUID;
|
||||
|
||||
private:
|
||||
FCollisionLimitDataCustomVersion()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
const FGuid FCollisionLimitDataCustomVersion::GUID(0x3A1F7B2E, 0x7B9D6E8C, 0x4C2A9F1D, 0x85B3E4F1);
|
||||
FCustomVersionRegistration GRegisterCollisionLimitDataCustomVersion(FCollisionLimitDataCustomVersion::GUID,
|
||||
FCollisionLimitDataCustomVersion::LatestVersion,
|
||||
TEXT("CollisionLimitData"));
|
||||
|
||||
#if WITH_EDITOR
|
||||
template <typename CollisionLimitType>
|
||||
void UpdateCollisionLimit(TArray<CollisionLimitType>& CollisionLimitsData, const CollisionLimitType& NewLimit)
|
||||
{
|
||||
for (auto& LimitData : CollisionLimitsData)
|
||||
{
|
||||
if (LimitData.Guid == NewLimit.Guid)
|
||||
{
|
||||
LimitData = NewLimit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UKawaiiPhysicsLimitsDataAsset::UpdateLimit(FCollisionLimitBase* Limit)
|
||||
{
|
||||
switch (Limit->Type)
|
||||
{
|
||||
case ECollisionLimitType::Spherical:
|
||||
UpdateCollisionLimit(SphericalLimits, *static_cast<FSphericalLimit*>(Limit));
|
||||
break;
|
||||
case ECollisionLimitType::Capsule:
|
||||
UpdateCollisionLimit(CapsuleLimits, *static_cast<FCapsuleLimit*>(Limit));
|
||||
break;
|
||||
case ECollisionLimitType::Box:
|
||||
UpdateCollisionLimit(BoxLimits, *static_cast<FBoxLimit*>(Limit));
|
||||
break;
|
||||
case ECollisionLimitType::Planar:
|
||||
UpdateCollisionLimit(PlanarLimits, *static_cast<FPlanarLimit*>(Limit));
|
||||
break;
|
||||
case ECollisionLimitType::None:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
MarkPackageDirty();
|
||||
}
|
||||
|
||||
template <typename CollisionLimitDataType, typename CollisionLimitType>
|
||||
void SyncCollisionLimits(const TArray<CollisionLimitDataType>& CollisionLimitData,
|
||||
TArray<CollisionLimitType>& CollisionLimits)
|
||||
{
|
||||
CollisionLimits.Empty();
|
||||
for (const auto& Data : CollisionLimitData)
|
||||
{
|
||||
CollisionLimits.Add(Data.Convert());
|
||||
}
|
||||
}
|
||||
|
||||
void UKawaiiPhysicsLimitsDataAsset::Sync()
|
||||
{
|
||||
SyncCollisionLimits(SphericalLimitsData, SphericalLimits);
|
||||
SyncCollisionLimits(CapsuleLimitsData, CapsuleLimits);
|
||||
SyncCollisionLimits(BoxLimitsData, BoxLimits);
|
||||
SyncCollisionLimits(PlanarLimitsData, PlanarLimits);
|
||||
}
|
||||
|
||||
void UKawaiiPhysicsLimitsDataAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
|
||||
{
|
||||
Super::PostEditChangeChainProperty(PropertyChangedEvent);
|
||||
|
||||
|
||||
FName ArrayPropertyName = PropertyChangedEvent.MemberProperty
|
||||
? PropertyChangedEvent.MemberProperty->GetFName()
|
||||
: NAME_None;
|
||||
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet &&
|
||||
PropertyChangedEvent.PropertyChain.GetActiveMemberNode())
|
||||
{
|
||||
ArrayPropertyName = PropertyChangedEvent.PropertyChain.GetActiveMemberNode()->GetValue()->GetFName();
|
||||
}
|
||||
|
||||
auto UpdateLimits = [&](auto& Limits)
|
||||
{
|
||||
int32 ArrayIndex = PropertyChangedEvent.GetArrayIndex(ArrayPropertyName.ToString());
|
||||
|
||||
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd ||
|
||||
PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet)
|
||||
{
|
||||
Limits[ArrayIndex].SourceType = ECollisionSourceType::DataAsset;
|
||||
}
|
||||
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::Duplicate)
|
||||
{
|
||||
Limits[ArrayIndex].Guid = FGuid::NewGuid();
|
||||
}
|
||||
};
|
||||
|
||||
if (ArrayPropertyName == GET_MEMBER_NAME_CHECKED(UKawaiiPhysicsLimitsDataAsset, SphericalLimits))
|
||||
{
|
||||
UpdateLimits(SphericalLimits);
|
||||
}
|
||||
else if (ArrayPropertyName == GET_MEMBER_NAME_CHECKED(UKawaiiPhysicsLimitsDataAsset, CapsuleLimits))
|
||||
{
|
||||
UpdateLimits(CapsuleLimits);
|
||||
}
|
||||
else if (ArrayPropertyName == GET_MEMBER_NAME_CHECKED(UKawaiiPhysicsLimitsDataAsset, BoxLimits))
|
||||
{
|
||||
UpdateLimits(BoxLimits);
|
||||
}
|
||||
else if (ArrayPropertyName == GET_MEMBER_NAME_CHECKED(UKawaiiPhysicsLimitsDataAsset, PlanarLimits))
|
||||
{
|
||||
UpdateLimits(PlanarLimits);
|
||||
}
|
||||
|
||||
OnLimitsChanged.Broadcast(PropertyChangedEvent);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
void UKawaiiPhysicsLimitsDataAsset::Serialize(FStructuredArchiveRecord Record)
|
||||
{
|
||||
Super::Serialize(Record);
|
||||
|
||||
Record.GetUnderlyingArchive().UsingCustomVersion(FCollisionLimitDataCustomVersion::GUID);
|
||||
}
|
||||
#endif
|
||||
|
||||
USkeleton* UKawaiiPhysicsLimitsDataAsset::GetSkeleton(bool& bInvalidSkeletonIsError,
|
||||
const IPropertyHandle* PropertyHandle)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
return Skeleton;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UKawaiiPhysicsLimitsDataAsset::PostLoad()
|
||||
{
|
||||
Super::PostLoad();
|
||||
|
||||
if (GetLinkerCustomVersion(FCollisionLimitDataCustomVersion::GUID) <
|
||||
FCollisionLimitDataCustomVersion::ChangeToBoneReference)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
for (auto& Data : SphericalLimitsData)
|
||||
{
|
||||
Data.DrivingBoneReference = FBoneReference(Data.DrivingBoneName);
|
||||
}
|
||||
for (auto& Data : CapsuleLimitsData)
|
||||
{
|
||||
Data.DrivingBoneReference = FBoneReference(Data.DrivingBoneName);
|
||||
}
|
||||
for (auto& Data : BoxLimitsData)
|
||||
{
|
||||
Data.DrivingBoneReference = FBoneReference(Data.DrivingBoneName);
|
||||
}
|
||||
for (auto& Data : PlanarLimitsData)
|
||||
{
|
||||
Data.DrivingBoneReference = FBoneReference(Data.DrivingBoneName);
|
||||
}
|
||||
UE_LOG(LogKawaiiPhysics, Log, TEXT("Update : BoneName -> BoneReference (%s)"), *this->GetName());
|
||||
#endif
|
||||
}
|
||||
|
||||
if (GetLinkerCustomVersion(FCollisionLimitDataCustomVersion::GUID) <
|
||||
FCollisionLimitDataCustomVersion::DeprecateLimitData)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
Sync();
|
||||
UE_LOG(LogKawaiiPhysics, Log, TEXT("Update : Deprecate LimitData (%s)"), *this->GetName());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogKawaiiPhysics, Log, All);
|
||||
|
||||
class FKawaiiPhysicsModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "Interfaces/Interface_BoneReferenceSkeletonProvider.h"
|
||||
#include "KawaiiPhysicsBoneConstraintsDataAsset.generated.h"
|
||||
|
||||
/**
|
||||
* Struct representing the data for modifying bone constraints in KawaiiPhysics.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct KAWAIIPHYSICS_API FModifyBoneConstraintData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Name of the first bone (deprecated) */
|
||||
UPROPERTY()
|
||||
FName BoneName1;
|
||||
|
||||
/** Name of the second bone (deprecated) */
|
||||
UPROPERTY()
|
||||
FName BoneName2;
|
||||
|
||||
/** Reference to the first bone */
|
||||
UPROPERTY(EditAnywhere, category = "KawaiiPhysics")
|
||||
FBoneReference BoneReference1;
|
||||
|
||||
/** Reference to the second bone */
|
||||
UPROPERTY(EditAnywhere, category = "KawaiiPhysics")
|
||||
FBoneReference BoneReference2;
|
||||
|
||||
/** Whether to override the compliance type */
|
||||
UPROPERTY(EditAnywhere, category = "KawaiiPhysics", meta=(InlineEditConditionToggle))
|
||||
bool bOverrideCompliance = false;
|
||||
|
||||
/** The compliance type to use if overriding */
|
||||
UPROPERTY(EditAnywhere, category = "KawaiiPhysics", meta=(EditCondition="bOverrideCompliance"))
|
||||
EXPBDComplianceType ComplianceType = EXPBDComplianceType::Leather;
|
||||
|
||||
/**
|
||||
* Updates the bone constraint data with the given constraint.
|
||||
* @param BoneConstraint The bone constraint to update from.
|
||||
*/
|
||||
void Update(const FModifyBoneConstraint& BoneConstraint);
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct representing a set of regex patterns for bones in KawaiiPhysics.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FRegexPatternBoneSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Regex pattern for the first bone */
|
||||
UPROPERTY(EditAnywhere, Category="Helper")
|
||||
FString RegexPatternBone1;
|
||||
|
||||
/** Regex pattern for the second bone */
|
||||
UPROPERTY(EditAnywhere, Category="Helper")
|
||||
FString RegexPatternBone2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Data asset for managing bone constraints in KawaiiPhysics.
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class KAWAIIPHYSICS_API UKawaiiPhysicsBoneConstraintsDataAsset : public UDataAsset,
|
||||
public IBoneReferenceSkeletonProvider
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Array of bone constraint data */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Bone Constraint (Experimental)",
|
||||
meta=(TitleProperty="{BoneReference1} - {BoneReference2}"))
|
||||
TArray<FModifyBoneConstraintData> BoneConstraintsData;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
|
||||
/** List of regex patterns for bones */
|
||||
UPROPERTY(EditAnywhere, Category="Helper")
|
||||
TArray<FRegexPatternBoneSet> RegexPatternList;
|
||||
|
||||
/** Preview skeleton for editor */
|
||||
UPROPERTY(EditAnywhere, Category = "Skeleton")
|
||||
TSoftObjectPtr<USkeleton> PreviewSkeleton;
|
||||
|
||||
/** List of preview bones */
|
||||
UPROPERTY(VisibleAnywhere, Category = "Skeleton", meta= (EditCondition=false))
|
||||
TArray<FBoneReference> PreviewBoneList;
|
||||
|
||||
/** String representation of the preview bone list */
|
||||
UPROPERTY()
|
||||
FString PreviewBoneListString;
|
||||
#endif
|
||||
|
||||
// Begin UObject Interface.
|
||||
virtual void Serialize(FStructuredArchiveRecord Record) override;
|
||||
virtual void PostLoad() override;
|
||||
// End UObject Interface.
|
||||
|
||||
// IBoneReferenceSkeletonProvider interface
|
||||
virtual USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError, const IPropertyHandle* PropertyHandle) override;
|
||||
|
||||
/** Generates bone constraints based on the current data */
|
||||
TArray<FModifyBoneConstraint> GenerateBoneConstraints();
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
/** Applies regex patterns to the bone constraints */
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="Helper")
|
||||
void ApplyRegex();
|
||||
|
||||
/** Updates the preview bone list */
|
||||
void UpdatePreviewBoneList();
|
||||
|
||||
/** Handles property changes in the editor */
|
||||
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "KawaiiPhysicsCustomExternalForce.generated.h"
|
||||
|
||||
|
||||
UCLASS(Abstract, Blueprintable, EditInlineNew, CollapseCategories)
|
||||
class KAWAIIPHYSICS_API UKawaiiPhysics_CustomExternalForce : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|CustomExternalForce")
|
||||
bool bIsEnabled = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|CustomExternalForce")
|
||||
bool bDrawDebug = false;
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintNativeEvent)
|
||||
void PreApply(UPARAM(ref) FAnimNode_KawaiiPhysics& Node,
|
||||
const USkeletalMeshComponent* SkelComp);
|
||||
|
||||
virtual void PreApply_Implementation(
|
||||
UPARAM(ref) FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp)PURE_VIRTUAL(,);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent)
|
||||
void Apply(UPARAM(ref) FAnimNode_KawaiiPhysics& Node, int32 ModifyBoneIndex,
|
||||
const USkeletalMeshComponent* SkelComp, const FTransform& BoneTransform);
|
||||
|
||||
virtual void Apply_Implementation(
|
||||
UPARAM(ref) FAnimNode_KawaiiPhysics& Node, int32 ModifyBoneIndex, const USkeletalMeshComponent* SkelComp,
|
||||
const FTransform& BoneTransform)
|
||||
{
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="KawaiiPhysics|CustomExternalForce")
|
||||
virtual bool IsDebugEnabled()
|
||||
{
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
if (CVarAnimNodeKawaiiPhysicsDebug.GetValueOnAnyThread())
|
||||
{
|
||||
return bDrawDebug;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,448 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "SceneManagement.h"
|
||||
#include "Curves/CurveVector.h"
|
||||
#include "KawaiiPhysicsExternalForce.generated.h"
|
||||
|
||||
/**
|
||||
* Enum representing the space in which external forces are simulated.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EExternalForceSpace : uint8
|
||||
{
|
||||
/** Simulate in component space. Moving the entire skeletal mesh will have no affect on velocities */
|
||||
ComponentSpace,
|
||||
/** Simulate in world space. Moving the skeletal mesh will generate velocity changes */
|
||||
WorldSpace,
|
||||
/** Simulate in another bone space. Moving the entire skeletal mesh and individually modifying the base bone will have no affect on velocities */
|
||||
BoneSpace,
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum representing the evaluation type for external force curves.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EExternalForceCurveEvaluateType : uint8
|
||||
{
|
||||
/** Evaluate the curve at a single point */
|
||||
Single,
|
||||
/** Evaluate the curve by averaging multiple points */
|
||||
Average,
|
||||
/** Evaluate the curve by taking the maximum value from multiple points */
|
||||
Max,
|
||||
/** Evaluate the curve by taking the minimum value from multiple points */
|
||||
Min
|
||||
};
|
||||
|
||||
|
||||
///
|
||||
/// Base
|
||||
///
|
||||
USTRUCT(BlueprintType)
|
||||
struct KAWAIIPHYSICS_API FKawaiiPhysics_ExternalForce
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Whether the external force is enabled */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce")
|
||||
bool bIsEnabled = true;
|
||||
|
||||
/** Whether to draw debug information for the external force */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce")
|
||||
bool bDrawDebug = false;
|
||||
|
||||
/**
|
||||
* 外力を適応するボーンを指定(=指定しなかったボーンには適応しない)
|
||||
* Specify the bones to which the external force will be applied (= the force will not be applied to bones that are not specified)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce")
|
||||
TArray<FBoneReference> ApplyBoneFilter;
|
||||
|
||||
/**
|
||||
* 外力を適応しないボーンを指定
|
||||
* Specify the bones to which the external force will be NOT applied
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce")
|
||||
TArray<FBoneReference> IgnoreBoneFilter;
|
||||
|
||||
/** The space in which the external force is simulated */
|
||||
UPROPERTY(EditAnywhere, meta=(DisplayPriority=1, EditCondition=bCanSelectForceSpace, EditConditionHides),
|
||||
Category="KawaiiPhysics|ExternalForce")
|
||||
EExternalForceSpace ExternalForceSpace = EExternalForceSpace::WorldSpace;
|
||||
|
||||
/** Range for randomizing the force scale */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce")
|
||||
FFloatInterval RandomForceScaleRange = FFloatInterval(1.0f, 1.0f);
|
||||
|
||||
/** Owner of the external force */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> ExternalOwner;
|
||||
|
||||
/** Whether the external force is applied only once */
|
||||
UPROPERTY()
|
||||
bool bIsOneShot = false;
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
/** Length of the debug arrow */
|
||||
float DebugArrowLength = 5.0f;
|
||||
|
||||
/** Size of the debug arrow */
|
||||
float DebugArrowSize = 1.0f;
|
||||
|
||||
/** Offset for the debug arrow */
|
||||
FVector DebugArrowOffset = FVector::Zero();
|
||||
|
||||
/** Map of bone names to forces for debugging */
|
||||
TMap<FName, FVector> BoneForceMap;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/** Randomized scale of the force */
|
||||
UPROPERTY()
|
||||
float RandomizedForceScale = 0.0f;
|
||||
|
||||
/** The force vector */
|
||||
UPROPERTY()
|
||||
FVector Force = FVector::Zero();
|
||||
|
||||
/** Transform of the component */
|
||||
UPROPERTY()
|
||||
FTransform ComponentTransform;
|
||||
|
||||
/** Whether the force space can be selected */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
bool bCanSelectForceSpace = true;
|
||||
|
||||
public:
|
||||
virtual ~FKawaiiPhysics_ExternalForce() = default;
|
||||
|
||||
virtual void Initialize(const FAnimationInitializeContext& Context)
|
||||
{
|
||||
}
|
||||
|
||||
/** Prepares the external force before applying it */
|
||||
virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp)
|
||||
{
|
||||
ComponentTransform = SkelComp->GetComponentTransform();
|
||||
RandomizedForceScale = FMath::RandRange(RandomForceScaleRange.Min, RandomForceScaleRange.Max);
|
||||
}
|
||||
|
||||
/** Applies the external force to a bone */
|
||||
virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM = FTransform::Identity)
|
||||
{
|
||||
}
|
||||
|
||||
/** Finalizes the external force after applying it */
|
||||
virtual void PostApply(FAnimNode_KawaiiPhysics& Node)
|
||||
{
|
||||
if (bIsOneShot)
|
||||
{
|
||||
Node.ExternalForces.RemoveAll([&](FInstancedStruct& InstancedStruct)
|
||||
{
|
||||
const auto* ExternalForcePtr = InstancedStruct.GetMutablePtr<FKawaiiPhysics_ExternalForce>();
|
||||
return ExternalForcePtr == this;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if debug information should be drawn */
|
||||
virtual bool IsDebugEnabled(bool bInPersona = false)
|
||||
{
|
||||
if (bInPersona)
|
||||
{
|
||||
return bDrawDebug && bIsEnabled;
|
||||
}
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
if (CVarAnimNodeKawaiiPhysicsDebug.GetValueOnAnyThread())
|
||||
{
|
||||
return bDrawDebug && bIsEnabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if ENABLE_ANIM_DEBUG
|
||||
/** Draws debug information for the external force */
|
||||
virtual void AnimDrawDebug(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext)
|
||||
{
|
||||
if (IsDebugEnabled() && !Force.IsZero())
|
||||
{
|
||||
const auto AnimInstanceProxy = PoseContext.AnimInstanceProxy;
|
||||
const FVector ModifyRootBoneLocationWS = AnimInstanceProxy->GetComponentTransform().TransformPosition(
|
||||
Bone.Location);
|
||||
|
||||
AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
|
||||
ModifyRootBoneLocationWS + DebugArrowOffset,
|
||||
ModifyRootBoneLocationWS + DebugArrowOffset + BoneForceMap.Find(Bone.BoneRef.BoneName)->GetSafeNormal()
|
||||
*
|
||||
DebugArrowLength,
|
||||
DebugArrowSize, FColor::Red, false, 0.f, 2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WITH_EDITOR
|
||||
/** Draws debug information for the external force in edit mode */
|
||||
virtual void AnimDrawDebugForEditMode(const FKawaiiPhysicsModifyBone& ModifyBone,
|
||||
const FAnimNode_KawaiiPhysics& Node, FPrimitiveDrawInterface* PDI)
|
||||
{
|
||||
if (IsDebugEnabled(true) && CanApply(ModifyBone) && !Force.IsNearlyZero() && BoneForceMap.Contains(
|
||||
ModifyBone.BoneRef.BoneName))
|
||||
{
|
||||
const FTransform ArrowTransform = FTransform(
|
||||
BoneForceMap.Find(ModifyBone.BoneRef.BoneName)->GetSafeNormal().ToOrientationRotator(),
|
||||
ModifyBone.Location + DebugArrowOffset);
|
||||
DrawDirectionalArrow(PDI, ArrowTransform.ToMatrixNoScale(), FColor::Red, DebugArrowLength, DebugArrowSize,
|
||||
SDPG_Foreground, 1.0f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/** Checks if the external force can be applied to a bone */
|
||||
bool CanApply(const FKawaiiPhysicsModifyBone& Bone) const
|
||||
{
|
||||
if (!ApplyBoneFilter.IsEmpty() && !ApplyBoneFilter.Contains(Bone.BoneRef))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IgnoreBoneFilter.IsEmpty() && IgnoreBoneFilter.Contains(Bone.BoneRef))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
///
|
||||
/// Basic
|
||||
///
|
||||
USTRUCT(BlueprintType, DisplayName = "Basic")
|
||||
struct KAWAIIPHYSICS_API FKawaiiPhysics_ExternalForce_Basic : public FKawaiiPhysics_ExternalForce
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Direction of the force */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
FVector ForceDir = FVector::Zero();
|
||||
|
||||
/**
|
||||
* 各ボーンに適用するForce Rateを補正。
|
||||
* 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算
|
||||
* Corrects the Force Rate applied to each bone.
|
||||
* Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
FRuntimeFloatCurve ForceRateByBoneLengthRate;
|
||||
|
||||
/** Interval for applying the force */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
float Interval = 0.0f;
|
||||
|
||||
virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override;
|
||||
virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext,
|
||||
const FTransform& BoneTM = FTransform::Identity) override;
|
||||
|
||||
private:
|
||||
/** Current time */
|
||||
UPROPERTY()
|
||||
float Time = 0.0f;
|
||||
|
||||
/** Previous time */
|
||||
UPROPERTY()
|
||||
float PrevTime = 0.0f;
|
||||
};
|
||||
|
||||
///
|
||||
/// Gravity
|
||||
///
|
||||
USTRUCT(BlueprintType, DisplayName = "Gravity")
|
||||
struct KAWAIIPHYSICS_API FKawaiiPhysics_ExternalForce_Gravity : public FKawaiiPhysics_ExternalForce
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FKawaiiPhysics_ExternalForce_Gravity()
|
||||
{
|
||||
bCanSelectForceSpace = false;
|
||||
ExternalForceSpace = EExternalForceSpace::WorldSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 各ボーンに適用するForce Rateを補正。
|
||||
* 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算
|
||||
* Corrects the Force Rate applied to each bone.
|
||||
* Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
FRuntimeFloatCurve ForceRateByBoneLengthRate;
|
||||
|
||||
/**
|
||||
* Character側で設定されたCustomGravityDirectionを使用するフラグ(UE5.4以降)
|
||||
* Flag to use CustomGravityDirection set on the Character side (UE5.4 and later)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
bool bUseCharacterGravityDirection = false;
|
||||
|
||||
/**
|
||||
* Character側で設定されたGravityScaleを使用するフラグ
|
||||
* Flag to use GravityScale set on the Character side
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
bool bUseCharacterGravityScale = false;
|
||||
|
||||
/**
|
||||
* Direction to override the gravity.
|
||||
* This direction is used when bUseOverrideGravityDirection is true.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,
|
||||
meta = (EditCondition = "bUseOverrideGravityDirection"), Category="KawaiiPhysics|ExternalForce")
|
||||
FVector OverrideGravityDirection = FVector::Zero();
|
||||
|
||||
/**
|
||||
* Flag to determine whether to use the override gravity direction.
|
||||
* If true, the gravity direction will be overridden by OverrideGravityDirection.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (InlineEditConditionToggle),
|
||||
Category="KawaiiPhysics|ExternalForce")
|
||||
bool bUseOverrideGravityDirection = false;
|
||||
|
||||
virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override;
|
||||
virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext,
|
||||
const FTransform& BoneTM = FTransform::Identity) override;
|
||||
};
|
||||
|
||||
///
|
||||
/// Curve
|
||||
///
|
||||
USTRUCT(BlueprintType, DisplayName = "Curve")
|
||||
struct KAWAIIPHYSICS_API FKawaiiPhysics_ExternalForce_Curve : public FKawaiiPhysics_ExternalForce
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* 時間に応じて変化する外力をカーブで設定。X軸:Time Y軸:Force
|
||||
* Set the external force that changes over time using a curve. X-axis: Time Y-axis: Force
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (XAxisName="Time", YAxisName="Force"),
|
||||
Category="KawaiiPhysics|ExternalForce")
|
||||
FRuntimeVectorCurve ForceCurve;
|
||||
|
||||
/**
|
||||
* カーブの評価方式。
|
||||
* Single以外に設定した場合:前フレームからの経過時間をSubstepCountで分割し、
|
||||
* 分割後の各時間におけるカーブの値の平均・最大値・最小値を外力として使用
|
||||
* Curve evaluation method
|
||||
* If set to anything other than Single: The time elapsed from the previous frame is divided by SubstepCount,
|
||||
* and the Average, Maximum, or Minimum values of the curve at each time point after division are used as external forces.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
EExternalForceCurveEvaluateType CurveEvaluateType = EExternalForceCurveEvaluateType::Single;
|
||||
|
||||
/**
|
||||
* 経過時間の分割数
|
||||
* Number of divisions of elapsed time
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,
|
||||
meta=(EditCondition="CurveEvaluateType!=EExternalForceCurveEvaluateType::Single"),
|
||||
Category="KawaiiPhysics|ExternalForce")
|
||||
int SubstepCount = 10;
|
||||
|
||||
/**
|
||||
* Scale factor for the time.
|
||||
* This value is used to scale the time for the external force.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category ="KawaiiPhysics|ExternalForce")
|
||||
float TimeScale = 1.0f;
|
||||
|
||||
/**
|
||||
* 各ボーンに適用するForce Rateを補正。
|
||||
* 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算
|
||||
* Corrects the Force Rate applied to each bone.
|
||||
* Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
FRuntimeFloatCurve ForceRateByBoneLengthRate;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Current time.
|
||||
* This value is used to track the current time for the external force.
|
||||
*/
|
||||
UPROPERTY()
|
||||
float Time = 0.0f;
|
||||
|
||||
/**
|
||||
* Previous time.
|
||||
* This value is used to track the previous time for the external force.
|
||||
*/
|
||||
UPROPERTY()
|
||||
float PrevTime = 0.0f;
|
||||
|
||||
/**
|
||||
* Maximum curve time.
|
||||
* This value is used to track the maximum time for the force curve.
|
||||
*/
|
||||
UPROPERTY()
|
||||
float MaxCurveTime = 0.0f;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initializes the maximum curve time.
|
||||
* This function calculates the maximum time value from the ForceCurve and sets it to MaxCurveTime.
|
||||
*/
|
||||
void InitMaxCurveTime();
|
||||
|
||||
virtual void Initialize(const FAnimationInitializeContext& Context) override;
|
||||
virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override;
|
||||
virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext,
|
||||
const FTransform& BoneTM = FTransform::Identity) override;
|
||||
};
|
||||
|
||||
///
|
||||
/// Wind
|
||||
///
|
||||
USTRUCT(BlueprintType, DisplayName = "Wind")
|
||||
struct KAWAIIPHYSICS_API FKawaiiPhysics_ExternalForce_Wind : public FKawaiiPhysics_ExternalForce
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FKawaiiPhysics_ExternalForce_Wind()
|
||||
{
|
||||
bCanSelectForceSpace = false;
|
||||
ExternalForceSpace = EExternalForceSpace::WorldSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 各ボーンに適用するForce Rateを補正。
|
||||
* 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算
|
||||
* Corrects the Force Rate applied to each bone.
|
||||
* Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce")
|
||||
FRuntimeFloatCurve ForceRateByBoneLengthRate;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Pointer to the world.
|
||||
* This is used to access the world context for the external force.
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UWorld> World;
|
||||
|
||||
public:
|
||||
virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override;
|
||||
virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node,
|
||||
const FComponentSpacePoseContext& PoseContext,
|
||||
const FTransform& BoneTM = FTransform::Identity) override;
|
||||
};
|
||||
@@ -0,0 +1,584 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "KawaiiPhysicsExternalForce.h"
|
||||
#include "Animation/AnimNodeReference.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "KawaiiPhysicsLibrary.generated.h"
|
||||
|
||||
UENUM()
|
||||
enum class EKawaiiPhysicsAccessExternalForceResult : uint8
|
||||
{
|
||||
Valid,
|
||||
NotValid,
|
||||
};
|
||||
|
||||
#define KAWAIIPHYSICS_VALUE_SETTER(PropertyType, PropertyName) \
|
||||
{ \
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>( \
|
||||
TEXT("Set" #PropertyName), \
|
||||
[PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) { \
|
||||
InKawaiiPhysics.PropertyName = PropertyName; \
|
||||
}); \
|
||||
return KawaiiPhysics; \
|
||||
}
|
||||
|
||||
#define KAWAIIPHYSICS_VALUE_GETTER(PropertyType, PropertyName) \
|
||||
{ \
|
||||
PropertyType Value; \
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>( \
|
||||
TEXT("Get" #PropertyName), \
|
||||
[&Value](FAnimNode_KawaiiPhysics& InKawaiiPhysics) { \
|
||||
Value = InKawaiiPhysics.PropertyName; \
|
||||
}); \
|
||||
return Value; \
|
||||
}
|
||||
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FKawaiiPhysicsReference : public FAnimNodeReference
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
using FInternalNodeType = FAnimNode_KawaiiPhysics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exposes operations to be performed on a blend space anim node.
|
||||
*/
|
||||
UCLASS()
|
||||
class KAWAIIPHYSICS_API UKawaiiPhysicsLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Get a KawaiiPhysics from an anim node */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta = (BlueprintThreadSafe, ExpandEnumAsExecs = "Result"))
|
||||
static FKawaiiPhysicsReference ConvertToKawaiiPhysics(const FAnimNodeReference& Node,
|
||||
EAnimNodeReferenceConversionResult& Result);
|
||||
|
||||
/** Get a KawaiiPhysics from an anim node (pure). */
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics",
|
||||
meta = (BlueprintThreadSafe, DisplayName = "Convert to Kawaii Physics (Pure)"))
|
||||
static void ConvertToKawaiiPhysicsPure(const FAnimNodeReference& Node, FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
bool& Result)
|
||||
{
|
||||
EAnimNodeReferenceConversionResult ConversionResult;
|
||||
KawaiiPhysics = ConvertToKawaiiPhysics(Node, ConversionResult);
|
||||
Result = (ConversionResult == EAnimNodeReferenceConversionResult::Succeeded);
|
||||
}
|
||||
|
||||
/** Collect KawaiiPhysics Node References from AnimInstance(ABP) */
|
||||
static bool CollectKawaiiPhysicsNodes(TArray<FKawaiiPhysicsReference>& Nodes,
|
||||
UAnimInstance* AnimInstance, const FGameplayTagContainer& FilterTags,
|
||||
bool bFilterExactMatch);
|
||||
|
||||
/** Collect KawaiiPhysics Node References from SkeletalMeshComponent */
|
||||
static bool CollectKawaiiPhysicsNodes(TArray<FKawaiiPhysicsReference>& Nodes,
|
||||
USkeletalMeshComponent* MeshComp, const FGameplayTagContainer& FilterTags,
|
||||
bool bFilterExactMatch);
|
||||
|
||||
/** ResetDynamics */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference ResetDynamics(const FKawaiiPhysicsReference& KawaiiPhysics);
|
||||
|
||||
/** Set RootBone */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
UPARAM(ref) FName& RootBoneName);
|
||||
/** Get RootBone */
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FName GetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics);
|
||||
|
||||
/** Set ExcludeBones */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
UPARAM(ref) TArray<FName>& ExcludeBoneNames);
|
||||
/** Get ExcludeBones */
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static TArray<FName> GetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics);
|
||||
|
||||
// PhysicsSettings
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetPhysicsSettings(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
UPARAM(ref) FKawaiiPhysicsSettings& PhysicsSettings)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(FKawaiiPhysicsSettings, PhysicsSettings);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsSettings GetPhysicsSettings(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(FKawaiiPhysicsSettings, PhysicsSettings);
|
||||
}
|
||||
|
||||
// DummyBoneLength
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetDummyBoneLength(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
float DummyBoneLength)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(float, DummyBoneLength);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static float GetDummyBoneLength(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(float, DummyBoneLength);
|
||||
}
|
||||
|
||||
/** TeleportDistanceThreshold */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetTeleportDistanceThreshold(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
float TeleportDistanceThreshold)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(float, TeleportDistanceThreshold);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static float GetTeleportDistanceThreshold(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(float, TeleportDistanceThreshold);
|
||||
}
|
||||
|
||||
/** TeleportRotationThreshold */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetTeleportRotationThreshold(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
float TeleportRotationThreshold)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(float, TeleportRotationThreshold);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static float GetTeleportRotationThreshold(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(float, TeleportRotationThreshold);
|
||||
}
|
||||
|
||||
/** Gravity */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetGravity(const FKawaiiPhysicsReference& KawaiiPhysics, FVector Gravity)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(FVector, Gravity);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FVector GetGravity(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(FVector, Gravity);
|
||||
}
|
||||
|
||||
/** EnableWind */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetEnableWind(const FKawaiiPhysicsReference& KawaiiPhysics, bool bEnableWind)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(bool, bEnableWind);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static bool GetEnableWind(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(bool, bEnableWind);
|
||||
}
|
||||
|
||||
/** WindScale */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetWindScale(const FKawaiiPhysicsReference& KawaiiPhysics, float WindScale)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(float, WindScale);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static float GetWindScale(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(float, WindScale);
|
||||
}
|
||||
|
||||
/** AllowWorldCollision */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetAllowWorldCollision(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
bool bAllowWorldCollision)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(bool, bAllowWorldCollision);
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static bool GetAllowWorldCollision(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(bool, bAllowWorldCollision);
|
||||
}
|
||||
|
||||
/** NeedWarmUp */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetNeedWarmUp(const FKawaiiPhysicsReference& KawaiiPhysics, bool bNeedWarmUp)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(bool, bNeedWarmUp);
|
||||
}
|
||||
|
||||
/** NeedWarmUp */
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static bool GetNeedWarmUp(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(bool, bNeedWarmUp);
|
||||
}
|
||||
|
||||
/** LimitsDataAsset */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static FKawaiiPhysicsReference SetLimitsDataAsset(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
UKawaiiPhysicsLimitsDataAsset* LimitsDataAsset)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_SETTER(TObjectPtr<UKawaiiPhysicsLimitsDataAsset>, LimitsDataAsset);
|
||||
}
|
||||
|
||||
/** LimitsDataAsset */
|
||||
UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static UKawaiiPhysicsLimitsDataAsset* GetLimitsDataAsset(const FKawaiiPhysicsReference& KawaiiPhysics)
|
||||
{
|
||||
KAWAIIPHYSICS_VALUE_GETTER(TObjectPtr<UKawaiiPhysicsLimitsDataAsset>, LimitsDataAsset);
|
||||
}
|
||||
|
||||
/** Add ExternalForce With ExecResult */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference AddExternalForceWithExecResult(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
FInstancedStruct& ExternalForce, UObject* Owner);
|
||||
|
||||
/** Add ExternalForce */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static bool AddExternalForce(const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
FInstancedStruct& ExternalForce, UObject* Owner, bool bIsOneShot = false);
|
||||
|
||||
/** Add ExternalForces to SkeletalMeshComponent */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static bool AddExternalForcesToComponent(USkeletalMeshComponent* MeshComp,
|
||||
UPARAM(ref) TArray<FInstancedStruct>& ExternalForces, UObject* Owner,
|
||||
UPARAM(ref) FGameplayTagContainer& FilterTags,
|
||||
bool bFilterExactMatch = false,
|
||||
bool bIsOneShot = false);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe))
|
||||
static bool RemoveExternalForcesFromComponent(USkeletalMeshComponent* MeshComp, UObject* Owner,
|
||||
UPARAM(ref) FGameplayTagContainer& FilterTags,
|
||||
bool bFilterExactMatch = false);
|
||||
|
||||
|
||||
/** Set ExternalForceParameter template */
|
||||
template <typename ValueType, typename PropertyType>
|
||||
static FKawaiiPhysicsReference SetExternalForceProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
ValueType Value);
|
||||
/** Get ExternalForceParameter template */
|
||||
template <typename ValueType>
|
||||
static ValueType GetExternalForceProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName);
|
||||
|
||||
/** Set ExternalForceParameter template struct */
|
||||
template <typename ValueType>
|
||||
static FKawaiiPhysicsReference SetExternalForceStructProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
ValueType Value);
|
||||
/** Get ExternalForceParameter template struct */
|
||||
template <typename ValueType>
|
||||
static ValueType GetExternalForceStructProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex,
|
||||
FName PropertyName);
|
||||
|
||||
/** Set ExternalForceParameter bool */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference SetExternalForceBoolProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
bool Value)
|
||||
{
|
||||
return SetExternalForceProperty<bool, FBoolProperty>(ExecResult, KawaiiPhysics, ExternalForceIndex,
|
||||
PropertyName, Value);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter bool */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static bool GetExternalForceBoolProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName)
|
||||
{
|
||||
return GetExternalForceProperty<bool>(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName);
|
||||
}
|
||||
|
||||
/** Set ExternalForceParameter int */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference SetExternalForceIntProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
int32 Value)
|
||||
{
|
||||
return SetExternalForceProperty<int32, FIntProperty>(ExecResult, KawaiiPhysics, ExternalForceIndex,
|
||||
PropertyName, Value);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter int */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static int32 GetExternalForceIntProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName)
|
||||
{
|
||||
return GetExternalForceProperty<int32>(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName);
|
||||
}
|
||||
|
||||
/** Set ExternalForceParameter float */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference SetExternalForceFloatProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
float Value)
|
||||
{
|
||||
return SetExternalForceProperty<float, FFloatProperty>(ExecResult, KawaiiPhysics, ExternalForceIndex,
|
||||
PropertyName, Value);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter float */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static float GetExternalForceFloatProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName)
|
||||
{
|
||||
return GetExternalForceProperty<float>(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter Vector */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference SetExternalForceVectorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
FVector Value)
|
||||
{
|
||||
return SetExternalForceStructProperty<FVector>(ExecResult, KawaiiPhysics, ExternalForceIndex,
|
||||
PropertyName, Value);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter Vector */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FVector GetExternalForceVectorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName)
|
||||
{
|
||||
return GetExternalForceStructProperty<FVector>(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter Rotator */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference SetExternalForceRotatorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
FRotator Value)
|
||||
{
|
||||
return SetExternalForceStructProperty<FRotator>(ExecResult, KawaiiPhysics, ExternalForceIndex,
|
||||
PropertyName, Value);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter Rotator */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FRotator GetExternalForceRotatorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex,
|
||||
FName PropertyName)
|
||||
{
|
||||
return GetExternalForceStructProperty<FRotator>(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter Transform */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FKawaiiPhysicsReference SetExternalForceTransformProperty(
|
||||
EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName,
|
||||
FTransform Value)
|
||||
{
|
||||
return SetExternalForceStructProperty<FTransform>(ExecResult, KawaiiPhysics, ExternalForceIndex,
|
||||
PropertyName, Value);
|
||||
}
|
||||
|
||||
/** Get ExternalForceParameter Transform */
|
||||
UFUNCTION(BlueprintCallable, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult"))
|
||||
static FTransform GetExternalForceTransformProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex,
|
||||
FName PropertyName)
|
||||
{
|
||||
return GetExternalForceStructProperty<FTransform>(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName);
|
||||
}
|
||||
|
||||
/** Set ExternalForceParameter Wildcard */
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult", CustomStructureParam = "Value"))
|
||||
static void SetExternalForceWildcardProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName, const int32& Value)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
|
||||
/** Get ExternalForceParameter Wildcard */
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, Category = "Kawaii Physics",
|
||||
meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult", CustomStructureParam = "Value"))
|
||||
static void GetExternalForceWildcardProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex,
|
||||
FName PropertyName, int32& Value)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
private:
|
||||
DECLARE_FUNCTION(execSetExternalForceWildcardProperty);
|
||||
DECLARE_FUNCTION(execGetExternalForceWildcardProperty);
|
||||
};
|
||||
|
||||
template <typename ValueType, typename PropertyType>
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetExternalForceProperty(
|
||||
EKawaiiPhysicsAccessExternalForceResult& ExecResult, const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName, ValueType Value)
|
||||
{
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("SetExternalForceProperty"),
|
||||
[&ExecResult, &ExternalForceIndex, &PropertyName, &Value](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) &&
|
||||
InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid())
|
||||
{
|
||||
const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct();
|
||||
auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable<
|
||||
FKawaiiPhysics_ExternalForce>();
|
||||
|
||||
if (const PropertyType* Property = FindFProperty<PropertyType>(ScriptStruct, PropertyName))
|
||||
{
|
||||
if (void* ValuePtr = Property->template ContainerPtrToValuePtr<uint8>(&Force))
|
||||
{
|
||||
Property->SetPropertyValue(ValuePtr, Value);
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return KawaiiPhysics;
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
ValueType UKawaiiPhysicsLibrary::GetExternalForceProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName)
|
||||
{
|
||||
ValueType Result;
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("GetExternalForceProperty"),
|
||||
[&Result, &ExecResult, &ExternalForceIndex, &PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) &&
|
||||
InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid())
|
||||
{
|
||||
const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct();
|
||||
const auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable<
|
||||
FKawaiiPhysics_ExternalForce>();
|
||||
|
||||
if (const FProperty* Property = FindFProperty<FProperty>(ScriptStruct, PropertyName))
|
||||
{
|
||||
Result = *(Property->ContainerPtrToValuePtr<ValueType>(&Force));
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetExternalForceStructProperty(
|
||||
EKawaiiPhysicsAccessExternalForceResult& ExecResult, const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName, ValueType Value)
|
||||
{
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("SetExternalForceStructProperty"),
|
||||
[&ExecResult, &ExternalForceIndex, &PropertyName, &Value](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) &&
|
||||
InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid())
|
||||
{
|
||||
const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct();
|
||||
auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable<
|
||||
FKawaiiPhysics_ExternalForce>();
|
||||
|
||||
if (const FStructProperty* StructProperty = FindFProperty<FStructProperty>(
|
||||
ScriptStruct, PropertyName))
|
||||
{
|
||||
if (StructProperty->Struct == TBaseStructure<ValueType>::Get())
|
||||
{
|
||||
if (void* ValuePtr = StructProperty->ContainerPtrToValuePtr<uint8>(&Force))
|
||||
{
|
||||
StructProperty->CopyCompleteValue(ValuePtr, &Value);
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return KawaiiPhysics;
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
ValueType UKawaiiPhysicsLibrary::GetExternalForceStructProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult,
|
||||
const FKawaiiPhysicsReference& KawaiiPhysics,
|
||||
int ExternalForceIndex, FName PropertyName)
|
||||
{
|
||||
ValueType Result;
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid;
|
||||
|
||||
KawaiiPhysics.CallAnimNodeFunction<FAnimNode_KawaiiPhysics>(
|
||||
TEXT("GetExternalForceStructProperty"),
|
||||
[&Result, &ExecResult, &ExternalForceIndex, &PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics)
|
||||
{
|
||||
if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) &&
|
||||
InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid())
|
||||
{
|
||||
const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct();
|
||||
const auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable<
|
||||
FKawaiiPhysics_ExternalForce>();
|
||||
|
||||
if (const FStructProperty* StructProperty = FindFProperty<FStructProperty>(
|
||||
ScriptStruct, PropertyName))
|
||||
{
|
||||
if (StructProperty->Struct == TBaseStructure<ValueType>::Get())
|
||||
{
|
||||
Result = *(StructProperty->ContainerPtrToValuePtr<ValueType>(&Force));
|
||||
ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "Interfaces/Interface_BoneReferenceSkeletonProvider.h"
|
||||
#include "KawaiiPhysicsLimitsDataAsset.generated.h"
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnLimitsChanged, struct FPropertyChangedEvent&);
|
||||
|
||||
// Deprecated
|
||||
USTRUCT()
|
||||
struct FCollisionLimitDataBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FBoneReference DrivingBoneReference;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FName DrivingBoneName;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FVector OffsetLocation = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FRotator OffsetRotation = FRotator::ZeroRotator;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FVector Location = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FQuat Rotation = FQuat::Identity;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty, IgnoreForMemberInitializationTest))
|
||||
FGuid Guid = FGuid::NewGuid();
|
||||
|
||||
protected:
|
||||
void ConvertBase(FCollisionLimitBase& Limit) const
|
||||
{
|
||||
Limit.DrivingBone.BoneName = DrivingBoneReference.BoneName;
|
||||
Limit.OffsetLocation = OffsetLocation;
|
||||
Limit.OffsetRotation = OffsetRotation;
|
||||
Limit.Location = Location;
|
||||
Limit.Rotation = Rotation;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
Limit.SourceType = ECollisionSourceType::DataAsset;
|
||||
Limit.Guid = Guid;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecated
|
||||
USTRUCT()
|
||||
struct FSphericalLimitData : public FCollisionLimitDataBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Radius of the sphere */
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
float Radius = 5.0f;
|
||||
|
||||
/** Whether to lock bodies inside or outside of the sphere */
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
ESphericalLimitType LimitType = ESphericalLimitType::Outer;
|
||||
|
||||
FSphericalLimit Convert() const
|
||||
{
|
||||
FSphericalLimit Limit;
|
||||
ConvertBase(Limit);
|
||||
Limit.Radius = Radius;
|
||||
Limit.LimitType = LimitType;
|
||||
|
||||
return Limit;
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecated
|
||||
USTRUCT()
|
||||
struct FCapsuleLimitData : public FCollisionLimitDataBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
float Radius = 5.0f;
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
float Length = 10.0f;
|
||||
|
||||
FCapsuleLimit Convert() const
|
||||
{
|
||||
FCapsuleLimit Limit;
|
||||
ConvertBase(Limit);
|
||||
Limit.Radius = Radius;
|
||||
Limit.Length = Length;
|
||||
|
||||
return Limit;
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecated
|
||||
USTRUCT()
|
||||
struct FBoxLimitData : public FCollisionLimitDataBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FVector Extent = FVector(5.0f, 5.0f, 5.0f);
|
||||
|
||||
FBoxLimit Convert() const
|
||||
{
|
||||
FBoxLimit Limit;
|
||||
ConvertBase(Limit);
|
||||
Limit.Extent = Extent;
|
||||
|
||||
return Limit;
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecated
|
||||
USTRUCT()
|
||||
struct FPlanarLimitData : public FCollisionLimitDataBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
FPlane Plane = FPlane(0, 0, 0, 0);
|
||||
|
||||
FPlanarLimit Convert() const
|
||||
{
|
||||
FPlanarLimit Limit;
|
||||
ConvertBase(Limit);
|
||||
Limit.Plane = Plane;
|
||||
|
||||
return Limit;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class KAWAIIPHYSICS_API UKawaiiPhysicsLimitsDataAsset : public UDataAsset, public IBoneReferenceSkeletonProvider
|
||||
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
#if WITH_EDITORONLY_DATA
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skeleton")
|
||||
TObjectPtr<USkeleton> Skeleton;
|
||||
|
||||
// Deprecated
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
TArray<FSphericalLimitData> SphericalLimitsData;
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
TArray<FCapsuleLimitData> CapsuleLimitsData;
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
TArray<FBoxLimitData> BoxLimitsData;
|
||||
UPROPERTY(meta=(DeprecatedProperty))
|
||||
TArray<FPlanarLimitData> PlanarLimitsData;
|
||||
|
||||
#endif
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spherical Limits")
|
||||
TArray<FSphericalLimit> SphericalLimits;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capsule Limits")
|
||||
TArray<FCapsuleLimit> CapsuleLimits;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Box Limits")
|
||||
TArray<FBoxLimit> BoxLimits;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Planar Limits")
|
||||
TArray<FPlanarLimit> PlanarLimits;
|
||||
|
||||
// Begin UObject Interface.
|
||||
#if WITH_EDITORONLY_DATA
|
||||
virtual void Serialize(FStructuredArchiveRecord Record) override;
|
||||
#endif
|
||||
virtual void PostLoad() override;
|
||||
// End UObject Interface.
|
||||
|
||||
// IBoneReferenceSkeletonProvider interface
|
||||
virtual USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError, const IPropertyHandle* PropertyHandle) override;
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UpdateLimit(FCollisionLimitBase* Limit);
|
||||
|
||||
FOnLimitsChanged OnLimitsChanged;
|
||||
virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if WITH_EDITOR
|
||||
void Sync();
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class KawaiiPhysicsEd : ModuleRules
|
||||
{
|
||||
public KawaiiPhysicsEd(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"InputCore",
|
||||
"KawaiiPhysics",
|
||||
"AnimGraph",
|
||||
"BlueprintGraph",
|
||||
"Persona",
|
||||
"UnrealEd",
|
||||
"AnimGraphRuntime",
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
});
|
||||
|
||||
if(Target.Version.MajorVersion >= 5)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("EditorFramework");
|
||||
if (Target.Version.MinorVersion >= 1)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("AnimationEditMode");
|
||||
}
|
||||
|
||||
// StructUtils plugin has been integrated into the engine starting from 5.5
|
||||
if (Target.Version.MinorVersion <= 4)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("StructUtils");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "AnimGraphNode_KawaiiPhysics.h"
|
||||
|
||||
#include "Subsystems/AssetEditorSubsystem.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "DetailCategoryBuilder.h"
|
||||
#include "DetailLayoutBuilder.h"
|
||||
#include "DetailWidgetRow.h"
|
||||
#include "KawaiiPhysicsBoneConstraintsDataAsset.h"
|
||||
#include "KawaiiPhysicsLimitsDataAsset.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Framework/Notifications/NotificationManager.h"
|
||||
#include "Selection.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Notifications/SNotificationList.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Dialogs/DlgPickAssetPath.h"
|
||||
#include "Kismet2/CompilerResultsLog.h"
|
||||
#include "Widgets/Layout/SUniformGridPanel.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimGraphNode_KawaiiPhysics)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "KawaiiPhysics"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
UAnimGraphNode_KawaiiPhysics::UAnimGraphNode_KawaiiPhysics(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
FText UAnimGraphNode_KawaiiPhysics::GetControllerDescription() const
|
||||
{
|
||||
return LOCTEXT("Kawaii Physics", "Kawaii Physics");
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
FText UAnimGraphNode_KawaiiPhysics::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
if ((TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle))
|
||||
{
|
||||
return GetControllerDescription();
|
||||
}
|
||||
// @TODO: the bone can be altered in the property editor, so we have to
|
||||
// choose to mark this dirty when that happens for this to properly work
|
||||
//if (!CachedNodeTitles.IsTitleCached(TitleType, this))
|
||||
|
||||
FFormatNamedArguments Args;
|
||||
Args.Add(TEXT("ControllerDescription"), GetControllerDescription());
|
||||
Args.Add(TEXT("RootBoneName"), FText::FromName(Node.RootBone.BoneName));
|
||||
Args.Add(TEXT("Tag"), FText::FromString(Node.KawaiiPhysicsTag.ToString()));
|
||||
|
||||
// FText::Format() is slow, so we cache this to save on performance
|
||||
if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
|
||||
{
|
||||
const FText Title = Node.KawaiiPhysicsTag.IsValid()
|
||||
? FText::Format(
|
||||
LOCTEXT("AnimGraphNode_KawaiiPhysics_ListTitle",
|
||||
"{ControllerDescription} - Root: {RootBoneName} - Tag: {Tag}"), Args)
|
||||
: FText::Format(
|
||||
LOCTEXT("AnimGraphNode_KawaiiPhysics_ListTitle",
|
||||
"{ControllerDescription} - Root: {RootBoneName}"), Args);
|
||||
|
||||
CachedNodeTitles.SetCachedTitle(TitleType, Title, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
const FText Title = Node.KawaiiPhysicsTag.IsValid()
|
||||
? FText::Format(
|
||||
LOCTEXT("AnimGraphNode_KawaiiPhysics_Title",
|
||||
"{ControllerDescription}\nRoot: {RootBoneName}\nTag: {Tag} "), Args)
|
||||
: FText::Format(
|
||||
LOCTEXT("AnimGraphNode_KawaiiPhysics_Title",
|
||||
"{ControllerDescription}\nRoot: {RootBoneName}"), Args);
|
||||
|
||||
CachedNodeTitles.SetCachedTitle(TitleType, Title, this);
|
||||
}
|
||||
return CachedNodeTitles[TitleType];
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||
|
||||
Node.ModifyBones.Empty();
|
||||
ReconstructNode();
|
||||
}
|
||||
|
||||
FEditorModeID UAnimGraphNode_KawaiiPhysics::GetEditorMode() const
|
||||
{
|
||||
return "AnimGraph.SkeletalControl.KawaiiPhysics";
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::ValidateAnimNodePostCompile(FCompilerResultsLog& MessageLog,
|
||||
UAnimBlueprintGeneratedClass* CompiledClass,
|
||||
int32 CompiledNodeIndex)
|
||||
{
|
||||
UAnimGraphNode_SkeletalControlBase::ValidateAnimNodePostCompile(MessageLog, CompiledClass, CompiledNodeIndex);
|
||||
|
||||
Node.RootBone.Initialize(CompiledClass->TargetSkeleton);
|
||||
if (Node.RootBone.BoneIndex >= 0)
|
||||
{
|
||||
if (Node.ExcludeBones.Contains(Node.RootBone))
|
||||
{
|
||||
MessageLog.Warning(TEXT("@@ ExcludeBones should NOT has RootBone."), this);
|
||||
}
|
||||
}
|
||||
// for template ABP
|
||||
else if (CompiledClass->TargetSkeleton)
|
||||
{
|
||||
MessageLog.Warning(TEXT("@@ RootBone is empty."), this);
|
||||
}
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::CopyNodeDataToPreviewNode(FAnimNode_Base* AnimNode)
|
||||
{
|
||||
FAnimNode_KawaiiPhysics* KawaiiPhysics = static_cast<FAnimNode_KawaiiPhysics*>(AnimNode);
|
||||
|
||||
// pushing properties to preview instance, for live editing
|
||||
// Default
|
||||
KawaiiPhysics->RootBone = Node.RootBone;
|
||||
KawaiiPhysics->ExcludeBones = Node.ExcludeBones;
|
||||
KawaiiPhysics->AdditionalRootBones = Node.AdditionalRootBones;
|
||||
KawaiiPhysics->TargetFramerate = Node.TargetFramerate;
|
||||
KawaiiPhysics->OverrideTargetFramerate = Node.OverrideTargetFramerate;
|
||||
|
||||
// Physics Settings
|
||||
KawaiiPhysics->PhysicsSettings = Node.PhysicsSettings;
|
||||
KawaiiPhysics->DampingCurveData = Node.DampingCurveData;
|
||||
KawaiiPhysics->WorldDampingLocationCurveData = Node.WorldDampingLocationCurveData;
|
||||
KawaiiPhysics->WorldDampingRotationCurveData = Node.WorldDampingRotationCurveData;
|
||||
KawaiiPhysics->StiffnessCurveData = Node.StiffnessCurveData;
|
||||
KawaiiPhysics->RadiusCurveData = Node.RadiusCurveData;
|
||||
KawaiiPhysics->LimitAngleCurveData = Node.LimitAngleCurveData;
|
||||
KawaiiPhysics->bUpdatePhysicsSettingsInGame = Node.bUpdatePhysicsSettingsInGame;
|
||||
KawaiiPhysics->PlanarConstraint = Node.PlanarConstraint;
|
||||
KawaiiPhysics->ResetBoneTransformWhenBoneNotFound = Node.ResetBoneTransformWhenBoneNotFound;
|
||||
|
||||
// DummyBone
|
||||
KawaiiPhysics->DummyBoneLength = Node.DummyBoneLength;
|
||||
KawaiiPhysics->BoneForwardAxis = Node.BoneForwardAxis;
|
||||
|
||||
// Limits
|
||||
KawaiiPhysics->SphericalLimits = Node.SphericalLimits;
|
||||
KawaiiPhysics->CapsuleLimits = Node.CapsuleLimits;
|
||||
KawaiiPhysics->BoxLimits = Node.BoxLimits;
|
||||
KawaiiPhysics->PlanarLimits = Node.PlanarLimits;
|
||||
KawaiiPhysics->LimitsDataAsset = Node.LimitsDataAsset;
|
||||
KawaiiPhysics->PhysicsAssetForLimits = Node.PhysicsAssetForLimits;
|
||||
|
||||
// ExternalForce
|
||||
KawaiiPhysics->Gravity = Node.Gravity;
|
||||
KawaiiPhysics->ExternalForces = Node.ExternalForces;
|
||||
KawaiiPhysics->CustomExternalForces = Node.CustomExternalForces;
|
||||
|
||||
// Wind
|
||||
KawaiiPhysics->bEnableWind = Node.bEnableWind;
|
||||
KawaiiPhysics->WindScale = Node.WindScale;
|
||||
|
||||
// BoneConstraint
|
||||
KawaiiPhysics->BoneConstraintGlobalComplianceType = Node.BoneConstraintGlobalComplianceType;
|
||||
KawaiiPhysics->BoneConstraintIterationCountBeforeCollision = Node.BoneConstraintIterationCountBeforeCollision;
|
||||
KawaiiPhysics->BoneConstraintIterationCountAfterCollision = Node.BoneConstraintIterationCountAfterCollision;
|
||||
KawaiiPhysics->bAutoAddChildDummyBoneConstraint = Node.bAutoAddChildDummyBoneConstraint;
|
||||
KawaiiPhysics->BoneConstraints = Node.BoneConstraints;
|
||||
KawaiiPhysics->BoneConstraintsDataAsset = Node.BoneConstraintsDataAsset;
|
||||
|
||||
// SimulationSpace
|
||||
KawaiiPhysics->SimulationSpace = Node.SimulationSpace;
|
||||
KawaiiPhysics->SimulationBaseBone = Node.SimulationBaseBone;
|
||||
|
||||
// Reset for sync without compile
|
||||
KawaiiPhysics->ModifyBones.Empty();
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::CustomizeDetailTools(IDetailLayoutBuilder& DetailBuilder)
|
||||
{
|
||||
IDetailCategoryBuilder& ViewportCategory = DetailBuilder.EditCategory(TEXT("Kawaii Physics Tools"));
|
||||
FDetailWidgetRow& WidgetRow = ViewportCategory.AddCustomRow(LOCTEXT("KawaiiPhysics", "KawaiiPhysicsTools"));
|
||||
|
||||
WidgetRow
|
||||
[
|
||||
SNew(SUniformGridPanel)
|
||||
.SlotPadding(FMargin(2, 0, 2, 0))
|
||||
+ SUniformGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->ExportLimitsDataAsset();
|
||||
return FReply::Handled();
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Export Limits")))
|
||||
]
|
||||
]
|
||||
+ SUniformGridPanel::Slot(1, 0)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->ExportBoneConstraintsDataAsset();
|
||||
return FReply::Handled();
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Export BoneConstraints")))
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::CustomizeDetailDebugVisualizations(IDetailLayoutBuilder& DetailBuilder)
|
||||
{
|
||||
IDetailCategoryBuilder& ViewportCategory = DetailBuilder.EditCategory(TEXT("Debug Visualization"));
|
||||
FDetailWidgetRow& WidgetRow = ViewportCategory.AddCustomRow(
|
||||
LOCTEXT("ToggleDebugVisualizationButtonRow", "DebugVisualization"));
|
||||
|
||||
WidgetRow
|
||||
[
|
||||
SNew(SUniformGridPanel)
|
||||
.SlotPadding(FMargin(2, 0, 2, 0))
|
||||
// Show/Hide Bones button.
|
||||
+ SUniformGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawBone = !this->bEnableDebugDrawBone;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawBone
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowBoneText", "Bone"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide LengthRate button.
|
||||
+ SUniformGridPanel::Slot(1, 0)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugBoneLengthRate = !this->bEnableDebugBoneLengthRate;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugBoneLengthRate
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowLengthRateText", "Length Rate"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide AngleLimit button.
|
||||
+ SUniformGridPanel::Slot(2, 0)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawLimitAngle = !this->bEnableDebugDrawLimitAngle;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawLimitAngle
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowLimitAngleText", "Limit Angle"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide SphereLimit button.
|
||||
+ SUniformGridPanel::Slot(0, 1)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawSphereLimit = !this->bEnableDebugDrawSphereLimit;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawSphereLimit
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowSphereLimitText", "Sphere Limit"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide CapsuleLimit button.
|
||||
+ SUniformGridPanel::Slot(1, 1)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawCapsuleLimit = !this->bEnableDebugDrawCapsuleLimit;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawCapsuleLimit
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowCapsuleLimitText", "Capsule Limit"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide BoxLimit button.
|
||||
+ SUniformGridPanel::Slot(2, 1)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawBoxLimit = !this->bEnableDebugDrawBoxLimit;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawBoxLimit
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowBoxLimitText", "Box Limit"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide PlanerLimit button.
|
||||
+ SUniformGridPanel::Slot(0, 2)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawPlanerLimit = !this->bEnableDebugDrawPlanerLimit;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawPlanerLimit
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowPlanerLimitText", "Planer Limit"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide BoneConstraint button.
|
||||
+ SUniformGridPanel::Slot(1, 2)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawBoneConstraint = !this->bEnableDebugDrawBoneConstraint;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawBoneConstraint
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowBoneConstraintText", "Bone Constraint"); })
|
||||
]
|
||||
]
|
||||
// Show/Hide ExternalForce button.
|
||||
+ SUniformGridPanel::Slot(2, 2)
|
||||
[
|
||||
SNew(SButton)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.OnClicked_Lambda([this]()
|
||||
{
|
||||
this->bEnableDebugDrawExternalForce = !this->bEnableDebugDrawExternalForce;
|
||||
return FReply::Handled();
|
||||
})
|
||||
.ButtonColorAndOpacity_Lambda([this]()
|
||||
{
|
||||
return this->bEnableDebugDrawExternalForce
|
||||
? FAppStyle::Get().GetSlateColor("Colors.AccentGreen")
|
||||
: FAppStyle::Get().GetSlateColor("Colors.AccentRed");
|
||||
})
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text_Lambda([this]() { return LOCTEXT("ShowExternalForceText", "External Force"); })
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
|
||||
{
|
||||
Super::CustomizeDetails(DetailBuilder);
|
||||
|
||||
CustomizeDetailTools(DetailBuilder);
|
||||
CustomizeDetailDebugVisualizations(DetailBuilder);
|
||||
|
||||
// Force order of details panel categories - Must set order for all of them as any that are edited automatically move to the top.
|
||||
auto CategorySorter = [](const TMap<FName, IDetailCategoryBuilder*>& Categories)
|
||||
{
|
||||
int32 Order = 0;
|
||||
auto SafeSetOrder = [&Categories, &Order](const FName& CategoryName)
|
||||
{
|
||||
if (IDetailCategoryBuilder* const* Builder = Categories.Find(CategoryName))
|
||||
{
|
||||
(*Builder)->SetSortOrder(Order++);
|
||||
}
|
||||
};
|
||||
|
||||
// Tools, Debug
|
||||
SafeSetOrder(FName("Kawaii Physics Tools"));
|
||||
SafeSetOrder(FName("Debug Visualization"));
|
||||
SafeSetOrder(FName("Functions"));
|
||||
|
||||
// Basic
|
||||
SafeSetOrder(FName("Bones"));
|
||||
SafeSetOrder(FName("Physics Settings"));
|
||||
SafeSetOrder(FName("Physics Settings Advanced"));
|
||||
|
||||
// Limits
|
||||
SafeSetOrder(FName("Limits"));
|
||||
SafeSetOrder(FName("Bone Constraint (Experimental)"));
|
||||
|
||||
// Other
|
||||
SafeSetOrder(FName("World Collision"));
|
||||
SafeSetOrder(FName("ExternalForce"));
|
||||
|
||||
// AnimNode
|
||||
SafeSetOrder(FName("Tag"));
|
||||
SafeSetOrder(FName("Alpha"));
|
||||
};
|
||||
|
||||
DetailBuilder.SortCategories(CategorySorter);
|
||||
}
|
||||
|
||||
struct FKawaiiPhysicsVersion
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
BeforeCustomVersionWasAdded,
|
||||
UseRuntimeFloatCurve,
|
||||
// -----<new versions can be added above this line>-------------------------------------------------
|
||||
VersionPlusOne,
|
||||
LatestVersion = VersionPlusOne - 1
|
||||
};
|
||||
|
||||
// The GUID for this custom version number
|
||||
const static FGuid GUID;
|
||||
|
||||
private:
|
||||
FKawaiiPhysicsVersion()
|
||||
{
|
||||
};
|
||||
};
|
||||
|
||||
const FGuid FKawaiiPhysicsVersion::GUID(0x4B2D3E25, 0xCD681D29, 0x2DB298D7, 0xAD3E55FA);
|
||||
|
||||
const FCustomVersionRegistration GRegisterKawaiiPhysCustomVersion(FKawaiiPhysicsVersion::GUID,
|
||||
FKawaiiPhysicsVersion::LatestVersion,
|
||||
TEXT("Kawaii-Phys"));
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::Serialize(FArchive& Ar)
|
||||
{
|
||||
Super::Serialize(Ar);
|
||||
|
||||
Ar.UsingCustomVersion(FKawaiiPhysicsVersion::GUID);
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::CreateExportDataAssetPath(FString& PackageName, const FString& DefaultSuffix) const
|
||||
{
|
||||
FString AssetName;
|
||||
const FString AnimBlueprintPath = GetAnimBlueprint()->GetPackage()->GetName();
|
||||
const FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
|
||||
AssetToolsModule.Get().CreateUniqueAssetName(AnimBlueprintPath, DefaultSuffix, PackageName, AssetName);
|
||||
}
|
||||
|
||||
UPackage* UAnimGraphNode_KawaiiPhysics::CreateDataAssetPackage(const FString& DialogTitle, const FString& DefaultSuffix,
|
||||
FString& AssetName) const
|
||||
{
|
||||
FString PackageName;
|
||||
CreateExportDataAssetPath(PackageName, DefaultSuffix);
|
||||
|
||||
const TSharedRef<SDlgPickAssetPath> NewAssetDlg =
|
||||
SNew(SDlgPickAssetPath)
|
||||
.Title(FText::FromString(DialogTitle))
|
||||
.DefaultAssetPath(FText::FromString(PackageName));
|
||||
|
||||
if (NewAssetDlg->ShowModal() == EAppReturnType::Cancel)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FString PackagePath(NewAssetDlg->GetFullAssetPath().ToString());
|
||||
AssetName = NewAssetDlg->GetAssetName().ToString();
|
||||
|
||||
return CreatePackage(*PackagePath);
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::ShowExportAssetNotification(UObject* NewAsset,
|
||||
FText NotificationText)
|
||||
{
|
||||
FNotificationInfo NotificationInfo(NotificationText);
|
||||
NotificationInfo.ExpireDuration = 5.0f;
|
||||
NotificationInfo.Hyperlink = FSimpleDelegate::CreateLambda([NewAsset]()
|
||||
{
|
||||
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(NewAsset);
|
||||
});
|
||||
NotificationInfo.HyperlinkText = LOCTEXT("OpenCreatedAsset", "Open Created Asset");
|
||||
|
||||
TSharedPtr<SNotificationItem> NotificationItem = FSlateNotificationManager::Get().AddNotification(
|
||||
NotificationInfo);
|
||||
NotificationItem->SetCompletionState(SNotificationItem::CS_Success);
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::ExportLimitsDataAsset()
|
||||
{
|
||||
FString AssetName;
|
||||
UPackage* Package = CreateDataAssetPackage(
|
||||
TEXT("Choose Location for Collision Data Asset"), TEXT("_Collision"), AssetName);
|
||||
if (!Package)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UKawaiiPhysicsLimitsDataAsset* NewDataAsset =
|
||||
NewObject<UKawaiiPhysicsLimitsDataAsset>(Package, UKawaiiPhysicsLimitsDataAsset::StaticClass(),
|
||||
FName(AssetName), RF_Public | RF_Standalone))
|
||||
{
|
||||
// look for a valid component in the object being debugged,
|
||||
// we might be set to something other than the preview.
|
||||
if (UObject* ObjectBeingDebugged = GetAnimBlueprint()->GetObjectBeingDebugged())
|
||||
{
|
||||
if (const UAnimInstance* InstanceBeingDebugged = Cast<UAnimInstance>(ObjectBeingDebugged))
|
||||
{
|
||||
NewDataAsset->Skeleton = InstanceBeingDebugged->CurrentSkeleton;
|
||||
}
|
||||
}
|
||||
|
||||
// copy data
|
||||
auto CopyLimits = [&](auto& DataLimits, auto& SourceLimits)
|
||||
{
|
||||
DataLimits = SourceLimits;
|
||||
for (auto& DataLimit : DataLimits)
|
||||
{
|
||||
DataLimit.SourceType = ECollisionSourceType::DataAsset;
|
||||
}
|
||||
};
|
||||
CopyLimits(NewDataAsset->SphericalLimits, Node.SphericalLimits);
|
||||
CopyLimits(NewDataAsset->CapsuleLimits, Node.CapsuleLimits);
|
||||
CopyLimits(NewDataAsset->BoxLimits, Node.BoxLimits);
|
||||
CopyLimits(NewDataAsset->PlanarLimits, Node.PlanarLimits);
|
||||
|
||||
// select new asset
|
||||
USelection* SelectionSet = GEditor->GetSelectedObjects();
|
||||
SelectionSet->DeselectAll();
|
||||
SelectionSet->Select(NewDataAsset);
|
||||
|
||||
FAssetRegistryModule::AssetCreated(NewDataAsset);
|
||||
Package->MarkPackageDirty();
|
||||
|
||||
// Add Notification
|
||||
FText NotificationText = FText::Format(
|
||||
LOCTEXT("ExportedLimitsDataAsset", "Exposted Limits Data Asset: {0}"), FText::FromString(AssetName));
|
||||
ShowExportAssetNotification(NewDataAsset, NotificationText);
|
||||
}
|
||||
}
|
||||
|
||||
void UAnimGraphNode_KawaiiPhysics::ExportBoneConstraintsDataAsset()
|
||||
{
|
||||
FString AssetName;
|
||||
UPackage* Package = CreateDataAssetPackage(
|
||||
TEXT("Choose Location for BoneConstraints Data Asset"), TEXT("_BoneConstraint"), AssetName);
|
||||
if (!Package)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UKawaiiPhysicsBoneConstraintsDataAsset* NewDataAsset =
|
||||
NewObject<UKawaiiPhysicsBoneConstraintsDataAsset>(
|
||||
Package, UKawaiiPhysicsBoneConstraintsDataAsset::StaticClass(),
|
||||
FName(AssetName), RF_Public | RF_Standalone))
|
||||
{
|
||||
// look for a valid component in the object being debugged,
|
||||
// we might be set to something other than the preview.
|
||||
if (UObject* ObjectBeingDebugged = GetAnimBlueprint()->GetObjectBeingDebugged())
|
||||
{
|
||||
if (const UAnimInstance* InstanceBeingDebugged = Cast<UAnimInstance>(ObjectBeingDebugged))
|
||||
{
|
||||
NewDataAsset->PreviewSkeleton = InstanceBeingDebugged->CurrentSkeleton;
|
||||
NewDataAsset->UpdatePreviewBoneList();
|
||||
}
|
||||
}
|
||||
|
||||
// copy data
|
||||
NewDataAsset->BoneConstraintsData.SetNum(Node.BoneConstraints.Num());
|
||||
for (int32 i = 0; i < Node.BoneConstraints.Num(); i++)
|
||||
{
|
||||
NewDataAsset->BoneConstraintsData[i].Update(Node.BoneConstraints[i]);
|
||||
}
|
||||
|
||||
// select new asset
|
||||
USelection* SelectionSet = GEditor->GetSelectedObjects();
|
||||
SelectionSet->DeselectAll();
|
||||
SelectionSet->Select(NewDataAsset);
|
||||
|
||||
FAssetRegistryModule::AssetCreated(NewDataAsset);
|
||||
Package->MarkPackageDirty();
|
||||
|
||||
// Add Notification
|
||||
FText NotificationText = FText::Format(
|
||||
LOCTEXT("ExportedBoneConstraintsDataAsset", "Exposted BoneConstraints Data Asset: {0}"),
|
||||
FText::FromString(AssetName));
|
||||
ShowExportAssetNotification(NewDataAsset, NotificationText);
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,27 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#include "KawaiiPhysicsEd.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Textures/SlateIcon.h"
|
||||
#include "KawaiiPhysicsEditMode.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FKawaiiPhysicsModuleEd"
|
||||
|
||||
|
||||
void FKawaiiPhysicsEdModule::StartupModule()
|
||||
{
|
||||
FEditorModeRegistry::Get().RegisterMode<FKawaiiPhysicsEditMode>("AnimGraph.SkeletalControl.KawaiiPhysics",
|
||||
LOCTEXT("FKawaiiPhysicsEditMode", "Kawaii Physics"),
|
||||
FSlateIcon(), false);
|
||||
}
|
||||
|
||||
|
||||
void FKawaiiPhysicsEdModule::ShutdownModule()
|
||||
{
|
||||
FEditorModeRegistry::Get().UnregisterMode("AnimGraph.SkeletalControl.KawaiiPhysics");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FKawaiiPhysicsEdModule, KawaiiPhysicsEd)
|
||||
//IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, KawaiiPhysicsEd, "KawaiiPhysicsEd" );
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AnimGraphNode_Base.h"
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
#include "AnimGraphNode_SkeletalControlBase.h"
|
||||
#include "EdGraph/EdGraphNodeUtils.h"
|
||||
|
||||
#include "AnimGraphNode_KawaiiPhysics.generated.h"
|
||||
|
||||
class FCompilerResultsLog;
|
||||
|
||||
UCLASS()
|
||||
class UAnimGraphNode_KawaiiPhysics : public UAnimGraphNode_SkeletalControlBase
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
UPROPERTY(EditAnywhere, Category = Settings)
|
||||
FAnimNode_KawaiiPhysics Node;
|
||||
|
||||
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||
|
||||
// UObject interface
|
||||
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
|
||||
protected:
|
||||
// UAnimGraphNode_Base interface
|
||||
virtual FEditorModeID GetEditorMode() const override;
|
||||
virtual void ValidateAnimNodePostCompile(FCompilerResultsLog& MessageLog,
|
||||
UAnimBlueprintGeneratedClass* CompiledClass,
|
||||
int32 CompiledNodeIndex) override;
|
||||
virtual void CopyNodeDataToPreviewNode(FAnimNode_Base* AnimNode) override;
|
||||
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
|
||||
// End of UAnimGraphNode_Base interface
|
||||
|
||||
//virtual FText GetControllerDescription() const override;
|
||||
virtual FText GetControllerDescription() const override;
|
||||
virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; }
|
||||
// End of UAnimGraphNode_SkeletalControlBase interface
|
||||
|
||||
// UObject interface
|
||||
virtual void Serialize(FArchive& Ar) override;
|
||||
|
||||
// End of UObject interface
|
||||
|
||||
virtual void CustomizeDetailTools(IDetailLayoutBuilder& DetailBuilder);
|
||||
virtual void CustomizeDetailDebugVisualizations(IDetailLayoutBuilder& DetailBuilder);
|
||||
|
||||
private:
|
||||
/** Creates the export data asset path. */
|
||||
void CreateExportDataAssetPath(FString& PackageName, const FString& DefaultSuffix) const;
|
||||
|
||||
/** Creates the data asset package. */
|
||||
UPackage* CreateDataAssetPackage(const FString& DialogTitle, const FString& DefaultSuffix,
|
||||
FString& AssetName) const;
|
||||
|
||||
/** Shows the export asset notification. */
|
||||
void ShowExportAssetNotification(UObject* NewAsset, FText NotificationText);
|
||||
|
||||
/** Exports the limits data asset. */
|
||||
void ExportLimitsDataAsset();
|
||||
|
||||
/** Exports the bone constraints data asset. */
|
||||
void ExportBoneConstraintsDataAsset();
|
||||
|
||||
public:
|
||||
/** Enables or disables debug drawing for bones. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawBone = true;
|
||||
|
||||
/** Enables or disables debug drawing for bone length rate. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugBoneLengthRate = true;
|
||||
|
||||
/** Enables or disables debug drawing for limit angles. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawLimitAngle = true;
|
||||
|
||||
/** Enables or disables debug drawing for spherical limits. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawSphereLimit = true;
|
||||
|
||||
/** Enables or disables debug drawing for capsule limits. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawCapsuleLimit = true;
|
||||
|
||||
/** Enables or disables debug drawing for box limits. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawBoxLimit = true;
|
||||
|
||||
/** Enables or disables debug drawing for planar limits. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawPlanerLimit = true;
|
||||
|
||||
/** Enables or disables debug drawing for bone constraints. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawBoneConstraint = true;
|
||||
|
||||
/** Enables or disables debug drawing for external forces. */
|
||||
UPROPERTY()
|
||||
bool bEnableDebugDrawExternalForce = true;
|
||||
|
||||
private:
|
||||
/** Constructing FText strings can be costly, so we cache the node's title */
|
||||
FNodeTitleTextTable CachedNodeTitles;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class FKawaiiPhysicsEdModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AnimNodeEditMode.h"
|
||||
#include "AnimGraphNode_KawaiiPhysics.h"
|
||||
#include "AnimNode_KawaiiPhysics.h"
|
||||
|
||||
#define UE_WIDGET UE::Widget
|
||||
|
||||
class FEditorViewportClient;
|
||||
class FPrimitiveDrawInterface;
|
||||
class USkeletalMeshComponent;
|
||||
struct FViewportClick;
|
||||
|
||||
class FKawaiiPhysicsEditMode : public FAnimNodeEditMode
|
||||
{
|
||||
public:
|
||||
FKawaiiPhysicsEditMode();
|
||||
|
||||
/** IAnimNodeEditMode interface */
|
||||
virtual void EnterMode(class UAnimGraphNode_Base* InEditorNode, struct FAnimNode_Base* InRuntimeNode) override;
|
||||
virtual void ExitMode() override;
|
||||
virtual FVector GetWidgetLocation() const override;
|
||||
virtual UE_WIDGET::EWidgetMode GetWidgetMode() const override;
|
||||
virtual ECoordSystem GetWidgetCoordinateSystem() const override;
|
||||
virtual void DoTranslation(FVector& InTranslation) override;
|
||||
virtual void DoRotation(FRotator& InRotation) override;
|
||||
virtual void DoScale(FVector& InScale) override;
|
||||
|
||||
/** FEdMode interface */
|
||||
virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override;
|
||||
virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy,
|
||||
const FViewportClick& Click) override;
|
||||
virtual bool GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) override;
|
||||
virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey,
|
||||
EInputEvent InEvent) override;
|
||||
virtual bool ShouldDrawWidget() const override;
|
||||
virtual void DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View,
|
||||
FCanvas* Canvas) override;
|
||||
|
||||
protected:
|
||||
void OnExternalNodePropertyChange(FPropertyChangedEvent& InPropertyEvent);
|
||||
FDelegateHandle NodePropertyDelegateHandle;
|
||||
|
||||
void OnLimitDataAssetPropertyChange(FPropertyChangedEvent& InPropertyEvent);
|
||||
bool IsSelectAnimNodeCollision() const;
|
||||
FDelegateHandle LimitsDataAssetPropertyDelegateHandle;
|
||||
|
||||
private:
|
||||
void RenderModifyBones(FPrimitiveDrawInterface* PDI) const;
|
||||
void RenderLimitAngle(FPrimitiveDrawInterface* PDI) const;
|
||||
|
||||
/** Render each collisions */
|
||||
void RenderSphericalLimits(FPrimitiveDrawInterface* PDI) const;
|
||||
void RenderCapsuleLimit(FPrimitiveDrawInterface* PDI) const;
|
||||
void RenderBoxLimit(FPrimitiveDrawInterface* PDI) const;
|
||||
void RenderPlanerLimit(FPrimitiveDrawInterface* PDI);
|
||||
|
||||
void RenderBoneConstraint(FPrimitiveDrawInterface* PDI) const;
|
||||
void RenderExternalForces(FPrimitiveDrawInterface* PDI) const;
|
||||
|
||||
/** Helper function for GetWidgetLocation() and joint rendering */
|
||||
FVector GetWidgetLocation(ECollisionLimitType CollisionType, int32 Index) const;
|
||||
|
||||
// methods to find a valid widget mode for gizmo because doesn't need to show gizmo when the mode is "Ignore"
|
||||
UE_WIDGET::EWidgetMode FindValidWidgetMode(UE_WIDGET::EWidgetMode InWidgetMode) const;
|
||||
|
||||
/** Checking if a collision is selected and the collision is valid */
|
||||
bool IsValidSelectCollision() const;
|
||||
|
||||
// Get Select Colliison Info
|
||||
FCollisionLimitBase* GetSelectCollisionLimitRuntime() const;
|
||||
FCollisionLimitBase* GetSelectCollisionLimitGraph() const;
|
||||
|
||||
/** Draw text func for DrawHUD */
|
||||
void DrawTextItem(const FText& Text, FCanvas* Canvas, float X, float& Y, float FontHeight);
|
||||
void Draw3DTextItem(const FText& Text, FCanvas* Canvas, const FSceneView* View, const FViewport* Viewport,
|
||||
FVector Location);
|
||||
|
||||
/** Cache the typed nodes */
|
||||
struct FAnimNode_KawaiiPhysics* RuntimeNode;
|
||||
UAnimGraphNode_KawaiiPhysics* GraphNode;
|
||||
|
||||
/** The current bone selection mode */
|
||||
ECollisionLimitType SelectCollisionType = ECollisionLimitType::None;
|
||||
int32 SelectCollisionIndex = -1;
|
||||
ECollisionSourceType SelectCollisionSourceType = ECollisionSourceType::AnimNode;
|
||||
|
||||
// storing current widget mode
|
||||
mutable UE_WIDGET::EWidgetMode CurWidgetMode;
|
||||
|
||||
// physics asset body material
|
||||
TObjectPtr<UMaterialInstanceDynamic> PhysicsAssetBodyMaterial;
|
||||
};
|
||||
@@ -0,0 +1,421 @@
|
||||
## Based on VisualStudio and UnrealEngine templates
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/UnrealEngine.gitignore
|
||||
|
||||
## VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
## UnrealEngine.gitignore
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.ipa
|
||||
|
||||
# These project files can be generated by the engine
|
||||
*.xcodeproj
|
||||
*.xcworkspace
|
||||
*.opensdf
|
||||
*.sdf
|
||||
|
||||
# Precompiled Assets
|
||||
SourceArt/**/*.png
|
||||
SourceArt/**/*.tga
|
||||
|
||||
# Binary Files
|
||||
Binaries/*
|
||||
Plugins/*/Binaries/*
|
||||
|
||||
# Builds
|
||||
Build/*
|
||||
|
||||
# Whitelist PakBlacklist-<BuildConfiguration>.txt files
|
||||
!Build/*/
|
||||
Build/*/**
|
||||
!Build/*/PakBlacklist*.txt
|
||||
|
||||
# Don't ignore icon files in Build
|
||||
!Build/**/*.ico
|
||||
|
||||
# Built data for maps
|
||||
*_BuiltData.uasset
|
||||
|
||||
# Configuration files generated by the Editor
|
||||
Saved/*
|
||||
|
||||
# Compiled source files for the engine to use
|
||||
Intermediate/*
|
||||
Plugins/*/Intermediate/*
|
||||
|
||||
# Cache files for the editor to use
|
||||
DerivedDataCache/*
|
||||
@@ -0,0 +1,9 @@
|
||||
# Microsoft Open Source Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
||||
@@ -0,0 +1,49 @@
|
||||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.
|
||||
|
||||
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Code Style Guide
|
||||
|
||||
The code in the repo follows the existing code conventions described in the Unreal Engine's [Code Standard document](https://docs.unrealengine.com/INT/epic-cplusplus-coding-standard-for-unreal-engine/). The `.editorconfig` file at the source root is used for Visual Studio to check the conventions and report violations.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
When submitting a pull request, make sure that it has a clean build using the instructions below. A core contributor will review your pull request and provide feedback. Once all the feedback is addressed and the PR is approved, we will merge the changes.
|
||||
|
||||
## Build workflow
|
||||
The plugin source can be built in isolation using the command below (which wrap the RunUAT.bat script) to ensure it's correct for submition to the Unreal Engine Marketplace.
|
||||
|
||||
From a Visual Studio Developer Prompt (or PowerShell Dev Prompt), run the following:
|
||||
|
||||
```cmd
|
||||
> msbuild -p:UnrealEngine=[path_or_version] -p:OutputPath=[absolute_path]
|
||||
``````
|
||||
|
||||
- `UnrealEngine` can be either a path to a source build (e.g. `c:\src\ue`) or a version identifier for an installed engine (e.g. `4.27`, `5.2`).
|
||||
- `OutputPath` cannot be under the Unreal Engine's folder due to a restriction from `RunUAT.bat`.
|
||||
|
||||
> Note: The contents of `OutputPath` will be overwritten!
|
||||
|
||||
By default the script will disable Unity Builds in the plugin modules to catch errors from cpp files not including all the required headers. It does not affect the build of other targets and modules.
|
||||
|
||||
## Unity build errors
|
||||
|
||||
If you get errors due to unity build problems, you get the same errors in Visual Studio by generating the solution with the command below. This will allow Visual Studio to suggest the includes as code fixes. Note that this will overwrite any existing solution and projects that are already present.
|
||||
|
||||
```powershell
|
||||
$env:VSTUE_IsCustomDevBuild=1; & "C:\Program Files\Epic Games\UE_5.2\Engine\Build\BatchFiles\Build.bat" -projectfiles -project="full_path_to_game.uproject" -game
|
||||
```
|
||||
|
||||
The module rules for the plugin check the enviroment variable above to use the more strict include settings.
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[FilterPlugin]
|
||||
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
|
||||
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
|
||||
;
|
||||
; Examples:
|
||||
; /README.txt
|
||||
; /Extras/...
|
||||
; /Binaries/ThirdParty/*.dll
|
||||
/Docs/...
|
||||
@@ -0,0 +1,57 @@
|
||||
# Visual Studio Integration Tool
|
||||
|
||||
Visual Studio Integration Tool is an Unreal Engine plugin that works in conjunction with Visual Studio to display information about Blueprints assets in C++ code (requires Visual Studio 2022 17.4 or later).
|
||||
|
||||
## Installing
|
||||
|
||||
### Visual Studio
|
||||
|
||||
The tool requires the `Visual Studio Tools for Unreal Engine` component from Visual Studio to be installed. You can find it under the "Game development with C++" workload in the Visual Studio Installer (figure 1).
|
||||
|
||||
 \
|
||||
*Figure 1 - Installing the Visual Studio component*
|
||||
|
||||
### Unreal Engine
|
||||
You can install the plugin in a couple of ways:
|
||||
|
||||
- Through the Epic Games Launcher:
|
||||
- Select the "Install to Engine" option within the Launcher. From there, you can select an engine version for installation.
|
||||
- If you're using the Marketplace website, you can add the plugin to your account and you will have an option to open the Launcher in order to install it as detailed above.
|
||||
- If you already added the plugin to your account, go Library -> Vault in the and locate the plugin there.
|
||||
|
||||
- Through source distribution:
|
||||
- If you're unable to use the Marketplace-based distribution (e.g. you're building the Unreal Engine from source), then you can install the plugin manually by following the instructions found at <https://github.com/microsoft/vc-ue-extensions#readme>
|
||||
|
||||
## Enabling the plugin
|
||||
|
||||
- Through the Unreal Editor
|
||||
- Open your project and then use the Plugin Manager to enable "VisualStudioTools".
|
||||
- See [official documentation](https://docs.unrealengine.com/INT/working-with-plugins-in-unreal-engine/) for more information on how to install and enable plugins.
|
||||
- (Advanced) Alternatively, you can manually edit the '.uproject' descriptor for your project and add an entry for the "VisualStudioTools" plugin.
|
||||
|
||||
## Usage
|
||||
|
||||
Test discovery in Visual Studio 2022
|
||||
|
||||
1. Begin by installing and enabling the `Visual Studio Tools for Unreal Engine` plugin.
|
||||
2. Open your solution in Visual Studio.
|
||||
3. Click on the Test Explorer to show a pop-up that will display available tests. (figure 3).
|
||||
4. You can find the logs from the plugin execution in the Tests Output Window.
|
||||
5. To refresh your filters for test discovery, you can select the "Options > Unreal Engine > Test Adapter" option under the "Tests" menu. (figure 4)
|
||||
|
||||
 \
|
||||
*Figure 2 - Unreal Engine project Configuration Page
|
||||
|
||||
 \
|
||||
*Figure 3 - Menu to rescan the blueprint assets in the game project*
|
||||
|
||||
 \
|
||||
*Figure 3 - Menu to change options for Tests Discovery
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues when setting up Visual Studio in conjunction with the Unreal Editor plugin, please refer to the [Troubleshooting](https://github.com/microsoft/vc-ue-extensions/blob/main/Docs/Troubleshooting.md) guide in the repository. This guide provides solutions for common issues and is periodically updated to ensure that the latest solutions are available.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
To report new issues, provide feedback, or request new features, please use the following options: [Report a Problem](https://aka.ms/feedback/cpp/unrealengine/report) and [Suggest a Feature](https://aka.ms/feedback/cpp/unrealengine/suggest). These options will allow you to submit your issue or feedback directly to our team and help us improve the plugin moving forward.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Troubleshooting guide
|
||||
|
||||
This document describes some of the errors that might happen in the integration with the Unreal Engine and potential ways to mitigate them.
|
||||
|
||||
The integration works by Visual Studio being able to invoke the `VisualStudioTools` plugin using the Unreal Editor executable in commandlet mode. That means the following must be true:
|
||||
|
||||
- The `Visual Studio Tools for Unreal Engine` component from Visual Studio must be installed. You can find it under the "Game Development with C++" workload in the VS Installer.
|
||||
- The game project must be built in a Editor target (e.g., `"Development_Editor"`).
|
||||
- The `VisualStudioTools` plugin must be enabled for the project, either explicitly in the .uproject descriptor file or be enabled by default to all projects if installed at the as an engine plugin (via the `"EnabledByDefault=true"` entry in the .uplugin file).
|
||||
- Starting on version 17.5, Visual Studio will wait to scan the game project until a file with the `UCLASS/UPROPERTY/UFUNCTION` macros is opened and the Code Lens hints are requested.
|
||||
- At the moment, the Code Lens hints will only be displayed for game projects. In particular, the files in the _engine_ project of the solution (with name like "UE4" or "UE5") will not display the hints yet.
|
||||
|
||||
## Code Lens are not visible
|
||||
|
||||
### Verify the required VS component is installed
|
||||
|
||||
In recent versions of UE, the generated solution comes with a `.vsconfig` file, which allows right-clicking on the Solution in VS and selecting "Install Missing Feature(s)". The component is part of the "Game Development with C++" workload.
|
||||
|
||||
You can also see this [help page](https://learn.microsoft.com/en-us/visualstudio/install/install-visual-studio?view=vs-2022#step-4---choose-workloads) about installing features using the Visual Studio installer.
|
||||
|
||||
### Check if the opened documents have any class decorated with the Unreal macros
|
||||
|
||||
For real world projects, scanning the blueprints information might take several seconds and be expensive in terms of machine resources. Visual Studio will only start the operation when the Code Lens are rendered. That means it will wait until a file from the game project with the Unreal macros is opened in the editor.
|
||||
|
||||
### Check if a `cpp.hint` file is redefining the relevant Unreal macros
|
||||
|
||||
Some projects might have a cpp.hint file that includes the `UCLASS`, `UPROPERTY`, `UFUNCTION` macros. That might suppress the new logic in Visual Studio that uses the macros to display the Code Lens hints.
|
||||
|
||||
If that is the case, you can remove those macros from the hint file, save it and try reloading the project.
|
||||
|
||||
Note that other macros in the hint file can be left as-is and do not affect the Code Lens hints.
|
||||
|
||||
### Ensure the C++ Database is enabled
|
||||
|
||||
In Tools > Options > Text Editor > C/C++ > Advanced > Browsing/Navigation, the setting "Disable Database" should be set to "False". This is the default value of this setting.
|
||||
|
||||
## Errors showing up in the Output Window and/or Task Center notification
|
||||
|
||||
### Message "LogInit: Error: VisualStudioToolsCommandlet looked like a commandlet, but we could not find the class."
|
||||
|
||||
Possible causes are the plugin not being installed correctly or installed but not yet enabled for the game project (which is required on installation from the Marketplace).
|
||||
|
||||
- See [this section](../README.md#building-and-installing) for installation instructions.
|
||||
|
||||
- See [this section](../README.md#optional-enabling-the-plugin) for instructions on how to enable the plugin.
|
||||
|
||||
### Message "Command finished with exit code 1" without other errors
|
||||
|
||||
Either the game project or the plugin DLL is not yet built. Rebuilding the project should ensure they are available. Then manually rescan the game project using the `Project > Rescan UE Blueprints` menu.
|
||||
|
||||
### Task Center error: "Your task failed with the message: Could not find a part of the path...'
|
||||
|
||||
This was a known issue when trying to locate the path the Unreal Editor executable, fixed in Visual Studio 17.5-Preview3. This usually happens when the selected Configuration in VS is not one with an "Editor" target.
|
||||
|
||||
A workaround is to switch to such configuration and manually rescan the game project using the `Project > Rescan UE Blueprints` menu.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -0,0 +1,22 @@
|
||||
Visual Studio Tools for Unreal Engine
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
@@ -0,0 +1,116 @@
|
||||
# Unreal Engine plugin for Visual Studio
|
||||
|
||||
This project contains an Unreal Editor plugin that works in conjunction with Visual Studio to help discover and run tests in C++ code.
|
||||
|
||||
The plugin can be installed in either the Engine or Game project sources, and it is automatically activated when an Unreal Engine project is opened in Visual Studio.
|
||||
|
||||
## Requirements
|
||||
|
||||
Before you begin, please make sure you have the following software and tools set up:
|
||||
|
||||
1. Visual Studio 2022 has the "Visual Studio Tools for Unreal Engine" component installed.
|
||||
1. The component can be found in the "Game development with C++" workload or as an individual component.
|
||||
2. Unreal Engine, either installed or built from source.
|
||||
1. To learn how to install or build Unreal Engine, please refer to the following guide: [Installing Unreal Engine](https://docs.unrealengine.com/5.0/en-US/installing-unreal-engine).
|
||||
1. The source code and instructions have been tested on Unreal Engine versions 4.27 and 5.0+.
|
||||
|
||||
## Building and Installing the Plugin
|
||||
|
||||
> If you have Unreal Engine installed and set up through the Epic Games Launcher, and you only want to use the plugin, you can skip the steps below and install it directly from the [Unreal Engine Marketplace](https://aka.ms/vsueplugin).
|
||||
|
||||
The most straightforward way to use the plugin is to clone the repo under the `Plugins` folder of your game project or engine source. If you have multiple projects in the same Visual Studio solution, it is recommended to install the plugin at the engine level and share the binaries across the projects.
|
||||
|
||||
1. Clone the repo by using the following commands:
|
||||
```powershell
|
||||
git clone https://github.com/microsoft/vc-ue-extensions.git
|
||||
```
|
||||
|
||||
2. Build the plugin from source:
|
||||
```powershell
|
||||
msbuild -p:UnrealEngine=<AbsolutePathToEngine>
|
||||
```
|
||||
Note#1: `<AbsolutePathToEngine>` can be path to source code folder of the engine or the one installed by `Epic Games Launcher` (e.g `C:\Program Files\Epic Games\UE_5.4`)
|
||||
Note#2: Alternatevly you can follow [Unreal Engine building plugins](https://dev.epicgames.com/community/learning/tutorials/qz93/unreal-engine-building-plugins) guide.
|
||||
|
||||
3. Clone built plugin.
|
||||
|
||||
3.1. To Project folder:
|
||||
```powershell
|
||||
move-item -path ./bin -destination <ProjectRoot>\Plugins\VisualStudioTools
|
||||
```
|
||||
Note: You have to create `Plugins` folder in the root of the game project if it doens't exisist yet.
|
||||
|
||||
3.2. To Engine folder:
|
||||
```powershell
|
||||
move-item -path ./bin -destination <AbsolutePathToEngine\Engine\Plugins
|
||||
```
|
||||
|
||||
2. Optional: Regenerate the Solution for your game project so that the plugin source will be visible in Visual Studio.
|
||||
|
||||
3. Rebuild the game project, which will also build the plugin.
|
||||
|
||||
After completing these steps, Visual Studio should automatically recognize the plugin when you open a solution or project, and it will start processing tests in your game.
|
||||
|
||||
You can also use the option `Run All Tests` in the `Tests` menu to manually force Visual Studio to invoke the the plugin.
|
||||
|
||||
### Cloning outside of engine or project sources
|
||||
|
||||
If you prefer to have the plugin's repository located separately from the engine or project sources (for example, if you want to share it between multiple engines), you can follow the instructions provided in the file [Building and Installing the Plugin](./README.md#building-and-installing-the-plugin) to learn how to build and install the plugin in such a scenario.
|
||||
|
||||
## Enabling the Plugin (Optional)
|
||||
|
||||
By default, the plugin descriptor is already set with `"EnabledByDefault = true"`, so it should function automatically without any additional steps. However, if you encounter difficulties with Unreal Engine building the plugin (e.g., UE fails to build the plugin when building the project), you can enable the plugin explicitly by using one of the following methods:
|
||||
|
||||
1. Navigate to the plugin manager in the Unreal Editor and select `VisualStudioTools`.
|
||||
2. Manually edit the game project's `.uproject` descriptor file by adding an entry for the plugin.
|
||||
|
||||
In either case, the end result should be a new entry in the `Plugins` array in the JSON file, as shown below:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Category": "...",
|
||||
"Description": "...",
|
||||
"Modules": ["..."],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "VisualStudioTools",
|
||||
"Enabled": true,
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
>Note: To ensure proper activation of the plugin, make sure the correct plugin is selected or the desired changes are made in the `.uproject` file.
|
||||
|
||||
## Manually invoking the plugin
|
||||
|
||||
The plugin is designed to be used with Visual Studio, and as such, it does not provide any user interfaces, commands, or logs within the Unreal Editor. However, it is still possible to test the plugin's execution by running the **sample** command below:
|
||||
|
||||
```powershell
|
||||
& "<AbsolutePathToEngine>\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" "$Env:UserProfile\Unreal Projects\EmptyProject\EmptyProject.uproject" -run=VisualStudioTools -output "$Env:Temp\vs-ue-tools.json" [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash]
|
||||
```
|
||||
|
||||
This command will run the plugin for the specified project and save Unreal Engine Blueprint information in the output file. Optional parameters are included to run the command faster.
|
||||
|
||||
For more information on the specific command line parameters, you can run the following command in the powershell prompt with `-help`:
|
||||
|
||||
```powershell
|
||||
& "<Editor-Cmd.exe>" "<path_to_uproject>" -run=VisualStudioTools -help [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash]
|
||||
```
|
||||
|
||||
>Note: The executable name is `UE4Editor-cmd.exe` for UE4.x, located under a similar path.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues when setting up Visual Studio in conjunction with the Unreal Editor plugin, please refer to the [Troubleshooting](https://github.com/microsoft/vc-ue-extensions/blob/main/Docs/Troubleshooting.md) guide in the repository. This guide provides solutions for common issues and is periodically updated to ensure that the latest solutions are available.
|
||||
|
||||
To report new issues, provide feedback, or request new features, please use the following options: [Report a Problem](https://aka.ms/feedback/cpp/unrealengine/report) and [Suggest a Feature](https://aka.ms/feedback/cpp/unrealengine/suggest). These options will allow you to submit your issue or feedback directly to our team and help us improve the plugin moving forward.
|
||||
|
||||
## Contributing
|
||||
This project welcomes contributions and suggestions. Check out our [contributing guide](CONTRIBUTING.md) for instructions on how to contribute to the project.
|
||||
|
||||
## Trademarks
|
||||
|
||||
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
||||
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
||||
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
||||
@@ -0,0 +1,41 @@
|
||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
@@ -0,0 +1,13 @@
|
||||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
||||
This project uses the Visual Studio Developer Community to track bugs and feature requests. Please search the existing feedback before filing new ones to avoid duplicates.
|
||||
|
||||
For common issues, please refer to our [Troubleshooting](https://github.com/microsoft/vc-ue-extensions/blob/main/Docs/troubleshooting.md) guide in the repository. We will periodically update the guide to provide solutions for common issues.
|
||||
|
||||
To report issues, provide feedback, and request features, please use one of the following options: [Report a Problem](https://aka.ms/feedback/cpp/unrealengine/report) and [Suggest a Feature](https://aka.ms/feedback/cpp/unrealengine/suggest).
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
|
||||
@@ -0,0 +1,43 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$EnginePath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$EngineVersion
|
||||
)
|
||||
|
||||
function New-TemporaryDirectory {
|
||||
$parent = [System.IO.Path]::GetTempPath()
|
||||
$name = [System.IO.Path]::GetRandomFileName()
|
||||
New-Item -ItemType Directory -Path (Join-Path $parent $name)
|
||||
}
|
||||
|
||||
$PackagePath = New-TemporaryDirectory
|
||||
& msbuild "-p:UnrealEngine=$EnginePath;OutputPath=$PackagePath;Versioned=true"
|
||||
|
||||
# Add EnabledByDefault property in the descriptor file
|
||||
Write-Host "Patch plugin descriptor file"
|
||||
$descriptor = "$PackagePath/VisualStudioTools.uplugin"
|
||||
$a = Get-Content $descriptor | ConvertFrom-Json
|
||||
$a | Add-Member -NotePropertyName EnabledByDefault -NotePropertyValue $true -ErrorAction Ignore
|
||||
$a | ConvertTo-Json -depth 100 | Out-File $descriptor -Encoding utf8
|
||||
|
||||
Write-Host "Copy Config folder"
|
||||
Copy-Item -Path Config -Destination $PackagePath/Config -Recurse
|
||||
|
||||
$PublishPath = "publish"
|
||||
If(!(test-path -PathType Container $PublishPath))
|
||||
{
|
||||
New-Item -ItemType Directory -Path $PublishPath | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "Create ZIP package"
|
||||
$tag = $EngineVersion.Replace(".", "")
|
||||
$files = Get-ChildItem $PackagePath -Exclude @("Binaries", "Intermediate")
|
||||
$zip = "$PublishPath/VisualStudioTools_v$($a.VersionName)_ue$tag.zip"
|
||||
Compress-Archive -Path $files -DestinationPath "$PublishPath/VisualStudioTools_v$($a.VersionName)_ue$tag.zip" -CompressionLevel Fastest
|
||||
|
||||
Remove-Item $PackagePath -Force -Recurse
|
||||
|
||||
Write-Host "Done: $($zip | Resolve-Path)"
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="SignFiles" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\Microsoft.VisualStudioEng.MicroBuild.Core.0.4.1\build\Microsoft.VisualStudioEng.MicroBuild.Core.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<BaseOutputDirectory>$(MSBuildThisFileDirectory)../../out/</BaseOutputDirectory>
|
||||
<!-- These properties are required by MicroBuild, which only signs files that are under these paths -->
|
||||
<IntermediateOutputPath>$(BaseOutputDirectory)</IntermediateOutputPath>
|
||||
<OutDir>$(BaseOutputDirectory)</OutDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FilesToSign Include="$(OutDir)\VisualStudioTools.zip.cat">
|
||||
<Authenticode>Microsoft400</Authenticode>
|
||||
</FilesToSign>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="packages\Microsoft.VisualStudioEng.MicroBuild.Core.0.4.1\build\Microsoft.VisualStudioEng.MicroBuild.Core.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.VisualStudioEng.MicroBuild.Core" version="0.4.1" targetFramework="native" developmentDependency="true" />
|
||||
</packages>
|
||||
@@ -0,0 +1,91 @@
|
||||
[*.{cpp,h}]
|
||||
|
||||
# Naming convention rules (note: currently need to be ordered from more to less specific)
|
||||
|
||||
cpp_naming_rule.aactor_prefixed.symbols = aactor_class
|
||||
cpp_naming_rule.aactor_prefixed.style = aactor_style
|
||||
|
||||
cpp_naming_rule.swidget_prefixed.symbols = swidget_class
|
||||
cpp_naming_rule.swidget_prefixed.style = swidget_style
|
||||
|
||||
cpp_naming_rule.uobject_prefixed.symbols = uobject_class
|
||||
cpp_naming_rule.uobject_prefixed.style = uobject_style
|
||||
|
||||
cpp_naming_rule.booleans_prefixed.symbols = boolean_vars
|
||||
cpp_naming_rule.booleans_prefixed.style = boolean_style
|
||||
|
||||
cpp_naming_rule.structs_prefixed.symbols = structs
|
||||
cpp_naming_rule.structs_prefixed.style = unreal_engine_structs
|
||||
|
||||
cpp_naming_rule.enums_prefixed.symbols = enums
|
||||
cpp_naming_rule.enums_prefixed.style = unreal_engine_enums
|
||||
|
||||
cpp_naming_rule.templates_prefixed.symbols = templates
|
||||
cpp_naming_rule.templates_prefixed.style = unreal_engine_templates
|
||||
|
||||
cpp_naming_rule.general_names.symbols = all_symbols
|
||||
cpp_naming_rule.general_names.style = unreal_engine_default
|
||||
|
||||
# Naming convention symbols
|
||||
|
||||
cpp_naming_symbols.aactor_class.applicable_kinds = class
|
||||
cpp_naming_symbols.aactor_class.applicable_type = AActor
|
||||
|
||||
cpp_naming_symbols.swidget_class.applicable_kinds = class
|
||||
cpp_naming_symbols.swidget_class.applicable_type = SWidget
|
||||
|
||||
cpp_naming_symbols.uobject_class.applicable_kinds = class
|
||||
cpp_naming_symbols.uobject_class.applicable_type = UObject
|
||||
|
||||
cpp_naming_symbols.boolean_vars.applicable_kinds = local,parameter,field
|
||||
cpp_naming_symbols.boolean_vars.applicable_type = bool
|
||||
|
||||
cpp_naming_symbols.enums.applicable_kinds = enum
|
||||
|
||||
cpp_naming_symbols.templates.applicable_kinds = template_class
|
||||
|
||||
cpp_naming_symbols.structs.applicable_kinds = struct
|
||||
|
||||
cpp_naming_symbols.all_symbols.applicable_kinds = *
|
||||
|
||||
# Naming convention styles
|
||||
|
||||
cpp_naming_style.unreal_engine_default.capitalization = pascal_case
|
||||
cpp_naming_style.unreal_engine_default.required_prefix =
|
||||
cpp_naming_style.unreal_engine_default.required_suffix =
|
||||
cpp_naming_style.unreal_engine_default.word_separator =
|
||||
|
||||
cpp_naming_style.unreal_engine_enums.capitalization = pascal_case
|
||||
cpp_naming_style.unreal_engine_enums.required_prefix = E
|
||||
cpp_naming_style.unreal_engine_enums.required_suffix =
|
||||
cpp_naming_style.unreal_engine_enums.word_separator =
|
||||
|
||||
cpp_naming_style.unreal_engine_templates.capitalization = pascal_case
|
||||
cpp_naming_style.unreal_engine_templates.required_prefix = T
|
||||
cpp_naming_style.unreal_engine_templates.required_suffix =
|
||||
cpp_naming_style.unreal_engine_templates.word_separator =
|
||||
|
||||
cpp_naming_style.unreal_engine_structs.capitalization = pascal_case
|
||||
cpp_naming_style.unreal_engine_structs.required_prefix = F
|
||||
cpp_naming_style.unreal_engine_structs.required_suffix =
|
||||
cpp_naming_style.unreal_engine_structs.word_separator =
|
||||
|
||||
cpp_naming_style.uobject_style.capitalization = pascal_case
|
||||
cpp_naming_style.uobject_style.required_prefix = U
|
||||
cpp_naming_style.uobject_style.required_suffix =
|
||||
cpp_naming_style.uobject_style.word_separator =
|
||||
|
||||
cpp_naming_style.aactor_style.capitalization = pascal_case
|
||||
cpp_naming_style.aactor_style.required_prefix = A
|
||||
cpp_naming_style.aactor_style.required_suffix =
|
||||
cpp_naming_style.aactor_style.word_separator =
|
||||
|
||||
cpp_naming_style.swidget_style.capitalization = pascal_case
|
||||
cpp_naming_style.swidget_style.required_prefix = S
|
||||
cpp_naming_style.swidget_style.required_suffix =
|
||||
cpp_naming_style.swidget_style.word_separator =
|
||||
|
||||
cpp_naming_style.boolean_style.capitalization = pascal_case
|
||||
cpp_naming_style.boolean_style.required_prefix = b
|
||||
cpp_naming_style.boolean_style.required_suffix =
|
||||
cpp_naming_style.boolean_style.word_separator =
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
#include "VisualStudioBlueprintDebuggerHelperModule.h"
|
||||
#include <Modules/ModuleManager.h>
|
||||
#include <UObject/Script.h>
|
||||
#include <UObject/Stack.h>
|
||||
#include <UObject/Object.h>
|
||||
#include <Templates/Casts.h>
|
||||
#include <Kismet2/KismetDebugUtilities.h>
|
||||
#include <Containers/Array.h>
|
||||
#include <UObject/Class.h>
|
||||
#include <Engine/BlueprintGeneratedClass.h>
|
||||
#include <Engine/Blueprint.h>
|
||||
#include <Runtime/Launch/Resources/Version.h>
|
||||
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 4
|
||||
#include <Blueprint/BlueprintExceptionInfo.h>
|
||||
#endif
|
||||
#include <Internationalization/Text.h>
|
||||
#include <HAL/Platform.h>
|
||||
#include <EdGraph/EdGraphNode.h>
|
||||
#include <EdGraph/EdGraphPin.h>
|
||||
#include <Templates/SharedPointer.h>
|
||||
#include <Templates/Tuple.h>
|
||||
#include <CoreGlobals.h>
|
||||
#include <map>
|
||||
|
||||
IMPLEMENT_MODULE(FVisualStudioBlueprintDebuggerHelper, VisualStudioBlueprintDebuggerHelper);
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogVisualStudioBlueprintDebuggerHelper);
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
#define FCustomBlueprintPropertyInfo TSharedPtr<FPropertyInstanceInfo>
|
||||
#else
|
||||
#define FCustomBlueprintPropertyInfo FDebugInfo
|
||||
#endif
|
||||
|
||||
struct FVSNodePinRuntimeInformation
|
||||
{
|
||||
UEdGraphPin* Pin;
|
||||
FCustomBlueprintPropertyInfo Property;
|
||||
|
||||
FVSNodePinRuntimeInformation(UEdGraphPin* InPin, FCustomBlueprintPropertyInfo InProperty)
|
||||
: Pin(InPin)
|
||||
, Property(InProperty)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct FVSNodeData
|
||||
{
|
||||
FText NodeName;
|
||||
TArray<TSharedPtr<FVSNodePinRuntimeInformation>> Properties;
|
||||
int32 ScriptEntryTag;
|
||||
const UEdGraphNode* Node;
|
||||
};
|
||||
|
||||
struct FVSNodesRuntimeInformation
|
||||
{
|
||||
TArray<TSharedPtr<FVSNodeData>> Nodes;
|
||||
};
|
||||
|
||||
struct FVSBlueprintRuntimeInformation
|
||||
{
|
||||
TArray<TTuple<UBlueprint*, TSharedPtr<FVSNodesRuntimeInformation>>> RunningBlueprints;
|
||||
};
|
||||
|
||||
struct StackTraceHelper
|
||||
{
|
||||
int32 ScriptEntryTag;
|
||||
FString NodeName;
|
||||
};
|
||||
|
||||
// Keep exported so we can read it.
|
||||
VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API FVSBlueprintRuntimeInformation BlueprintsRuntimeInformation;
|
||||
|
||||
VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API std::map<void*, StackTraceHelper> StackFrameInformation;
|
||||
|
||||
VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API const char* DebuggerHelperVersion = "1.0.0";
|
||||
|
||||
void FVisualStudioBlueprintDebuggerHelper::StartupModule()
|
||||
{
|
||||
CurrentScriptEntryTag = 0;
|
||||
|
||||
FBlueprintContextTracker::OnEnterScriptContext.AddRaw(
|
||||
this,
|
||||
&FVisualStudioBlueprintDebuggerHelper::OnEnterScriptContext);
|
||||
|
||||
FBlueprintContextTracker::OnExitScriptContext.AddRaw(
|
||||
this,
|
||||
&FVisualStudioBlueprintDebuggerHelper::OnExitScriptContext);
|
||||
|
||||
FBlueprintCoreDelegates::OnScriptException.AddRaw(
|
||||
this,
|
||||
&FVisualStudioBlueprintDebuggerHelper::OnScriptException);
|
||||
}
|
||||
|
||||
void FVisualStudioBlueprintDebuggerHelper::ShutdownModule()
|
||||
{
|
||||
FBlueprintCoreDelegates::OnScriptException.RemoveAll(this);
|
||||
FBlueprintContextTracker::OnExitScriptContext.RemoveAll(this);
|
||||
FBlueprintContextTracker::OnEnterScriptContext.RemoveAll(this);
|
||||
}
|
||||
|
||||
void FVisualStudioBlueprintDebuggerHelper::OnEnterScriptContext(
|
||||
const struct FBlueprintContextTracker& Context,
|
||||
const UObject* SourceObject,
|
||||
const UFunction* Function)
|
||||
{
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentScriptEntryTag = Context.GetScriptEntryTag();
|
||||
}
|
||||
|
||||
void FVisualStudioBlueprintDebuggerHelper::OnExitScriptContext(const struct FBlueprintContextTracker& Context)
|
||||
{
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto ItRunningBlueprints = BlueprintsRuntimeInformation.RunningBlueprints.CreateIterator(); ItRunningBlueprints; ++ItRunningBlueprints)
|
||||
{
|
||||
auto& RunningBlueprint = ItRunningBlueprints->Value;
|
||||
for (auto ItNodeData = RunningBlueprint->Nodes.CreateIterator(); ItNodeData; ++ItNodeData)
|
||||
{
|
||||
if ((*ItNodeData)->ScriptEntryTag == Context.GetScriptEntryTag())
|
||||
{
|
||||
ItNodeData.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!RunningBlueprint->Nodes.Num())
|
||||
{
|
||||
ItRunningBlueprints.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto ItStackFrameInfo = StackFrameInformation.begin(); ItStackFrameInfo != StackFrameInformation.end();)
|
||||
{
|
||||
if (ItStackFrameInfo->second.ScriptEntryTag == Context.GetScriptEntryTag())
|
||||
{
|
||||
ItStackFrameInfo = StackFrameInformation.erase(ItStackFrameInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
++ItStackFrameInfo;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentScriptEntryTag--;
|
||||
}
|
||||
|
||||
void FVisualStudioBlueprintDebuggerHelper::OnScriptException(
|
||||
const UObject* Owner,
|
||||
const struct FFrame& Stack,
|
||||
const FBlueprintExceptionInfo& ExceptionInfo)
|
||||
{
|
||||
EBlueprintExceptionType::Type ExceptionType = ExceptionInfo.GetType();
|
||||
if (ExceptionType != EBlueprintExceptionType::Type::Tracepoint &&
|
||||
ExceptionType != EBlueprintExceptionType::Type::WireTracepoint &&
|
||||
ExceptionType != EBlueprintExceptionType::Type::Breakpoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UFunction* NodeFunction = Cast<UFunction>(Stack.Node);
|
||||
if (!NodeFunction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UBlueprintGeneratedClass* BlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(NodeFunction->GetOuter());
|
||||
if (!BlueprintGeneratedClass)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UBlueprint* Blueprint = Cast<UBlueprint>(BlueprintGeneratedClass->ClassGeneratedBy);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 BreakpointOffset = Stack.Code - Stack.Node->Script.GetData() - 1;
|
||||
const UEdGraphNode* NodeStoppedAt = FKismetDebugUtilities::FindSourceNodeForCodeLocation(Owner, Stack.Node, BreakpointOffset, /*bAllowImpreciseHit=*/ true);
|
||||
if (!NodeStoppedAt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StackFrameInformation[NodeFunction] = { CurrentScriptEntryTag, FString::Printf(TEXT("%s::%s"), *Blueprint->GetFriendlyName(), *NodeStoppedAt->GetNodeTitle(ENodeTitleType::Type::ListView).ToString()) };
|
||||
TTuple<UBlueprint*, TSharedPtr<FVSNodesRuntimeInformation>>* ExistingNodesRuntimeInformationTuple = BlueprintsRuntimeInformation.RunningBlueprints.FindByPredicate([&Blueprint](const TTuple<UBlueprint*, TSharedPtr<FVSNodesRuntimeInformation>>& Tuple) {
|
||||
return Tuple.Key == Blueprint;
|
||||
});
|
||||
|
||||
TSharedPtr<FVSNodesRuntimeInformation> NodesRuntimeInformation;
|
||||
if (!ExistingNodesRuntimeInformationTuple)
|
||||
{
|
||||
NodesRuntimeInformation = MakeShared<FVSNodesRuntimeInformation>();
|
||||
BlueprintsRuntimeInformation.RunningBlueprints.Add(MakeTuple(Blueprint, NodesRuntimeInformation));
|
||||
}
|
||||
else
|
||||
{
|
||||
NodesRuntimeInformation = ExistingNodesRuntimeInformationTuple->Value;
|
||||
}
|
||||
|
||||
TSharedPtr<FVSNodeData> CurrentNodeData;
|
||||
if (NodesRuntimeInformation->Nodes.Num() == 0 || NodeStoppedAt != NodesRuntimeInformation->Nodes.Top()->Node)
|
||||
{
|
||||
CurrentNodeData = MakeShared<FVSNodeData>();
|
||||
CurrentNodeData->Node = NodeStoppedAt;
|
||||
CurrentNodeData->NodeName = NodeStoppedAt->GetNodeTitle(ENodeTitleType::Type::ListView);
|
||||
CurrentNodeData->ScriptEntryTag = CurrentScriptEntryTag;
|
||||
NodesRuntimeInformation->Nodes.Push(CurrentNodeData);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentNodeData = NodesRuntimeInformation->Nodes.Top();
|
||||
}
|
||||
|
||||
FCustomBlueprintPropertyInfo PinInstanceInfo;
|
||||
for (auto GraphPin : NodeStoppedAt->Pins)
|
||||
{
|
||||
FKismetDebugUtilities::EWatchTextResult DebugResult = FKismetDebugUtilities::GetDebugInfo(PinInstanceInfo, Blueprint, (UObject*)Owner, GraphPin);
|
||||
if (DebugResult != FKismetDebugUtilities::EWTR_Valid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TSharedPtr<FVSNodePinRuntimeInformation>* Existing = CurrentNodeData->Properties.FindByPredicate([&GraphPin](TSharedPtr<FVSNodePinRuntimeInformation>& PinInfo) {
|
||||
return PinInfo->Pin == GraphPin;
|
||||
});
|
||||
|
||||
if (!Existing)
|
||||
{
|
||||
CurrentNodeData->Properties.Add(MakeShared<FVSNodePinRuntimeInformation>(GraphPin, PinInstanceInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
(*Existing)->Property = PinInstanceInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <Modules/ModuleInterface.h>
|
||||
#include <Modules/ModuleManager.h>
|
||||
#include <UObject/Script.h>
|
||||
#include <UObject/Stack.h>
|
||||
#include <UObject/Object.h>
|
||||
#include <Logging/LogMacros.h>
|
||||
#include <UObject/Class.h>
|
||||
#include <HAL/Platform.h>
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogVisualStudioBlueprintDebuggerHelper, Log, All);
|
||||
|
||||
class FVisualStudioBlueprintDebuggerHelper : public FDefaultModuleImpl
|
||||
{
|
||||
private:
|
||||
void OnScriptException(const UObject* Owner, const struct FFrame& Stack, const FBlueprintExceptionInfo& ExceptionInfo);
|
||||
void OnEnterScriptContext(const struct FBlueprintContextTracker& Context, const UObject* SourceObject, const UFunction* Function);
|
||||
void OnExitScriptContext(const struct FBlueprintContextTracker& Context);
|
||||
|
||||
int32 CurrentScriptEntryTag;
|
||||
|
||||
public:
|
||||
void StartupModule() override;
|
||||
void ShutdownModule() override;
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class VisualStudioBlueprintDebuggerHelper: ModuleRules
|
||||
{
|
||||
public VisualStudioBlueprintDebuggerHelper(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
OptimizeCode = CodeOptimization.Never;
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"ApplicationCore",
|
||||
"AssetRegistry",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Json",
|
||||
"JsonUtilities",
|
||||
"Kismet",
|
||||
"UnrealEd",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"ToolMenus",
|
||||
"EditorSubsystem",
|
||||
"MainFrame",
|
||||
"BlueprintGraph",
|
||||
"VisualStudioDTE",
|
||||
"EditorStyle",
|
||||
"Projects"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "BlueprintAssetHelpers.h"
|
||||
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Blueprint/BlueprintSupport.h"
|
||||
#include "Engine/BlueprintCore.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
#include "Misc/ScopeExit.h"
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
namespace VisualStudioTools
|
||||
{
|
||||
namespace AssetHelpers
|
||||
{
|
||||
/*
|
||||
* These helpers handle the usage of some APIs that were deprecated in 5.1
|
||||
* but the replacements are not available in older versions.
|
||||
* Might be overridden by the `Build.cs` rules
|
||||
*/
|
||||
#if FILTER_ASSETS_BY_CLASS_PATH
|
||||
|
||||
void SetBlueprintClassFilter(FARFilter& InOutFilter)
|
||||
{
|
||||
// UE5.1 deprecated the API to filter using class names
|
||||
InOutFilter.ClassPaths.Add(UBlueprintCore::StaticClass()->GetClassPathName());
|
||||
}
|
||||
|
||||
static FString GetObjectPathString(const FAssetData& InAssetData)
|
||||
{
|
||||
// UE5.1 deprecated 'FAssetData::ObjectPath' in favor of 'FAssetData::GetObjectPathString()'
|
||||
return InAssetData.GetObjectPathString();
|
||||
}
|
||||
|
||||
#else // FILTER_ASSETS_BY_CLASS_PATH
|
||||
|
||||
void SetBlueprintClassFilter(FARFilter& InOutFilter)
|
||||
{
|
||||
InOutFilter.ClassNames.Add(UBlueprintCore::StaticClass()->GetFName());
|
||||
}
|
||||
|
||||
static FString GetObjectPathString(const FAssetData& InAssetData)
|
||||
{
|
||||
return InAssetData.ObjectPath.ToString();
|
||||
}
|
||||
|
||||
#endif // FILTER_ASSETS_BY_CLASS_PATH
|
||||
|
||||
void ForEachAsset(
|
||||
const TArray<FAssetData>& TargetAssets,
|
||||
TFunctionRef<void(UBlueprintGeneratedClass*, const FAssetData& AssetData)> Callback)
|
||||
{
|
||||
// Show a simpler logging output.
|
||||
// LogTimes are still useful to tell how long it takes to process each asset.
|
||||
TGuardValue<bool> DisableLogVerbosity(GPrintLogVerbosity, false);
|
||||
TGuardValue<bool> DisableLogCategory(GPrintLogCategory, false);
|
||||
|
||||
// We're about to load the assets which might trigger a ton of log messages
|
||||
// Temporarily suppress them during this stage.
|
||||
GEngine->Exec(nullptr, TEXT("log LogVisualStudioTools only"));
|
||||
ON_SCOPE_EXIT
|
||||
{
|
||||
GEngine->Exec(nullptr, TEXT("log reset"));
|
||||
};
|
||||
|
||||
FStreamableManager AssetLoader;
|
||||
|
||||
for (int32 Idx = 0; Idx < TargetAssets.Num(); Idx++)
|
||||
{
|
||||
const FAssetData AssetData = TargetAssets[Idx];
|
||||
FSoftClassPath GenClassPath = AssetData.GetTagValueRef<FString>(FBlueprintTags::GeneratedClassPath);
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Processing blueprints [%d/%d]: %s"), Idx + 1, TargetAssets.Num(), *GenClassPath.ToString());
|
||||
|
||||
TSharedPtr<FStreamableHandle> Handle = AssetLoader.RequestSyncLoad(GenClassPath);
|
||||
ON_SCOPE_EXIT
|
||||
{
|
||||
// We're done, notify an unload.
|
||||
Handle->ReleaseHandle();
|
||||
};
|
||||
|
||||
if (!Handle.IsValid())
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Warning, TEXT("Failed to get a streamable handle for Blueprint. Skipping. GenClassPath: %s"), *GenClassPath.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto BlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(Handle->GetLoadedAsset()))
|
||||
{
|
||||
Callback(BlueprintGeneratedClass, AssetData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log some extra information to help the user understand why the asset failed to load.
|
||||
|
||||
FString ObjectPathString = AssetHelpers::GetObjectPathString(AssetData);
|
||||
|
||||
FString Msg = !GenClassPath.ToString().Contains(ObjectPathString)
|
||||
? FString::Printf(
|
||||
TEXT("ObjectPath is not compatible with GenClassPath, consider re-saving it to avoid future issues. { ObjectPath: %s, GenClassPath: %s }"),
|
||||
*ObjectPathString,
|
||||
*GenClassPath.ToString())
|
||||
: FString::Printf(TEXT("ClassPath: %s"), *GenClassPath.ToString());
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Warning, TEXT("Failed to load Blueprint. Skipping. %s"), *Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
|
||||
class UBlueprintGeneratedClass;
|
||||
|
||||
namespace VisualStudioTools
|
||||
{
|
||||
namespace AssetHelpers
|
||||
{
|
||||
void SetBlueprintClassFilter(FARFilter& InOutFilter);
|
||||
|
||||
/**
|
||||
* Loads each blueprint asset and invokes the callback with the resulting blueprint generated class.
|
||||
* Each iteration will load the asset using a FStreamableHandle and verify that is a valid blueprint
|
||||
* before invoking the callback.
|
||||
*/
|
||||
void ForEachAsset(
|
||||
const TArray<FAssetData>& TargetAssets,
|
||||
TFunctionRef<void(UBlueprintGeneratedClass*, const FAssetData& AssetData)> Callback);
|
||||
|
||||
} // namespace AssetHelpers
|
||||
} // namespace VisualStudioTools
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "BlueprintReferencesCommandlet.h"
|
||||
|
||||
#include "Algo/Find.h"
|
||||
#include "Algo/Transform.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "BlueprintAssetHelpers.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "FindInBlueprintManager.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/ScopeExit.h"
|
||||
#include "Policies/CondensedJsonPrintPolicy.h"
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
namespace VisualStudioTools
|
||||
{
|
||||
static FString StripClassPrefix(const FString& InClassName)
|
||||
{
|
||||
if (InClassName.IsEmpty())
|
||||
{
|
||||
return InClassName;
|
||||
}
|
||||
|
||||
size_t PrefixSize = 0;
|
||||
|
||||
const TCHAR ClassPrefixChar = InClassName[0];
|
||||
switch (ClassPrefixChar)
|
||||
{
|
||||
case TEXT('I'):
|
||||
case TEXT('A'):
|
||||
case TEXT('U'):
|
||||
// If it is a class prefix, check for deprecated class prefix also
|
||||
if (InClassName.Len() > 12 && FCString::Strncmp(&(InClassName[1]), TEXT("DEPRECATED_"), 11) == 0)
|
||||
{
|
||||
PrefixSize = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrefixSize = 1;
|
||||
}
|
||||
break;
|
||||
case TEXT('F'):
|
||||
case TEXT('T'):
|
||||
// Struct prefixes are also fine.
|
||||
PrefixSize = 1;
|
||||
break;
|
||||
default:
|
||||
PrefixSize = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return InClassName.RightChop(PrefixSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the asset data matching the given FindInBlueprints query.
|
||||
*/
|
||||
TArray<FAssetData> SearchForCandidateAssets(const FString& SearchQuery)
|
||||
{
|
||||
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
||||
AssetRegistry.SearchAllAssets(true);
|
||||
|
||||
TArray<FSearchResult> OutItemsFound;
|
||||
FStreamSearch StreamSearch(SearchQuery);
|
||||
while (!StreamSearch.IsComplete())
|
||||
{
|
||||
FFindInBlueprintSearchManager::Get().Tick(0.0);
|
||||
}
|
||||
|
||||
// Execute the search and get all the assets in the result.
|
||||
StreamSearch.GetFilteredItems(OutItemsFound);
|
||||
|
||||
|
||||
TArray<FAssetData> OutTargetAssets;
|
||||
Algo::Transform(OutItemsFound, OutTargetAssets,
|
||||
[&](const FSearchResult& Item)
|
||||
{
|
||||
// The DisplayText property of the result contains the blueprint's object path
|
||||
// Use that to find the respective asset in the registry
|
||||
#if FILTER_ASSETS_BY_CLASS_PATH
|
||||
return AssetRegistry.GetAssetByObjectPath(FSoftObjectPath(*Item->DisplayText.ToString()));
|
||||
#else
|
||||
return AssetRegistry.GetAssetByObjectPath(*Item->DisplayText.ToString());
|
||||
#endif // FILTER_ASSETS_BY_CLASS_PATH
|
||||
});
|
||||
|
||||
return OutTargetAssets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads each blueprint asset and filters the collection to items which use the
|
||||
* target UFunction in their call graph, matching the native class and function names.
|
||||
*/
|
||||
TMap<FString, FAssetData> GetConfirmedAssets(
|
||||
const FString& FunctionName, const FString& ClassNameWithoutPrefix, const TArray<FAssetData>& InAssets)
|
||||
{
|
||||
TMap<FString, FAssetData> OutResults;
|
||||
|
||||
AssetHelpers::ForEachAsset(InAssets,
|
||||
[&](UBlueprintGeneratedClass* BlueprintClassName, const FAssetData AssetData)
|
||||
{
|
||||
auto MatchingFunction = Algo::FindByPredicate(BlueprintClassName->CalledFunctions,
|
||||
[&](const UFunction* Fn)
|
||||
{
|
||||
return Fn->HasAnyFunctionFlags(EFunctionFlags::FUNC_Native)
|
||||
&& Fn->GetName() == FunctionName
|
||||
&& Fn->GetOwnerClass()->GetName() == ClassNameWithoutPrefix;
|
||||
});
|
||||
|
||||
if (MatchingFunction != nullptr)
|
||||
{
|
||||
OutResults.Add(BlueprintClassName->GetName(), AssetData);
|
||||
}
|
||||
});
|
||||
|
||||
return OutResults;
|
||||
}
|
||||
using JsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
|
||||
|
||||
static void SerializeBlueprintReference(
|
||||
TSharedRef<JsonWriter>& Json, const FString& BlueprintClassName, const FAssetData& Asset)
|
||||
{
|
||||
FString PackageFileName;
|
||||
FString PackageFile;
|
||||
FString PackageFilePath;
|
||||
if (FPackageName::TryConvertLongPackageNameToFilename(Asset.GetPackage()->GetName(), PackageFileName) &&
|
||||
FPackageName::FindPackageFileWithoutExtension(PackageFileName, PackageFile))
|
||||
{
|
||||
PackageFilePath = FPaths::ConvertRelativePathToFull(MoveTemp(PackageFile));
|
||||
}
|
||||
|
||||
Json->WriteObjectStart();
|
||||
Json->WriteValue(TEXT("name"), BlueprintClassName);
|
||||
Json->WriteValue(TEXT("path"), PackageFilePath);
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
|
||||
static void SerializeBlueprints(
|
||||
TSharedRef<JsonWriter>& Json, const TMap<FString, FAssetData>& InAssets)
|
||||
{
|
||||
Json->WriteIdentifierPrefix(TEXT("blueprints"));
|
||||
Json->WriteArrayStart();
|
||||
|
||||
for (auto& Item : InAssets)
|
||||
{
|
||||
const FString& BlueprintClassName = Item.Key;
|
||||
const FAssetData& Asset = Item.Value;
|
||||
SerializeBlueprintReference(Json, BlueprintClassName, Asset);
|
||||
}
|
||||
|
||||
Json->WriteArrayEnd();
|
||||
}
|
||||
|
||||
static void SerializeMetadata(
|
||||
TSharedRef<JsonWriter>& Json, int TotalAssetCount)
|
||||
{
|
||||
Json->WriteIdentifierPrefix(TEXT("metadata"));
|
||||
Json->WriteObjectStart();
|
||||
{
|
||||
Json->WriteValue(TEXT("asset_count"), TotalAssetCount);
|
||||
}
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
|
||||
static void SerializeResults(
|
||||
const TMap<FString, FAssetData>& InAssets,
|
||||
FArchive& OutArchive,
|
||||
int TotalAssetCount)
|
||||
{
|
||||
TSharedRef<JsonWriter> Json = JsonWriter::Create(&OutArchive);
|
||||
Json->WriteObjectStart();
|
||||
|
||||
SerializeBlueprints(Json, InAssets);
|
||||
SerializeMetadata(Json, TotalAssetCount);
|
||||
|
||||
Json->WriteObjectEnd();
|
||||
Json->Close();
|
||||
}
|
||||
} // namespace VisualStudioTools
|
||||
|
||||
static constexpr auto SymbolParamVal = TEXT("symbol");
|
||||
|
||||
UVsBlueprintReferencesCommandlet::UVsBlueprintReferencesCommandlet()
|
||||
: Super()
|
||||
{
|
||||
HelpDescription = TEXT("Commandlet for generating data used by Blueprint support in Visual Studio.");
|
||||
|
||||
HelpParamNames.Add(SymbolParamVal);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] Fully qualified symbol to search for in the blueprints."));
|
||||
|
||||
HelpUsage = TEXT("<Editor-Cmd.exe> <path_to_uproject> -run=VsBlueprintReferences -output=<path_to_output_file> -symbol=<ClassName::FunctionName> [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash]");
|
||||
}
|
||||
|
||||
int32 UVsBlueprintReferencesCommandlet::Run(
|
||||
TArray<FString>& Tokens,
|
||||
TArray<FString>& Switches,
|
||||
TMap<FString, FString>& ParamVals,
|
||||
FArchive& OutArchive)
|
||||
{
|
||||
using namespace VisualStudioTools;
|
||||
GIsRunning = true; // Required for the blueprint search to work.
|
||||
|
||||
FString* ReferencesSymbol = ParamVals.Find(SymbolParamVal);
|
||||
if (ReferencesSymbol->IsEmpty())
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Missing required symbol parameter."));
|
||||
PrintHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
FString FunctionName;
|
||||
FString ClassNameNative;
|
||||
if (!ReferencesSymbol->Split(TEXT("::"), &ClassNameNative, &FunctionName))
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Reference parameter should be in the qualified 'NativeClassName::MethodName' format."));
|
||||
PrintHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Execute the search in two stages:
|
||||
// 1. Use FindInBlueprints to get all candidate blueprints with calls to functions that match the requested symbol
|
||||
// 2. Confirm the blueprints reference the requested function, by matching the target UFunction in their call graph.
|
||||
// The first step acts as a filter to avoid loading too many blueprints to inspect their call graph.
|
||||
// The second step is required because the FiB data does not always allow for searching with the function
|
||||
// qualified with the owned class name, if the function is static.
|
||||
|
||||
FString ClassNameWithoutPrefix = StripClassPrefix(ClassNameNative);
|
||||
|
||||
// Create a FiB search query for function nodes where the native name matches the requested symbol
|
||||
FString SearchValue = FString::Printf(TEXT("Nodes(\"Native Name\"=+%s & ClassName=K2Node_CallFunction)"), *FunctionName);
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Blueprint search query: %s"), *SearchValue);
|
||||
|
||||
// Step 1: Execute the Fib search
|
||||
TArray<FAssetData> TargetAssets = SearchForCandidateAssets(SearchValue);
|
||||
|
||||
// Step 2: Load the assets to confirm they are a match
|
||||
TMap<FString, FAssetData> MatchAssets = GetConfirmedAssets(FunctionName, ClassNameWithoutPrefix, TargetAssets);
|
||||
|
||||
// Finally, write the results back to the output
|
||||
SerializeResults(MatchAssets, OutArchive, TargetAssets.Num());
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Found %d blueprints."), MatchAssets.Num());
|
||||
return 0;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VisualStudioToolsCommandletBase.h"
|
||||
|
||||
#include "BlueprintReferencesCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UVsBlueprintReferencesCommandlet
|
||||
: public UVisualStudioToolsCommandletBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UVsBlueprintReferencesCommandlet();
|
||||
|
||||
int32 Run(
|
||||
TArray<FString>& Tokens,
|
||||
TArray<FString>& Switches,
|
||||
TMap<FString, FString>& ParamVals,
|
||||
FArchive& OutArchive) override;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "VisualStudioDTE.h"
|
||||
#include <utility>
|
||||
|
||||
class FSmartBSTR
|
||||
{
|
||||
public:
|
||||
FSmartBSTR() : data(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FSmartBSTR(const FSmartBSTR& Other)
|
||||
{
|
||||
if (Other.data) data = SysAllocString(Other.data);
|
||||
else data = nullptr;
|
||||
}
|
||||
|
||||
FSmartBSTR(FSmartBSTR&& Other)
|
||||
{
|
||||
data = std::exchange(Other.data, nullptr);
|
||||
}
|
||||
|
||||
FSmartBSTR(const FString& Other)
|
||||
{
|
||||
data = SysAllocString(*Other);
|
||||
}
|
||||
|
||||
FSmartBSTR(const OLECHAR *Ptr)
|
||||
{
|
||||
if (Ptr) data = SysAllocString(Ptr);
|
||||
else data = nullptr;
|
||||
}
|
||||
|
||||
~FSmartBSTR()
|
||||
{
|
||||
if (data) SysFreeString(data);
|
||||
}
|
||||
|
||||
FSmartBSTR& operator=(const FSmartBSTR& Other)
|
||||
{
|
||||
if (this == &Other) return *this;
|
||||
if (data) SysFreeString(data);
|
||||
if (Other.data) data = SysAllocString(Other.data);
|
||||
else data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FSmartBSTR& operator=(FSmartBSTR&& Other)
|
||||
{
|
||||
if (data) SysFreeString(data);
|
||||
data = std::exchange(Other.data, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BSTR operator*() const
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
private:
|
||||
BSTR data;
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
#include "VSServerCommandlet.h"
|
||||
#include "VSTestAdapterCommandlet.h"
|
||||
|
||||
#include "Windows/AllowWindowsPlatformTypes.h"
|
||||
|
||||
#include "HAL/PlatformNamedPipe.h"
|
||||
#include "Runtime/Core/Public/Async/TaskGraphInterfaces.h"
|
||||
#include "Runtime/Core/Public/Containers/Ticker.h"
|
||||
#include "Runtime/Engine/Classes/Engine/World.h"
|
||||
#include "Runtime/Engine/Public/TimerManager.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#include "Runtime\CoreUObject\Public\UObject\UObjectGlobals.h"
|
||||
#include <chrono>
|
||||
#include <codecvt>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
#include "Windows/HideWindowsPlatformTypes.h"
|
||||
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
static constexpr auto NamedPipeParam = TEXT("NamedPipe");
|
||||
static constexpr auto KillServerParam = TEXT("KillVSServer");
|
||||
|
||||
UVSServerCommandlet::UVSServerCommandlet()
|
||||
{
|
||||
HelpDescription = TEXT("Commandlet for Unreal Engine server mode.");
|
||||
HelpUsage = TEXT("<Editor-Cmd.exe> <path_to_uproject> -run=VSServer [-stdout -multiprocess -silent -unattended -AllowStdOutLogVerbosity -NoShaderCompile]");
|
||||
|
||||
HelpParamNames.Add(NamedPipeParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Required] The name of the named pipe used to communicate with Visual Studio."));
|
||||
|
||||
HelpParamNames.Add(KillServerParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] Quit the server mode commandlet immediately."));
|
||||
}
|
||||
|
||||
void UVSServerCommandlet::ExecuteSubCommandlet(FString ueServerNamedPipe)
|
||||
{
|
||||
char buffer[1024];
|
||||
DWORD dwRead;
|
||||
std::string result = "0";
|
||||
|
||||
// Open the named pipe.
|
||||
std::wstring pipeName = L"\\\\.\\pipe\\";
|
||||
pipeName.append(ueServerNamedPipe.GetCharArray().GetData());
|
||||
HANDLE HPipe = CreateFile(pipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (HPipe != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ConnectNamedPipe(HPipe, NULL);
|
||||
DWORD dwState;
|
||||
BOOL bSuccess = GetNamedPipeHandleState(HPipe, &dwState, NULL, NULL, NULL, NULL, 0);
|
||||
if (bSuccess)
|
||||
{
|
||||
// Read data from the named pipe.
|
||||
ReadFile(HPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL);
|
||||
buffer[dwRead] = '\0';
|
||||
std::string strSubCommandletParams(buffer, dwRead);
|
||||
FString SubCommandletParams = FString(strSubCommandletParams.c_str());
|
||||
|
||||
// Determine which sub-commandlet to invoke, and write back result response.
|
||||
if (SubCommandletParams.Contains("VSTestAdapter"))
|
||||
{
|
||||
UVSTestAdapterCommandlet *Commandlet = NewObject<UVSTestAdapterCommandlet>();
|
||||
try
|
||||
{
|
||||
int32 subCommandletResult = Commandlet->Main(SubCommandletParams);
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Exception invoking VSTestAdapter commandlet: %s"), UTF8_TO_TCHAR(ex.what()));
|
||||
result = "0";
|
||||
}
|
||||
}
|
||||
else if (SubCommandletParams.Contains("KillVSServer"))
|
||||
{
|
||||
// When KillVSServer is passed in, then kill the Unreal Editor process to end server mode.
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If cannot find which sub-commandlet to run, then return error.
|
||||
result = "1";
|
||||
}
|
||||
|
||||
WriteFile(HPipe, result.c_str(), result.size(), &dwRead, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 UVSServerCommandlet::Main(const FString &ServerParams)
|
||||
{
|
||||
TArray<FString> Tokens;
|
||||
TArray<FString> Switches;
|
||||
TMap<FString, FString> ParamVals;
|
||||
|
||||
ParseCommandLine(*ServerParams, Tokens, Switches, ParamVals);
|
||||
if (ParamVals.Contains(NamedPipeParam))
|
||||
{
|
||||
FString ueServerNamedPipe = ParamVals[NamedPipeParam];
|
||||
|
||||
// Infinite loop that listens to requests every second.
|
||||
while (true)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
ExecuteSubCommandlet(ueServerNamedPipe);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Missing named pipe parameter."));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include <string>
|
||||
|
||||
#include <Runtime/Core/Public/Misc/AutomationTest.h>
|
||||
#include <Runtime/CoreUObject/Public/UObject/ObjectMacros.h>
|
||||
#include <Runtime/Engine/Classes/Commandlets/Commandlet.h>
|
||||
|
||||
#include "VSServerCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UVSServerCommandlet
|
||||
: public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UVSServerCommandlet();
|
||||
|
||||
public:
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
|
||||
private:
|
||||
void ExecuteSubCommandlet(FString ueServerNamedPipe);
|
||||
};
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
#include "VSTestAdapterCommandlet.h"
|
||||
|
||||
#include "Runtime/Core/Public/Async/TaskGraphInterfaces.h"
|
||||
#include "Runtime/Core/Public/Containers/Ticker.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
static constexpr auto FiltersParam = TEXT("filters");
|
||||
static constexpr auto ListTestsParam = TEXT("listtests");
|
||||
static constexpr auto RunTestsParam = TEXT("runtests");
|
||||
static constexpr auto TestResultsFileParam = TEXT("testresultfile");
|
||||
static constexpr auto HelpParam = TEXT("help");
|
||||
|
||||
static void GetAllTests(TArray<FAutomationTestInfo>& OutTestList)
|
||||
{
|
||||
FAutomationTestFramework& Framework = FAutomationTestFramework::GetInstance();
|
||||
Framework.GetValidTestNames(OutTestList);
|
||||
}
|
||||
|
||||
static void ReadTestsFromFile(const FString& InFile, TArray<FAutomationTestInfo>& OutTestList)
|
||||
{
|
||||
TSet<FString> TestCommands;
|
||||
|
||||
// Wrapping in an inner scope to ensure automatic destruction of InStream object without explicitly calling .close().
|
||||
{
|
||||
std::wifstream InStream(*InFile);
|
||||
if (!InStream.good())
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to open file at path: %s"), *InFile);
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring Line;
|
||||
while (std::getline(InStream, Line))
|
||||
{
|
||||
if (Line.length() > 0)
|
||||
{
|
||||
TestCommands.Add(FString(Line.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetAllTests(OutTestList);
|
||||
for (int32 Idx = OutTestList.Num() - 1; Idx >= 0; Idx--)
|
||||
{
|
||||
if (!TestCommands.Contains(OutTestList[Idx].GetTestName()))
|
||||
{
|
||||
OutTestList.RemoveAt(Idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int32 ListTests(const FString& TargetFile)
|
||||
{
|
||||
std::wofstream OutFile(*TargetFile);
|
||||
if (!OutFile.good())
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to open file at path: %s"), *TargetFile);
|
||||
return 1;
|
||||
}
|
||||
|
||||
FAutomationTestFramework& Framework = FAutomationTestFramework::GetInstance();
|
||||
|
||||
TArray<FAutomationTestInfo> TestInfos;
|
||||
GetAllTests(TestInfos);
|
||||
|
||||
for (const auto& TestInfo : TestInfos)
|
||||
{
|
||||
const FString TestCommand = TestInfo.GetTestName();
|
||||
const FString DisplayName = TestInfo.GetDisplayName();
|
||||
const FString SourceFile = TestInfo.GetSourceFile();
|
||||
const int32 Line = TestInfo.GetSourceFileLine();
|
||||
|
||||
OutFile << *TestCommand << TEXT("|") << *DisplayName << TEXT("|") << Line << TEXT("|") << *SourceFile << std::endl;
|
||||
}
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Found %d tests"), TestInfos.Num());
|
||||
OutFile.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32 RunTests(const FString& TestListFile, const FString& ResultsFile)
|
||||
{
|
||||
std::wofstream OutFile(*ResultsFile);
|
||||
if (!OutFile.good())
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to open file at path: %s"), *ResultsFile);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TArray<FAutomationTestInfo> TestInfos;
|
||||
if (TestListFile.Equals(TEXT("All"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
GetAllTests(TestInfos);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadTestsFromFile(TestListFile, TestInfos);
|
||||
}
|
||||
|
||||
bool AllSuccessful = true;
|
||||
|
||||
FAutomationTestFramework& Framework = FAutomationTestFramework::GetInstance();
|
||||
|
||||
for (const FAutomationTestInfo& TestInfo : TestInfos)
|
||||
{
|
||||
const FString TestCommand = TestInfo.GetTestName();
|
||||
const FString DisplayName = TestInfo.GetDisplayName();
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Log, TEXT("Running %s"), *DisplayName);
|
||||
|
||||
const int32 RoleIndex = 0; // always default to "local" role index. Only used for multi-participant tests
|
||||
Framework.StartTestByName(TestCommand, RoleIndex);
|
||||
|
||||
FDateTime Last = FDateTime::UtcNow();
|
||||
|
||||
while (!Framework.ExecuteLatentCommands())
|
||||
{
|
||||
// Because we are not 'ticked' by the Engine we need to pump the TaskGraph
|
||||
FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
|
||||
|
||||
const FDateTime Now = FDateTime::UtcNow();
|
||||
const float Delta = static_cast<float>((Now - Last).GetTotalSeconds());
|
||||
|
||||
// .. and the core FTicker
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
FTSTicker::GetCoreTicker().Tick(Delta);
|
||||
#else
|
||||
FTicker::GetCoreTicker().Tick(Delta);
|
||||
#endif
|
||||
|
||||
Last = Now;
|
||||
}
|
||||
|
||||
FAutomationTestExecutionInfo ExecutionInfo;
|
||||
const bool CurrentTestSuccessful = Framework.StopTest(ExecutionInfo) && ExecutionInfo.GetErrorTotal() == 0;
|
||||
AllSuccessful = AllSuccessful && CurrentTestSuccessful;
|
||||
|
||||
const FString Result = CurrentTestSuccessful ? TEXT("OK") : TEXT("FAIL");
|
||||
|
||||
// [RUNTEST] is part of the protocol, so do not remove.
|
||||
OutFile << TEXT("[RUNTEST]") << *TestCommand << TEXT("|") << *DisplayName << TEXT("|") << *Result << TEXT("|") << ExecutionInfo.Duration << std::endl;
|
||||
|
||||
if (!CurrentTestSuccessful)
|
||||
{
|
||||
for (const auto& Entry : ExecutionInfo.GetEntries())
|
||||
{
|
||||
if (Entry.Event.Type == EAutomationEventType::Error)
|
||||
{
|
||||
OutFile << *Entry.Event.Message << std::endl;
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("%s"), *Entry.Event.Message);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Log, TEXT("Failed %s"), *DisplayName);
|
||||
}
|
||||
|
||||
OutFile.flush();
|
||||
}
|
||||
|
||||
return AllSuccessful ? 0 : 1;
|
||||
}
|
||||
|
||||
UVSTestAdapterCommandlet::UVSTestAdapterCommandlet()
|
||||
{
|
||||
HelpDescription = TEXT("Commandlet for generating data used by Blueprint support in Visual Studio.");
|
||||
HelpUsage = TEXT("<Editor-Cmd.exe> <path_to_uproject> -run=VSTestAdapter [-stdout -multiprocess -silent -unattended -AllowStdOutLogVerbosity -NoShaderCompile]");
|
||||
|
||||
HelpParamNames.Add(ListTestsParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Required] The file path to write the test cases retrieved from FAutomationTestFramework"));
|
||||
|
||||
HelpParamNames.Add(RunTestsParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Required] The test cases that will be sent to FAutomationTestFramework to run."));
|
||||
|
||||
HelpParamNames.Add(TestResultsFileParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Required] The output file from running test cases that we parse to retrieve test case results."));
|
||||
|
||||
HelpParamNames.Add(FiltersParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] List of test filters to enable separated by '+'. Default is 'application+smoke+product+perf+stress+negative'"));
|
||||
|
||||
HelpParamNames.Add(HelpParam);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] Print this help message and quit the commandlet immediately."));
|
||||
}
|
||||
|
||||
void UVSTestAdapterCommandlet::PrintHelp() const
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("%s"), *HelpDescription);
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Usage: %s"), *HelpUsage);
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Parameters:"));
|
||||
for (int32 Idx = 0; Idx < HelpParamNames.Num(); ++Idx)
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("\t-%s: %s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]);
|
||||
}
|
||||
}
|
||||
|
||||
int32 UVSTestAdapterCommandlet::Main(const FString& Params)
|
||||
{
|
||||
TArray<FString> Tokens;
|
||||
TArray<FString> Switches;
|
||||
TMap<FString, FString> ParamVals;
|
||||
|
||||
// Functionality for Unreal Engine Test Adapter.
|
||||
ParseCommandLine(*Params, Tokens, Switches, ParamVals);
|
||||
if (ParamVals.Contains(HelpParam))
|
||||
{
|
||||
PrintHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Default to all the test filters.
|
||||
auto filter = EAutomationTestFlags::ProductFilter | EAutomationTestFlags::SmokeFilter | EAutomationTestFlags::PerfFilter | EAutomationTestFlags::EngineFilter;
|
||||
if (ParamVals.Contains(FiltersParam))
|
||||
{
|
||||
FString filters = ParamVals[FiltersParam];
|
||||
if (filters.Contains("smoke"))
|
||||
{
|
||||
filter |= EAutomationTestFlags::SmokeFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter &= ~EAutomationTestFlags::SmokeFilter;
|
||||
}
|
||||
|
||||
if (filters.Contains("engine"))
|
||||
{
|
||||
filter |= EAutomationTestFlags::EngineFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter &= ~EAutomationTestFlags::EngineFilter;
|
||||
}
|
||||
|
||||
if (filters.Contains("product"))
|
||||
{
|
||||
filter |= EAutomationTestFlags::ProductFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter &= ~EAutomationTestFlags::ProductFilter;
|
||||
}
|
||||
|
||||
if (filters.Contains("perf"))
|
||||
{
|
||||
filter |= EAutomationTestFlags::PerfFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter &= ~EAutomationTestFlags::PerfFilter;
|
||||
}
|
||||
|
||||
if (filters.Contains("stress"))
|
||||
{
|
||||
filter |= EAutomationTestFlags::StressFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter &= ~EAutomationTestFlags::StressFilter;
|
||||
}
|
||||
|
||||
if (filters.Contains("negative"))
|
||||
{
|
||||
filter |= EAutomationTestFlags::NegativeFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter &= ~EAutomationTestFlags::NegativeFilter;
|
||||
}
|
||||
}
|
||||
|
||||
FAutomationTestFramework::GetInstance().SetRequestedTestFilter(filter);
|
||||
if (ParamVals.Contains(ListTestsParam))
|
||||
{
|
||||
return ListTests(ParamVals[ListTestsParam]);
|
||||
}
|
||||
else if (ParamVals.Contains(RunTestsParam) && ParamVals.Contains(TestResultsFileParam))
|
||||
{
|
||||
return RunTests(ParamVals[RunTestsParam], ParamVals[TestResultsFileParam]);
|
||||
}
|
||||
|
||||
PrintHelp();
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Commandlets/Commandlet.h"
|
||||
|
||||
#include <Runtime/Core/Public/Misc/AutomationTest.h>
|
||||
#include <Runtime/CoreUObject/Public/UObject/ObjectMacros.h>
|
||||
#include <Runtime/Engine/Classes/Commandlets/Commandlet.h>
|
||||
|
||||
#include "VSTestAdapterCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UVSTestAdapterCommandlet
|
||||
: public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UVSTestAdapterCommandlet();
|
||||
|
||||
public:
|
||||
virtual int32 Main(const FString &Params) override;
|
||||
|
||||
private:
|
||||
void PrintHelp() const;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
#include "Modules/ModuleInterface.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogVisualStudioTools);
|
||||
|
||||
class FVisualStudioToolsModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override {}
|
||||
virtual void ShutdownModule() override {}
|
||||
};
|
||||
|
||||
IMPLEMENT_MODULE(FVisualStudioToolsModule, VisualStudioTools)
|
||||
+576
@@ -0,0 +1,576 @@
|
||||
#include "VisualStudioToolsBlueprintBreakpointExtension.h"
|
||||
#include "FSmartBSTR.h"
|
||||
#include <Modules/ModuleManager.h>
|
||||
#include <Framework/MultiBox/MultiBoxBuilder.h>
|
||||
#include <BlueprintGraphClasses.h>
|
||||
#include <EditorSubsystem.h>
|
||||
#include <unknwn.h>
|
||||
#include <Windows/WindowsPlatformStackWalk.h>
|
||||
#include <Subsystems/Subsystem.h>
|
||||
#include <Windows/WindowsPlatformMisc.h>
|
||||
#include <Windows/WindowsPlatformProcess.h>
|
||||
#include <SourceCodeNavigation.h>
|
||||
#include <GraphEditorModule.h>
|
||||
#include <Containers/Array.h>
|
||||
#include <EdGraph/EdGraph.h>
|
||||
#include <EdGraph/EdGraphNode.h>
|
||||
#include <EdGraph/EdGraphPin.h>
|
||||
#include <Framework/Commands/UIAction.h>
|
||||
#include <Widgets/Notifications/SNotificationList.h>
|
||||
#include <Framework/Notifications/NotificationManager.h>
|
||||
#include <Misc/FileHelper.h>
|
||||
#include <Interfaces/IProjectManager.h>
|
||||
#include <Misc/UProjectInfo.h>
|
||||
#include <ProjectDescriptor.h>
|
||||
#include <Misc/App.h>
|
||||
#include <Runtime/Launch/Resources/Version.h>
|
||||
#include <EditorStyleSet.h>
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
#include <DbgHelp.h>
|
||||
#include <Psapi.h>
|
||||
#endif
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogUVisualStudioToolsBlueprintBreakpointExtension);
|
||||
|
||||
static const FName GraphEditorModuleName(TEXT("GraphEditor"));
|
||||
|
||||
void UVisualStudioToolsBlueprintBreakpointExtension::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
FGraphEditorModule& GraphEditorModule = FModuleManager::LoadModuleChecked<FGraphEditorModule>(GraphEditorModuleName);
|
||||
GraphEditorModule.GetAllGraphEditorContextMenuExtender().Add(
|
||||
FGraphEditorModule::FGraphEditorMenuExtender_SelectedNode::CreateUObject(this, &ThisClass::HandleOnExtendGraphEditorContextMenu));
|
||||
}
|
||||
|
||||
void UVisualStudioToolsBlueprintBreakpointExtension::Deinitialize()
|
||||
{
|
||||
FGraphEditorModule* GraphEditorModule = FModuleManager::GetModulePtr<FGraphEditorModule>(GraphEditorModuleName);
|
||||
if (!GraphEditorModule)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GraphEditorModule->GetAllGraphEditorContextMenuExtender().RemoveAll(
|
||||
[](const FGraphEditorModule::FGraphEditorMenuExtender_SelectedNode& Delegate) {
|
||||
FName LocalFunction = GET_FUNCTION_NAME_CHECKED(ThisClass, HandleOnExtendGraphEditorContextMenu);
|
||||
return Delegate.TryGetBoundFunctionName() == LocalFunction;
|
||||
});
|
||||
}
|
||||
|
||||
TSharedRef<FExtender> UVisualStudioToolsBlueprintBreakpointExtension::HandleOnExtendGraphEditorContextMenu(
|
||||
const TSharedRef<FUICommandList> CommandList,
|
||||
const UEdGraph* Graph,
|
||||
const UEdGraphNode* Node,
|
||||
const UEdGraphPin* Pin,
|
||||
bool /* bIsConst */)
|
||||
{
|
||||
TSharedRef<FExtender> Extender = MakeShared<FExtender>();
|
||||
if (!CanAddVisualStudioBreakpoint(Node, nullptr, nullptr))
|
||||
{
|
||||
return Extender;
|
||||
}
|
||||
|
||||
const FName ExtensionHook(TEXT("EdGraphSchemaNodeActions"));
|
||||
Extender->AddMenuExtension(
|
||||
ExtensionHook,
|
||||
EExtensionHook::After,
|
||||
CommandList,
|
||||
FMenuExtensionDelegate::CreateUObject(this, &ThisClass::AddVisualStudioBlueprintBreakpointMenuOption, Node));
|
||||
|
||||
return Extender;
|
||||
}
|
||||
|
||||
void UVisualStudioToolsBlueprintBreakpointExtension::AddVisualStudioBlueprintBreakpointMenuOption(FMenuBuilder& MenuBuilder, const UEdGraphNode *Node)
|
||||
{
|
||||
MenuBuilder.BeginSection(TEXT("VisualStudioTools"), FText::FromString("Visual Studio Tools"));
|
||||
MenuBuilder.AddMenuEntry(
|
||||
FText::FromString("Set breakpoint in Visual Studio"),
|
||||
FText::FromString("This will set a breakpoint in Visual Studio so the native debugger can break the execution"),
|
||||
FSlateIcon(),
|
||||
FUIAction(FExecuteAction::CreateUObject(this, &ThisClass::AddVisualStudioBreakpoint, Node)));
|
||||
MenuBuilder.EndSection();
|
||||
}
|
||||
|
||||
FString UVisualStudioToolsBlueprintBreakpointExtension::GetProjectPath(const FString &ProjectDir)
|
||||
{
|
||||
FString ProjectPath;
|
||||
if (!FFileHelper::LoadFileToString(ProjectPath, *(FPaths::EngineIntermediateDir() / TEXT("ProjectFiles") / TEXT("PrimaryProjectPath.txt"))))
|
||||
{
|
||||
const FProjectDescriptor* CurrentProject = IProjectManager::Get().GetCurrentProject();
|
||||
|
||||
if ((CurrentProject == nullptr || CurrentProject->Modules.Num() == 0) || !FUProjectDictionary::GetDefault().IsForeignProject(ProjectDir))
|
||||
{
|
||||
ProjectPath = FPaths::RootDir() / TEXT("UE5");
|
||||
}
|
||||
else
|
||||
{
|
||||
const FString BaseName = FApp::HasProjectName() ? FApp::GetProjectName() : FPaths::GetBaseFilename(ProjectDir);
|
||||
ProjectPath = ProjectDir / BaseName;
|
||||
}
|
||||
}
|
||||
|
||||
ProjectPath = ProjectPath + TEXT(".sln");
|
||||
|
||||
FPaths::NormalizeFilename(ProjectPath);
|
||||
|
||||
return ProjectPath;
|
||||
}
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::GetRunningVisualStudioDTE(TComPtr<EnvDTE::_DTE>& OutDTE)
|
||||
{
|
||||
IRunningObjectTable* RunningObjectTable;
|
||||
bool bResult = false;
|
||||
FString ProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
|
||||
FPaths::NormalizeDirectoryName(ProjectDir);
|
||||
FString SolutionPath = GetProjectPath(ProjectDir);
|
||||
|
||||
if (SUCCEEDED(GetRunningObjectTable(0, &RunningObjectTable)) && RunningObjectTable)
|
||||
{
|
||||
IEnumMoniker* MonikersTable;
|
||||
if (SUCCEEDED(RunningObjectTable->EnumRunning(&MonikersTable)))
|
||||
{
|
||||
MonikersTable->Reset();
|
||||
|
||||
// Look for all visual studio instances in the ROT
|
||||
IMoniker* CurrentMoniker;
|
||||
while (MonikersTable->Next(1, &CurrentMoniker, NULL) == S_OK)
|
||||
{
|
||||
IBindCtx* BindContext;
|
||||
LPOLESTR OutName;
|
||||
if (SUCCEEDED(CreateBindCtx(0, &BindContext)) && SUCCEEDED(CurrentMoniker->GetDisplayName(BindContext, NULL, &OutName)))
|
||||
{
|
||||
TComPtr<IUnknown> ComObject;
|
||||
if (SUCCEEDED(RunningObjectTable->GetObject(CurrentMoniker, &ComObject)))
|
||||
{
|
||||
TComPtr<EnvDTE::_DTE> TempDTE;
|
||||
if (SUCCEEDED(TempDTE.FromQueryInterface(__uuidof(EnvDTE::_DTE), ComObject)))
|
||||
{
|
||||
TComPtr<EnvDTE::_Solution> Solution;
|
||||
BSTR OutPath = nullptr;
|
||||
if (SUCCEEDED(TempDTE->get_Solution(&Solution)) &&
|
||||
SUCCEEDED(Solution->get_FullName(&OutPath)))
|
||||
{
|
||||
FString Filename(OutPath);
|
||||
FPaths::NormalizeFilename(Filename);
|
||||
if (Filename == SolutionPath || Filename == ProjectDir)
|
||||
{
|
||||
OutDTE = TempDTE;
|
||||
bResult = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get solution from DTE"));
|
||||
}
|
||||
|
||||
SysFreeString(OutPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get display name for moniker"));
|
||||
}
|
||||
BindContext->Release();
|
||||
CurrentMoniker->Release();
|
||||
if (bResult) break;
|
||||
}
|
||||
MonikersTable->Release();
|
||||
RunningObjectTable->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not enumerate Running Object Table"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get Running Object Table"));
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::CanAddVisualStudioBreakpoint(const UEdGraphNode* Node, UClass **OutOwnerClass, UFunction **OutFunction)
|
||||
{
|
||||
const UK2Node_CallFunction* K2Node = Cast<const UK2Node_CallFunction>(Node);
|
||||
if (!K2Node)
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Node is not a UK2Node_CallFunction"));
|
||||
return false;
|
||||
}
|
||||
|
||||
UFunction* Function = K2Node->GetTargetFunction();
|
||||
if (!Function || !Function->IsNative())
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Function is not native"));
|
||||
return false;
|
||||
}
|
||||
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Trying to get function definition for %s"), *Function->GetName());
|
||||
|
||||
UClass* OwnerClass = Function->GetOwnerClass();
|
||||
if (!OwnerClass->HasAllClassFlags(CLASS_Native))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Owning class is not native"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutOwnerClass) *OutOwnerClass = OwnerClass;
|
||||
if (OutFunction) *OutFunction = Function;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
|
||||
#define PRINT_PLATFORM_ERROR_MSG(_TXT) \
|
||||
do { \
|
||||
TCHAR _ErrorBuffer[MAX_SPRINTF] = { 0 }; \
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("" #_TXT ": [%s]"), \
|
||||
FPlatformMisc::GetSystemErrorMessage(_ErrorBuffer, MAX_SPRINTF, 0)); \
|
||||
} while (0)
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::PreloadModule(HANDLE ProcessHandle, HMODULE ModuleHandle, const FString& RemoteStorage)
|
||||
{
|
||||
int32 ErrorCode = 0;
|
||||
MODULEINFO ModuleInfo = { 0 };
|
||||
WCHAR ModuleName[FProgramCounterSymbolInfo::MAX_NAME_LENGTH] = { 0 };
|
||||
WCHAR ImageName[FProgramCounterSymbolInfo::MAX_NAME_LENGTH] = { 0 };
|
||||
#if PLATFORM_64BITS
|
||||
static_assert(sizeof(MODULEINFO) == 24, "Broken alignment for 64bit Windows include.");
|
||||
#else
|
||||
static_assert(sizeof(MODULEINFO) == 12, "Broken alignment for 32bit Windows include.");
|
||||
#endif
|
||||
|
||||
if (!GetModuleInformation(ProcessHandle, ModuleHandle, &ModuleInfo, sizeof(ModuleInfo)))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not read GetModuleInformation");
|
||||
return false;
|
||||
}
|
||||
|
||||
IMAGEHLP_MODULE64 ImageHelpModule = { 0 };
|
||||
ImageHelpModule.SizeOfStruct = sizeof(ImageHelpModule);
|
||||
if (!SymGetModuleInfo64(ProcessHandle, (DWORD64)ModuleInfo.EntryPoint, &ImageHelpModule))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not SymGetModuleInfo64 from module");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ImageHelpModule.SymType != SymDeferred && ImageHelpModule.SymType != SymNone)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!GetModuleFileNameExW(ProcessHandle, ModuleHandle, ImageName, 1024))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not GetModuleFileNameExW");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetModuleBaseNameW(ProcessHandle, ModuleHandle, ModuleName, 1024))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not GetModuleBaseNameW");
|
||||
return false;
|
||||
}
|
||||
|
||||
WCHAR SearchPath[MAX_PATH] = { 0 };
|
||||
WCHAR* FileName = NULL;
|
||||
const auto Result = GetFullPathNameW(ImageName, MAX_PATH, SearchPath, &FileName);
|
||||
|
||||
FString SearchPathList;
|
||||
if (Result != 0 && Result < MAX_PATH)
|
||||
{
|
||||
*FileName = 0;
|
||||
SearchPathList = SearchPath;
|
||||
}
|
||||
|
||||
if (!RemoteStorage.IsEmpty())
|
||||
{
|
||||
if (!SearchPathList.IsEmpty())
|
||||
{
|
||||
SearchPathList.AppendChar(TEXT(';'));
|
||||
}
|
||||
SearchPathList.Append(RemoteStorage);
|
||||
}
|
||||
|
||||
if (!SymSetSearchPathW(ProcessHandle, *SearchPathList))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not SymSetSearchPathW");
|
||||
return false;
|
||||
}
|
||||
|
||||
const DWORD64 BaseAddress = SymLoadModuleExW(
|
||||
ProcessHandle,
|
||||
ModuleHandle,
|
||||
ImageName,
|
||||
ModuleName,
|
||||
(DWORD64)ModuleInfo.lpBaseOfDll,
|
||||
ModuleInfo.SizeOfImage,
|
||||
NULL,
|
||||
0);
|
||||
if (!BaseAddress)
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not load the module");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::GetFunctionDefinitionLocation(const FString& FunctionSymbolName, const FString& FunctionModuleName, FString& SourceFilePath, uint32& SourceLineNumber)
|
||||
{
|
||||
const HANDLE ProcessHandle = GetCurrentProcess();
|
||||
HMODULE ModuleHandle = GetModuleHandle(*FunctionModuleName);
|
||||
if (!ModuleHandle || !PreloadModule(ProcessHandle, ModuleHandle, FPlatformStackWalk::GetDownstreamStorage()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ANSICHAR SymbolInfoBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_SYM_NAME];
|
||||
PIMAGEHLP_SYMBOL64 SymbolInfoPtr = reinterpret_cast<IMAGEHLP_SYMBOL64*>(SymbolInfoBuffer);
|
||||
SymbolInfoPtr->SizeOfStruct = sizeof(SymbolInfoBuffer);
|
||||
SymbolInfoPtr->MaxNameLength = MAX_SYM_NAME;
|
||||
|
||||
FString FullyQualifiedSymbolName = FunctionSymbolName;
|
||||
if (!FunctionModuleName.IsEmpty())
|
||||
{
|
||||
FullyQualifiedSymbolName = FString::Printf(TEXT("%s!%s"), *FunctionModuleName, *FunctionSymbolName);
|
||||
}
|
||||
|
||||
if (!SymGetSymFromName64(ProcessHandle, TCHAR_TO_ANSI(*FullyQualifiedSymbolName), SymbolInfoPtr))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not load module symbol information");
|
||||
return false;
|
||||
}
|
||||
|
||||
IMAGEHLP_LINE64 FileAndLineInfo;
|
||||
FileAndLineInfo.SizeOfStruct = sizeof(FileAndLineInfo);
|
||||
|
||||
uint32 SourceColumnNumber = 0;
|
||||
if (!SymGetLineFromAddr64(ProcessHandle, SymbolInfoPtr->Address, (::DWORD*)&SourceColumnNumber, &FileAndLineInfo))
|
||||
{
|
||||
PRINT_PLATFORM_ERROR_MSG("Could not query module file and line number");
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceLineNumber = FileAndLineInfo.LineNumber;
|
||||
SourceFilePath = FString((const ANSICHAR*)(FileAndLineInfo.FileName));
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::GetFunctionDefinitionLocation(const UEdGraphNode* Node, FString& SourceFilePath, FString& SymbolName, uint32& SourceLineNumber)
|
||||
{
|
||||
UClass* OwningClass;
|
||||
UFunction* Function;
|
||||
if (!CanAddVisualStudioBreakpoint(Node, &OwningClass, &Function))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FString ModuleName;
|
||||
|
||||
// Find module name for class
|
||||
if (!FSourceCodeNavigation::FindClassModuleName(OwningClass, ModuleName))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to find module name for class"));
|
||||
return false;
|
||||
}
|
||||
|
||||
SymbolName = FString::Printf(
|
||||
TEXT("%s%s::%s"),
|
||||
OwningClass->GetPrefixCPP(),
|
||||
*OwningClass->GetName(),
|
||||
*Function->GetName());
|
||||
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Symbol %s is defined in module %s"), *SymbolName, *ModuleName);
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
uint32 SourceColumnNumber = 0;
|
||||
return FPlatformStackWalk::GetFunctionDefinitionLocation(
|
||||
SymbolName,
|
||||
ModuleName,
|
||||
SourceFilePath,
|
||||
SourceLineNumber,
|
||||
SourceColumnNumber);
|
||||
#else
|
||||
return GetFunctionDefinitionLocation(SymbolName, ModuleName, SourceFilePath, SourceLineNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::GetProcessById(const TComPtr<EnvDTE::Processes>& Processes, DWORD CurrentProcessId, TComPtr<EnvDTE::Process>& OutProcess)
|
||||
{
|
||||
long Count = 0;
|
||||
if (FAILED(Processes->get_Count(&Count)))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get the process count"));
|
||||
return false;
|
||||
}
|
||||
|
||||
TComPtr<EnvDTE::Process> Process;
|
||||
for (long i = 1; i <= Count; i++)
|
||||
{
|
||||
VARIANT Index;
|
||||
Index.vt = VT_I4;
|
||||
Index.lVal = i;
|
||||
if (SUCCEEDED(Processes->Item(Index, &Process)))
|
||||
{
|
||||
long PID = 0;
|
||||
if (SUCCEEDED(Process->get_ProcessID(&PID)) && CurrentProcessId == PID)
|
||||
{
|
||||
OutProcess = Process;
|
||||
return true;
|
||||
}
|
||||
|
||||
Process.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UVisualStudioToolsBlueprintBreakpointExtension::AttachDebuggerIfNecessary(const TComPtr<EnvDTE::Debugger>& Debugger)
|
||||
{
|
||||
TComPtr<EnvDTE::Processes> Processes;
|
||||
if (FAILED(Debugger->get_DebuggedProcesses(&Processes)))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get debugging proccess"));
|
||||
return;
|
||||
}
|
||||
|
||||
TComPtr<EnvDTE::Process> Process;
|
||||
DWORD CurrentProcessId = GetCurrentProcessId();
|
||||
if (!GetProcessById(Processes, CurrentProcessId, Process))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to check if UE is already in debug mode"));
|
||||
return;
|
||||
}
|
||||
|
||||
// currently debugging this process
|
||||
if (Process.Get() != nullptr)
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Already debugging UE."));
|
||||
return;
|
||||
}
|
||||
|
||||
Processes.Reset();
|
||||
if (FAILED(Debugger->get_LocalProcesses(&Processes)))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to attach to process"));
|
||||
return;
|
||||
}
|
||||
|
||||
Process.Reset();
|
||||
if (!GetProcessById(Processes, CurrentProcessId, Process))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get all process"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Process.Get() == nullptr)
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("No UE proccess running."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(Process->Attach()))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to attach to process"));
|
||||
}
|
||||
}
|
||||
|
||||
bool UVisualStudioToolsBlueprintBreakpointExtension::SetVisualStudioBreakpoint(const UEdGraphNode* Node, const FString& SourceFilePath, const FString& SymbolName, uint32 SourceLineNumber)
|
||||
{
|
||||
TComPtr<EnvDTE::_DTE> DTE;
|
||||
bool bBreakpointAdded = false;
|
||||
if (!GetRunningVisualStudioDTE(DTE))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to access Visual Studio via DTE"));
|
||||
return bBreakpointAdded;
|
||||
}
|
||||
|
||||
TComPtr<EnvDTE::Debugger> Debugger;
|
||||
TComPtr<EnvDTE::Breakpoints> Breakpoints;
|
||||
if (SUCCEEDED(DTE->get_Debugger(&Debugger)) && SUCCEEDED(Debugger->get_Breakpoints(&Breakpoints)))
|
||||
{
|
||||
FSmartBSTR BSTREmptyStr;
|
||||
FSmartBSTR BSTRFilePath(SourceFilePath);
|
||||
HRESULT Result = Breakpoints->Add(
|
||||
*BSTREmptyStr,
|
||||
*BSTRFilePath,
|
||||
SourceLineNumber,
|
||||
1,
|
||||
*BSTREmptyStr,
|
||||
EnvDTE::dbgBreakpointConditionType::dbgBreakpointConditionTypeWhenTrue,
|
||||
*BSTREmptyStr,
|
||||
*BSTREmptyStr,
|
||||
0,
|
||||
*BSTREmptyStr,
|
||||
0,
|
||||
EnvDTE::dbgHitCountType::dbgHitCountTypeNone,
|
||||
&Breakpoints);
|
||||
|
||||
if (FAILED(Result))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to add breakpoint"));
|
||||
}
|
||||
else
|
||||
{
|
||||
bBreakpointAdded = true;
|
||||
AttachDebuggerIfNecessary(Debugger);
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Breakpoint set for %s"), *SymbolName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get debugger or breakpoints"));
|
||||
}
|
||||
|
||||
return bBreakpointAdded;
|
||||
}
|
||||
|
||||
void UVisualStudioToolsBlueprintBreakpointExtension::AddVisualStudioBreakpoint(const UEdGraphNode* Node)
|
||||
{
|
||||
FWindowsPlatformMisc::CoInitialize();
|
||||
FPlatformStackWalk::InitStackWalking();
|
||||
FString SourceFilePath;
|
||||
FString SymbolName;
|
||||
uint32 SourceLineNumber;
|
||||
bool bBreakpointAdded = false;
|
||||
|
||||
if (GetFunctionDefinitionLocation(Node, SourceFilePath, SymbolName, SourceLineNumber))
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Method defined in %s at line %d"), *SourceFilePath, SourceLineNumber);
|
||||
bBreakpointAdded = SetVisualStudioBreakpoint(Node, SourceFilePath, SymbolName, SourceLineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get function definition location"));
|
||||
}
|
||||
|
||||
ShowOperationResultNotification(bBreakpointAdded, SymbolName);
|
||||
FWindowsPlatformMisc::CoUninitialize();
|
||||
}
|
||||
|
||||
void UVisualStudioToolsBlueprintBreakpointExtension::ShowOperationResultNotification(bool bBreakpointAdded, const FString &SymbolName)
|
||||
{
|
||||
FNotificationInfo Info(bBreakpointAdded ? FText::FromString(FString::Printf(TEXT("Breakpoint added at %s"), *SymbolName)) : FText::FromString("Could not add Breakpoint in Visual Studio"));
|
||||
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1
|
||||
Info.Image = FAppStyle::GetBrush(TEXT("LevelEditor.RecompileGameCode"));
|
||||
#else
|
||||
Info.Image = FEditorStyle::GetBrush(TEXT("LevelEditor.RecompileGameCode"));
|
||||
#endif
|
||||
Info.FadeInDuration = 0.1f;
|
||||
Info.FadeOutDuration = 0.5f;
|
||||
Info.ExpireDuration = 3.0f;
|
||||
Info.bUseThrobber = false;
|
||||
Info.bUseSuccessFailIcons = true;
|
||||
Info.bUseLargeFont = true;
|
||||
Info.bFireAndForget = false;
|
||||
Info.bAllowThrottleWhenFrameRateIsLow = false;
|
||||
Info.WidthOverride = 400.0f;
|
||||
TSharedPtr<SNotificationItem> NotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
|
||||
NotificationItem->SetCompletionState(bBreakpointAdded ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail);
|
||||
NotificationItem->ExpireAndFadeout();
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <EditorSubsystem.h>
|
||||
#include <EdGraph/EdGraph.h>
|
||||
#include <EdGraph/EdGraphNode.h>
|
||||
#include <EdGraph/EdGraphPin.h>
|
||||
#include <GraphEditorModule.h>
|
||||
#include <VisualStudioDTE.h>
|
||||
#include <Microsoft/COMPointer.h>
|
||||
#include <Runtime/Launch/Resources/Version.h>
|
||||
#include "VisualStudioToolsBlueprintBreakpointExtension.generated.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, All);
|
||||
|
||||
UCLASS()
|
||||
class UVisualStudioToolsBlueprintBreakpointExtension : public UEditorSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnNodeMenuExtensionHookRequestDelegate, const UEdGraphNode*, const UEdGraph*, TSet<FName>&);
|
||||
|
||||
void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
void Deinitialize() override;
|
||||
|
||||
FOnNodeMenuExtensionHookRequestDelegate& OnNodeMenuExtensionHookRequest() { return OnNodeMenuExtensionHookRequestDelegate; }
|
||||
|
||||
private:
|
||||
FOnNodeMenuExtensionHookRequestDelegate OnNodeMenuExtensionHookRequestDelegate;
|
||||
|
||||
TSharedRef<FExtender> HandleOnExtendGraphEditorContextMenu(
|
||||
const TSharedRef<FUICommandList> CommandList,
|
||||
const UEdGraph* Graph,
|
||||
const UEdGraphNode* Node,
|
||||
const UEdGraphPin* Pin,
|
||||
bool bIsConst);
|
||||
|
||||
void AddVisualStudioBlueprintBreakpointMenuOption(FMenuBuilder& MenuBuilder, const UEdGraphNode* node);
|
||||
|
||||
void AddVisualStudioBreakpoint(const UEdGraphNode* Node);
|
||||
|
||||
bool GetFunctionDefinitionLocation(const UEdGraphNode* Node, FString& SourceFilePath, FString& SymbolName, uint32& SourceLineNumber);
|
||||
|
||||
bool SetVisualStudioBreakpoint(const UEdGraphNode* Node, const FString& SourceFilePath, const FString& SymbolName, uint32 SourceLineNumber);
|
||||
|
||||
bool CanAddVisualStudioBreakpoint(const UEdGraphNode* Node, UClass** OutOwnerClass, UFunction** OutFunction);
|
||||
|
||||
void ShowOperationResultNotification(bool bBreakpointAdded, const FString& SymbolName);
|
||||
|
||||
FString GetProjectPath(const FString& ProjectDir);
|
||||
|
||||
bool GetRunningVisualStudioDTE(TComPtr<EnvDTE::_DTE>& OutDTE);
|
||||
|
||||
void AttachDebuggerIfNecessary(const TComPtr<EnvDTE::Debugger>& Debugger);
|
||||
|
||||
bool GetProcessById(const TComPtr<EnvDTE::Processes>& Processes, DWORD CurrentProcessId, TComPtr<EnvDTE::Process>& OutProcess);
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
bool PreloadModule(HANDLE ProcessHandle, HMODULE ModuleHandle, const FString& RemoteStorage);
|
||||
|
||||
bool GetFunctionDefinitionLocation(const FString& FunctionSymbolName, const FString& FunctionModuleName, FString& SourceFilePath, uint32& SourceLineNumber);
|
||||
#endif
|
||||
};
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
#include "VisualStudioToolsCommandlet.h"
|
||||
|
||||
#include "Algo/Transform.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Blueprint/BlueprintSupport.h"
|
||||
#include "BlueprintAssetHelpers.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/ScopeExit.h"
|
||||
#include "Policies/CondensedJsonPrintPolicy.h"
|
||||
#include "SourceCodeNavigation.h"
|
||||
#include "UObject/CoreRedirects.h"
|
||||
#include "UObject/UObjectIterator.h"
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
namespace VisualStudioTools
|
||||
{
|
||||
static const FName CategoryFName = TEXT("Category");
|
||||
static const FName ModuleNameFName = TEXT("ModuleName");
|
||||
|
||||
static TArray<FProperty*> GetChangedPropertiesList(
|
||||
UStruct* InStruct, const uint8* DataPtr, const uint8* DefaultDataPtr)
|
||||
{
|
||||
TArray<FProperty*> Result;
|
||||
|
||||
const UClass* OwnerClass = Cast<UClass>(InStruct);
|
||||
|
||||
// Walk only in the properties defined in the current class, the super classes are processed individually
|
||||
for (TFieldIterator<FProperty> It(OwnerClass, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
||||
{
|
||||
FProperty* Property = *It;
|
||||
for (int32 Idx = 0; Idx < Property->ArrayDim; Idx++)
|
||||
{
|
||||
const uint8* PropertyValue = Property->ContainerPtrToValuePtr<uint8>(DataPtr, Idx);
|
||||
const uint8* DefaultPropertyValue = Property->ContainerPtrToValuePtrForDefaults<uint8>(InStruct, DefaultDataPtr, Idx);
|
||||
|
||||
if (!Property->Identical(PropertyValue, DefaultPropertyValue))
|
||||
{
|
||||
Result.Add(Property);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static bool FindBlueprintNativeParents(
|
||||
const UClass* BlueprintGeneratedClass, TFunctionRef<void(UClass*)> Callback)
|
||||
{
|
||||
bool bAnyNativeParent = false;
|
||||
for (UClass* Super = BlueprintGeneratedClass->GetSuperClass(); Super; Super = Super->GetSuperClass())
|
||||
{
|
||||
// Ignore the root `UObject` class and non-native parents.
|
||||
if (Super->HasAnyClassFlags(CLASS_Native) && Super->GetFName() != NAME_Object)
|
||||
{
|
||||
bAnyNativeParent = true;
|
||||
Callback(Super);
|
||||
}
|
||||
}
|
||||
|
||||
return bAnyNativeParent;
|
||||
}
|
||||
|
||||
struct FPropertyEntry
|
||||
{
|
||||
FProperty* Property;
|
||||
TArray<int32> Blueprints;
|
||||
};
|
||||
|
||||
struct FFunctionEntry
|
||||
{
|
||||
UFunction* Function;
|
||||
TArray<int32> Blueprints;
|
||||
};
|
||||
|
||||
struct FClassEntry
|
||||
{
|
||||
const UClass* Class;
|
||||
TArray<int32> Blueprints;
|
||||
TMap<FString, FPropertyEntry> Properties;
|
||||
TMap<FString, FFunctionEntry> Functions;
|
||||
};
|
||||
|
||||
using ClassMap = TMap<FString, FClassEntry>;
|
||||
|
||||
struct FAssetIndex
|
||||
{
|
||||
TSet<FString> AssetPathCache;
|
||||
ClassMap Classes;
|
||||
TArray<const UClass*> Blueprints;
|
||||
|
||||
void ProcessBlueprint(const UBlueprintGeneratedClass* BlueprintGeneratedClass)
|
||||
{
|
||||
if (BlueprintGeneratedClass == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int32 BlueprintIndex = Blueprints.Num();
|
||||
|
||||
bool bHasAnyParent = FindBlueprintNativeParents(BlueprintGeneratedClass, [&](UClass* Parent)
|
||||
{
|
||||
FString ParentName = Parent->GetFName().ToString();
|
||||
if (!Classes.Contains(ParentName))
|
||||
{
|
||||
Classes.Add(ParentName).Class = Parent;
|
||||
}
|
||||
|
||||
FClassEntry& ClassEntry = Classes[ParentName];
|
||||
|
||||
ClassEntry.Blueprints.Add(BlueprintIndex);
|
||||
|
||||
// Retrieve the properties from the parent class that changed in the Blueprint class, by comparing their CDOs.
|
||||
UObject* GeneratedClassDefault = BlueprintGeneratedClass->ClassDefaultObject;
|
||||
UObject* SuperClassDefault = Parent->GetDefaultObject(false);
|
||||
TArray<FProperty*> ChangedProperties = GetChangedPropertiesList(Parent, (uint8*)GeneratedClassDefault, (uint8*)SuperClassDefault);
|
||||
|
||||
for (FProperty* Property : ChangedProperties)
|
||||
{
|
||||
FString PropertyName = Property->GetFName().ToString();
|
||||
if (!ClassEntry.Properties.Contains(PropertyName))
|
||||
{
|
||||
ClassEntry.Properties.Add(PropertyName).Property = Property;
|
||||
}
|
||||
|
||||
FPropertyEntry& PropEntry = ClassEntry.Properties[PropertyName];
|
||||
PropEntry.Blueprints.Add(BlueprintIndex);
|
||||
}
|
||||
|
||||
// Iterate over the functions originally from the parent class
|
||||
// and check if they are implemented in the BP class as well.
|
||||
for (TFieldIterator<UFunction> It(Parent, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
||||
{
|
||||
UFunction* Fn = BlueprintGeneratedClass->FindFunctionByName((*It)->GetFName(), EIncludeSuperFlag::ExcludeSuper);
|
||||
// If the function not present in the BP class directly, it means it was implemented. Otherwise, ignore.
|
||||
if (!Fn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FString FnName = Fn->GetFName().ToString();
|
||||
if (!ClassEntry.Functions.Contains(FnName))
|
||||
{
|
||||
ClassEntry.Functions.Add(FnName).Function = Fn;
|
||||
}
|
||||
|
||||
FFunctionEntry& FuncEntry = ClassEntry.Functions[FnName];
|
||||
FuncEntry.Blueprints.Add(BlueprintIndex);
|
||||
}
|
||||
});
|
||||
|
||||
if (bHasAnyParent)
|
||||
{
|
||||
check(Blueprints.Add(BlueprintGeneratedClass) == BlueprintIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
using JsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
|
||||
|
||||
static bool ShouldSerializePropertyValue(FProperty* Property)
|
||||
{
|
||||
if (Property->ArrayDim > 1) // Skip properties that are not scalars
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property))
|
||||
{
|
||||
UEnum* EnumDef = NumericProperty->GetIntPropertyEnum();
|
||||
if (EnumDef != NULL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NumericProperty->IsFloatingPoint())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NumericProperty->IsInteger())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FStrProperty* StringProperty = CastField<FStrProperty>(Property))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SerializeBlueprints(TSharedRef<JsonWriter>& Json, TArray<const UClass*> Items)
|
||||
{
|
||||
Json->WriteArrayStart();
|
||||
for (const UClass* Blueprint : Items)
|
||||
{
|
||||
Json->WriteObjectStart();
|
||||
|
||||
Json->WriteValue(TEXT("name"), Blueprint->GetName());
|
||||
Json->WriteValue(TEXT("path"), Blueprint->GetPathName());
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
Json->WriteArrayEnd();
|
||||
}
|
||||
|
||||
static void SerializeProperties(TSharedRef<JsonWriter>& Json, FClassEntry& Entry, TArray<const UClass*>& Blueprints)
|
||||
{
|
||||
Json->WriteArrayStart();
|
||||
for (auto& Item : Entry.Properties)
|
||||
{
|
||||
auto& PropName = Item.Key;
|
||||
auto& PropEntry = Item.Value;
|
||||
FProperty* Property = PropEntry.Property;
|
||||
|
||||
Json->WriteObjectStart();
|
||||
|
||||
Json->WriteValue(TEXT("name"), PropName);
|
||||
|
||||
Json->WriteIdentifierPrefix(TEXT("metadata"));
|
||||
{
|
||||
Json->WriteObjectStart();
|
||||
if (Property->HasMetaData(CategoryFName))
|
||||
{
|
||||
Json->WriteValue(TEXT("categories"), Property->GetMetaData(CategoryFName));
|
||||
}
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
|
||||
Json->WriteIdentifierPrefix(TEXT("values"));
|
||||
{
|
||||
Json->WriteArrayStart();
|
||||
for (auto& BlueprintEntry : PropEntry.Blueprints)
|
||||
{
|
||||
Json->WriteObjectStart();
|
||||
|
||||
Json->WriteValue(TEXT("blueprint"), BlueprintEntry);
|
||||
|
||||
UObject* GeneratedClassDefault = Blueprints[BlueprintEntry]->ClassDefaultObject;
|
||||
const uint8* PropData = PropEntry.Property->ContainerPtrToValuePtr<uint8>(GeneratedClassDefault);
|
||||
|
||||
if (ShouldSerializePropertyValue(PropEntry.Property))
|
||||
{
|
||||
TSharedPtr<FJsonValue> JsonValue = FJsonObjectConverter::UPropertyToJsonValue(Property, PropData);
|
||||
FJsonSerializer::Serialize(JsonValue.ToSharedRef(), TEXT("value"), Json);
|
||||
}
|
||||
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
Json->WriteArrayEnd();
|
||||
}
|
||||
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
Json->WriteArrayEnd();
|
||||
}
|
||||
|
||||
static void SerializeFunctions(TSharedRef<JsonWriter>& Json, FClassEntry& Entry)
|
||||
{
|
||||
Json->WriteArrayStart();
|
||||
for (auto& Item : Entry.Functions)
|
||||
{
|
||||
auto& Name = Item.Key;
|
||||
auto& FnEntry = Item.Value;
|
||||
Json->WriteObjectStart();
|
||||
Json->WriteValue(TEXT("name"), Name);
|
||||
Json->WriteValue(TEXT("blueprints"), FnEntry.Blueprints);
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
Json->WriteArrayEnd();
|
||||
}
|
||||
|
||||
static void SerializeClasses(TSharedRef<JsonWriter>& Json, ClassMap& Items, TArray<const UClass*> Blueprints)
|
||||
{
|
||||
Json->WriteArrayStart();
|
||||
for (auto& Item : Items)
|
||||
{
|
||||
auto& ClassName = Item.Key;
|
||||
auto& Entry = Item.Value;
|
||||
Json->WriteObjectStart();
|
||||
Json->WriteValue(TEXT("name"), FString::Printf(TEXT("%s%s"), Entry.Class->GetPrefixCPP(), *Entry.Class->GetName()));
|
||||
|
||||
Json->WriteValue(TEXT("blueprints"), Entry.Blueprints);
|
||||
|
||||
Json->WriteIdentifierPrefix(TEXT("properties"));
|
||||
SerializeProperties(Json, Entry, Blueprints);
|
||||
|
||||
Json->WriteIdentifierPrefix(TEXT("functions"));
|
||||
SerializeFunctions(Json, Entry);
|
||||
|
||||
Json->WriteObjectEnd();
|
||||
}
|
||||
Json->WriteArrayEnd();
|
||||
}
|
||||
|
||||
static void SerializeToIndex(FAssetIndex Index, FArchive& IndexFile)
|
||||
{
|
||||
TSharedRef<JsonWriter> Json = JsonWriter::Create(&IndexFile);
|
||||
|
||||
Json->WriteObjectStart();
|
||||
|
||||
Json->WriteIdentifierPrefix(TEXT("blueprints"));
|
||||
SerializeBlueprints(Json, Index.Blueprints);
|
||||
|
||||
Json->WriteIdentifierPrefix(TEXT("classes"));
|
||||
SerializeClasses(Json, Index.Classes, Index.Blueprints);
|
||||
|
||||
Json->WriteObjectEnd();
|
||||
Json->Close();
|
||||
}
|
||||
|
||||
static TArray<FString> GetModulesByPath(const FString& InDir)
|
||||
{
|
||||
TArray<FString> OutResult;
|
||||
Algo::TransformIf(
|
||||
FSourceCodeNavigation::GetSourceFileDatabase().GetModuleNames(),
|
||||
OutResult,
|
||||
[&](const FString& Module) {
|
||||
return FPaths::IsUnderDirectory(Module, InDir);
|
||||
},
|
||||
[](const FString& Module) {
|
||||
#if 0
|
||||
// Old version assumes that each module is in a folder with the same name as the module
|
||||
return FPaths::GetBaseFilename(FPaths::GetPath(*Module));
|
||||
#else
|
||||
// New version assumes that each module is in a file with the name Module.Build.cs
|
||||
FString TempString = FPaths::GetBaseFilename(*Module);
|
||||
TempString.RemoveFromEnd(TEXT(".Build"));
|
||||
return TempString;
|
||||
#endif
|
||||
});
|
||||
|
||||
return OutResult;
|
||||
}
|
||||
|
||||
static void GetNativeClassesByPath(const FString& InDir, TArray<TWeakObjectPtr<UClass>>& OutClasses)
|
||||
{
|
||||
TArray<FString> Modules = GetModulesByPath(InDir);
|
||||
|
||||
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
|
||||
{
|
||||
UClass* TestClass = *ClassIt;
|
||||
if (!TestClass->HasAnyClassFlags(CLASS_Native))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FAssetData ClassAssetData(TestClass);
|
||||
FString ModuleName = ClassAssetData.GetTagValueRef<FString>(ModuleNameFName);
|
||||
|
||||
if (!ModuleName.IsEmpty() && Modules.Contains(ModuleName))
|
||||
{
|
||||
OutClasses.Add(TestClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RunAssetScan(
|
||||
FAssetIndex& Index,
|
||||
const TArray<TWeakObjectPtr<UClass>>& FilterBaseClasses)
|
||||
{
|
||||
FARFilter Filter;
|
||||
Filter.bRecursivePaths = true;
|
||||
Filter.bRecursiveClasses = true;
|
||||
AssetHelpers::SetBlueprintClassFilter(Filter);
|
||||
|
||||
// Add all base classes to the tag filter for native parent
|
||||
Algo::Transform(FilterBaseClasses, Filter.TagsAndValues, [](const TWeakObjectPtr<UClass>& Class) {
|
||||
return MakeTuple(
|
||||
FBlueprintTags::NativeParentClassPath,
|
||||
FObjectPropertyBase::GetExportPath(Class.Get(), nullptr /*Parent*/, nullptr /*ExportRootScope*/, 0 /*PortFlags*/));
|
||||
});
|
||||
|
||||
// Take account of any core redirects for the blueprint classes we want to scan.
|
||||
for (const auto& BaseClass : FilterBaseClasses)
|
||||
{
|
||||
if (BaseClass.IsValid())
|
||||
{
|
||||
TArray<FCoreRedirectObjectName> PreviousNames;
|
||||
if (FCoreRedirects::FindPreviousNames(ECoreRedirectFlags::Type_Class, BaseClass->GetPathName(), PreviousNames))
|
||||
{
|
||||
for (const auto& PreviousName : PreviousNames)
|
||||
{
|
||||
// FString PreviousString = FObjectPropertyBase::GetExportPath(BaseClass->GetClass()->GetClassPathName(), PreviousName.ToString()); // Alternative way to add /Script/CoreUObject.Class'' wrapper - but not sure it makes sense to use the new class when referencing a previous name
|
||||
FString PreviousString = "/Script/CoreUObject.Class'" + PreviousName.ToString() + "'";
|
||||
Filter.TagsAndValues.Add(FBlueprintTags::NativeParentClassPath, PreviousString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
||||
|
||||
TArray<FAssetData> TargetAssets;
|
||||
AssetRegistry.GetAssets(Filter, TargetAssets);
|
||||
|
||||
AssetHelpers::ForEachAsset(TargetAssets,
|
||||
[&](UBlueprintGeneratedClass* BlueprintGeneratedClass, const FAssetData& /*AssetData*/)
|
||||
{
|
||||
Index.ProcessBlueprint(BlueprintGeneratedClass);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace VS
|
||||
|
||||
static constexpr auto FilterSwitch = TEXT("filter");
|
||||
static constexpr auto FullSwitch = TEXT("full");
|
||||
|
||||
UVisualStudioToolsCommandlet::UVisualStudioToolsCommandlet()
|
||||
: Super()
|
||||
{
|
||||
HelpDescription = TEXT("Commandlet for generating data used by Blueprint support in Visual Studio.");
|
||||
|
||||
HelpParamNames.Add(FilterSwitch);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] Scan only blueprints derived from native classes under the provided path. Defaults to `FPaths::ProjectDir`. Incompatible with `-full`."));
|
||||
|
||||
HelpParamNames.Add(FullSwitch);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] Scan blueprints derived from native classes from ALL modules, include the Engine. This can be _very slow_ for large projects. Incompatible with `-filter`."));
|
||||
|
||||
HelpUsage = TEXT("<Editor-Cmd.exe> <path_to_uproject> -run=VisualStudioTools -output=<path_to_output_file> [-filter=<subdir_native_classes>|-full] [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash]");
|
||||
}
|
||||
|
||||
int32 UVisualStudioToolsCommandlet::Run(
|
||||
TArray<FString>& Tokens,
|
||||
TArray<FString>& Switches,
|
||||
TMap<FString, FString>& ParamVals,
|
||||
FArchive& OutArchive)
|
||||
{
|
||||
using namespace VisualStudioTools;
|
||||
|
||||
FString* Filter = ParamVals.Find(FilterSwitch);
|
||||
const bool bFullScan = Switches.Contains(FullSwitch);
|
||||
|
||||
if (Filter != nullptr && bFullScan)
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Incompatible scan options."));
|
||||
PrintHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
TArray<TWeakObjectPtr<UClass>> FilterBaseClasses;
|
||||
if (!bFullScan)
|
||||
{
|
||||
if (Filter)
|
||||
{
|
||||
FPaths::NormalizeDirectoryName(*Filter);
|
||||
GetNativeClassesByPath(*Filter, FilterBaseClasses);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetNativeClassesByPath(FPaths::ProjectDir(), FilterBaseClasses);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
|
||||
{
|
||||
UClass* TestClass = *ClassIt;
|
||||
if (!TestClass->HasAnyClassFlags(CLASS_Native))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FilterBaseClasses.Add(TestClass);
|
||||
}
|
||||
}
|
||||
|
||||
FAssetIndex Index;
|
||||
RunAssetScan(Index, FilterBaseClasses);
|
||||
SerializeToIndex(Index, OutArchive);
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Found %d blueprints."), Index.Blueprints.Num());
|
||||
|
||||
return 0;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VisualStudioToolsCommandletBase.h"
|
||||
|
||||
#include "VisualStudioToolsCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UVisualStudioToolsCommandlet
|
||||
: public UVisualStudioToolsCommandletBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UVisualStudioToolsCommandlet();
|
||||
int32 Run(
|
||||
TArray<FString>& Tokens,
|
||||
TArray<FString>& Switches,
|
||||
TMap<FString, FString>& ParamVals,
|
||||
FArchive& OutArchive) override;
|
||||
};
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "VisualStudioToolsCommandletBase.h"
|
||||
|
||||
#include "Windows/AllowWindowsPlatformTypes.h"
|
||||
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "VisualStudioTools.h"
|
||||
|
||||
#include "Windows/HideWindowsPlatformTypes.h"
|
||||
|
||||
static constexpr auto HelpSwitch = TEXT("help");
|
||||
static constexpr auto OutputSwitch = TEXT("output");
|
||||
|
||||
UVisualStudioToolsCommandletBase::UVisualStudioToolsCommandletBase()
|
||||
{
|
||||
IsClient = false;
|
||||
IsEditor = true;
|
||||
IsServer = false;
|
||||
LogToConsole = false;
|
||||
ShowErrorCount = false;
|
||||
|
||||
HelpParamNames.Add(OutputSwitch);
|
||||
HelpParamDescriptions.Add(TEXT("[Required] The file path to write the command output."));
|
||||
|
||||
HelpParamNames.Add(HelpSwitch);
|
||||
HelpParamDescriptions.Add(TEXT("[Optional] Print this help message and quit the commandlet immediately."));
|
||||
}
|
||||
|
||||
void UVisualStudioToolsCommandletBase::PrintHelp() const
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("%s"), *HelpDescription);
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Usage: %s"), *HelpUsage);
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Parameters:"));
|
||||
for (int32 i = 0; i < HelpParamNames.Num(); ++i)
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("\t-%s: %s"), *HelpParamNames[i], *HelpParamDescriptions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int32 UVisualStudioToolsCommandletBase::Main(const FString& Params)
|
||||
{
|
||||
TArray<FString> Tokens;
|
||||
TArray<FString> Switches;
|
||||
TMap<FString, FString> ParamVals;
|
||||
|
||||
ParseCommandLine(*Params, Tokens, Switches, ParamVals);
|
||||
|
||||
if (Switches.Contains(HelpSwitch))
|
||||
{
|
||||
PrintHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
UE_LOG(LogVisualStudioTools, Display, TEXT("Init VS Tools cmdlet."));
|
||||
|
||||
if (!FPaths::IsProjectFilePathSet())
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("You must invoke this commandlet with a project file."));
|
||||
return -1;
|
||||
}
|
||||
|
||||
FString FullPath = ParamVals.FindRef(OutputSwitch);
|
||||
|
||||
if (FullPath.IsEmpty() && !FParse::Value(*Params, TEXT("output "), FullPath))
|
||||
{
|
||||
// VS:1678426 - Initial version was using `-output "path-to-file"` (POSIX style).
|
||||
// However, that does not support paths with spaces, even when surrounded with
|
||||
// quotes because `FParse::Value` only handles that case when there's no space
|
||||
// between the parameter name and quoted value.
|
||||
// For back-compatibility reasons, parse that style by including the space in
|
||||
// the parameter token like it's usually done for the `=` sign.
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Missing file output parameter."));
|
||||
PrintHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
TUniquePtr<FArchive> OutArchive{ IFileManager::Get().CreateFileWriter(*FullPath) };
|
||||
if (!OutArchive)
|
||||
{
|
||||
UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to create index with path: %s."), *FullPath);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this->Run(Tokens, Switches, ParamVals, *OutArchive);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Commandlets/Commandlet.h"
|
||||
|
||||
#include "VisualStudioToolsCommandletBase.generated.h"
|
||||
|
||||
UCLASS(Abstract)
|
||||
class UVisualStudioToolsCommandletBase
|
||||
: public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
int32 Main(const FString& Params) override;
|
||||
|
||||
protected:
|
||||
UVisualStudioToolsCommandletBase();
|
||||
|
||||
void PrintHelp() const;
|
||||
|
||||
virtual int32 Run(
|
||||
TArray<FString>& Tokens,
|
||||
TArray<FString>& Switches,
|
||||
TMap<FString, FString>& ParamVals,
|
||||
FArchive& OutArchive) PURE_VIRTUAL(UVisualStudioToolsCommandletBase::Run, return 0;);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogVisualStudioTools, Log, All);
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2022 (c) Microsoft. All rights reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class VisualStudioTools : ModuleRules
|
||||
{
|
||||
public VisualStudioTools(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
bool bIsCustomDevBuild = System.Environment.GetEnvironmentVariable("VSTUE_IsCustomDevBuild") == "1";
|
||||
if (bIsCustomDevBuild)
|
||||
{
|
||||
// Get correct header suggestions in the IDE and validate
|
||||
// the plugin build without having to affect for the whole target,
|
||||
// which is expensive in source-builds of the engine.
|
||||
PCHUsage = ModuleRules.PCHUsageMode.NoPCHs;
|
||||
bUseUnity = false;
|
||||
|
||||
// When debugging the commandlet code, disable optimizations to get
|
||||
// proper local variable inspection and less inlined stack frames
|
||||
OptimizeCode = CodeOptimization.Never;
|
||||
|
||||
// Enable more restrict warnings during compilation in UE5.
|
||||
// Required by tasks in the compliance pipeline.
|
||||
if (Target.Version.MajorVersion >= 5)
|
||||
{
|
||||
UnsafeTypeCastWarningLevel = WarningLevel.Error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
}
|
||||
|
||||
// To support UE5.1+, the code is using the new FTopLevelAssetPath API
|
||||
// with a detection of support via version numbers.
|
||||
// If the check is producing a false positive/negative in your version of the engine
|
||||
// you can change the block below and force the check as enabled/disabled.
|
||||
if ((Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 1) || Target.Version.MajorVersion > 5)
|
||||
{
|
||||
PrivateDefinitions.Add("FILTER_ASSETS_BY_CLASS_PATH=1");
|
||||
}
|
||||
else
|
||||
{
|
||||
PrivateDefinitions.Add("FILTER_ASSETS_BY_CLASS_PATH=0");
|
||||
}
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"ApplicationCore",
|
||||
"AssetRegistry",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Json",
|
||||
"JsonUtilities",
|
||||
"Kismet",
|
||||
"UnrealEd",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"ToolMenus",
|
||||
"EditorSubsystem",
|
||||
"MainFrame",
|
||||
"BlueprintGraph",
|
||||
"VisualStudioDTE",
|
||||
"EditorStyle",
|
||||
"Projects"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "2.7",
|
||||
"FriendlyName": "Visual Studio Integration Tools",
|
||||
"Description": "Enables integration with Visual Studio IDE.",
|
||||
"Category": "Programming",
|
||||
"CreatedBy": "Microsoft",
|
||||
"CreatedByURL": "http://www.microsoft.com",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2",
|
||||
"SupportURL": "https://developercommunity.visualstudio.com/",
|
||||
"EnabledByDefault": true,
|
||||
"Installed": false,
|
||||
"bExplicitlyLoaded": true,
|
||||
"CanContainContent": false,
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64"
|
||||
],
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "VisualStudioTools",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "VisualStudioBlueprintDebuggerHelper",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "None",
|
||||
"PlatformAllowList": [
|
||||
"Win64"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
From f7238064c8680f6392793eb664ee2c773daff594 Mon Sep 17 00:00:00 2001
|
||||
From: Oleksandr Kozlov <okozlov@microsoft.com>
|
||||
Date: Tue, 1 Apr 2025 15:22:14 +0200
|
||||
Subject: [PATCH] Support extra UBT args in UAT.BuildPlugin
|
||||
|
||||
Forwarding extra parameters to UBT to allow customizing the build of a plugin.
|
||||
Example: runuat.bat buildpluing -plugin=... -ubtargs="-LinkerArguments=\"/profile\""
|
||||
|
||||
---
|
||||
.../Scripts/BuildPluginCommand.Automation.cs | 13 +++++++++++++
|
||||
1 file changed, 13 insertions(+)
|
||||
|
||||
diff --git a/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs
|
||||
index 5a43dc0c4..aaf3f192f 100644
|
||||
--- a/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs
|
||||
+++ b/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs
|
||||
@@ -64,6 +64,9 @@ public sealed class BuildPlugin : BuildCommand
|
||||
// Option for verifying that all include directive s
|
||||
bool bStrictIncludes = ParseParam("StrictIncludes");
|
||||
|
||||
+ // Extra arguments forwarded to UBT
|
||||
+ string UBTArgs = ParseParamValue("ubtargs");
|
||||
+
|
||||
// Make sure the packaging directory is valid
|
||||
DirectoryReference PackageDir = new DirectoryReference(PackageParam);
|
||||
|
||||
@@ -126,6 +129,16 @@ public sealed class BuildPlugin : BuildCommand
|
||||
AdditionalArgs.Append(" -NoPCH -NoSharedPCH -DisableUnity");
|
||||
}
|
||||
|
||||
+ // Pass extra parameters to UBT
|
||||
+ if (string.IsNullOrEmpty(UBTArgs) == false)
|
||||
+ {
|
||||
+ Logger.LogInformation("Building with extra UBT parameters: {UBTArgs}", UBTArgs);
|
||||
+ string Arg = UBTArgs;
|
||||
+ Arg = Arg.TrimStart(new char[] { '\"' });
|
||||
+ Arg = Arg.TrimEnd(new char[] { '\"' });
|
||||
+ AdditionalArgs.Append(' ').Append(Arg);
|
||||
+ }
|
||||
+
|
||||
// check if any architectures were specified
|
||||
foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms())
|
||||
{
|
||||
--
|
||||
2.49.0.windows.1
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"tsaVersion": "TsaV2",
|
||||
"codebase": "NewOrUpdate",
|
||||
"codebaseName": "vc-ue-extensions",
|
||||
"tsaStamp": "DevDiv",
|
||||
"tsaEnvironment": "PROD",
|
||||
"notificationAliases": [
|
||||
"hebaggio@microsoft.com",
|
||||
"cpp-apogee@microsoft.com"
|
||||
],
|
||||
"codebaseAdmins": [
|
||||
"REDMOND\\hebaggio",
|
||||
"REDMOND\\cpp-apogee"
|
||||
],
|
||||
"instanceUrl": "https://devdiv.visualstudio.com",
|
||||
"projectName": "DevDiv",
|
||||
"areaPath": "DevDiv\\Cpp Developer Experience\\GameDev Experience\\Unreal Engine integrations",
|
||||
"iterationPath": "DevDiv",
|
||||
"alltools": true,
|
||||
"repositoryName": "vc-ue-extensions"
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
# IMPORTANT:
|
||||
# Do not run BinSkim because we do not control producing the binaries. That process is owned by
|
||||
# Epic. We just provide source code. Since we do not control the build, BinSkim is not needed.
|
||||
|
||||
variables:
|
||||
Codeql.Enabled: true
|
||||
Codeql.SourceRoot: '$(Build.SourcesDirectory)'
|
||||
|
||||
trigger:
|
||||
- main
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- dev/*
|
||||
|
||||
schedules:
|
||||
- cron: "0 12 * * Sun"
|
||||
displayName: Weekly run
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: true
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: 1ESPipelineTemplates
|
||||
type: git
|
||||
name: 1ESPipelineTemplates/1ESPipelineTemplates
|
||||
ref: refs/tags/release
|
||||
|
||||
extends:
|
||||
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
|
||||
parameters:
|
||||
pool:
|
||||
name: VSEngSS-MicroBuild2022-1ES
|
||||
os: windows
|
||||
customBuildTags:
|
||||
- ES365AIMigrationTooling
|
||||
stages:
|
||||
- stage: stage
|
||||
jobs:
|
||||
- job: 'UnrealEngine_VisualStudioTools_Compliance'
|
||||
timeoutInMinutes: 1440
|
||||
steps:
|
||||
- task: CmdLine@2
|
||||
displayName: 'Clone Unreal Engine Repository'
|
||||
inputs:
|
||||
script: 'git clone "https://$(token)@github.com/EpicGames/UnrealEngine" --single-branch --branch $(ue_branch) --depth 1 ue'
|
||||
workingDirectory: '$(Agent.BuildDirectory)'
|
||||
- task: CmdLine@2
|
||||
displayName: 'Apply patch to allow us to pass linker flags to the build'
|
||||
inputs:
|
||||
script: 'git apply --ignore-whitespace $(Build.SourcesDirectory)/azure-pipelines/Support-extra-UBT-args-in-UAT.BuildPlugin.patch'
|
||||
workingDirectory: '$(Agent.BuildDirectory)\ue'
|
||||
- task: PowerShell@2
|
||||
displayName: '[UE] Append /unattended option'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script:
|
||||
$filePath = "$(Agent.BuildDirectory)/ue/Setup.bat";
|
||||
(Get-Content $filePath).Replace("/register", "/register /unattended") | Set-Content $filePath
|
||||
- task: CmdLine@2
|
||||
displayName: '[UE] Run Setup.bat'
|
||||
inputs:
|
||||
script: 'ue\Setup.bat --force'
|
||||
workingDirectory: '$(Agent.BuildDirectory)'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build Plugin'
|
||||
timeoutInMinutes: 300
|
||||
inputs:
|
||||
solution: '$(Build.SourcesDirectory)/build.proj'
|
||||
msbuildArguments: '/p:UnrealEngine=$(Agent.BuildDirectory)\ue /p:OutputPath=$(Build.ArtifactStagingDirectory)\drop /p:VulkanReadyBinaries=true'
|
||||
createLogFile: true
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Collect binaries to analyze'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.ArtifactStagingDirectory)\drop'
|
||||
Contents: '**\unrealeditor-visualstudiotools.*'
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)\binariesToAnalyze'
|
||||
CleanTargetFolder: true
|
||||
OverWrite: true
|
||||
- task: PoliCheck@2
|
||||
inputs:
|
||||
targetType: 'F'
|
||||
targetArgument: '$(Build.SourcesDirectory)'
|
||||
- task: ComponentGovernanceComponentDetection@0
|
||||
inputs:
|
||||
ignoreDirectories: '$(Agent.BuildDirectory)\ue'
|
||||
displayName: 'Component Detection'
|
||||
- task: APIScan@2
|
||||
displayName: 'Run APIScan'
|
||||
inputs:
|
||||
softwareFolder: '$(Build.ArtifactStagingDirectory)/binariesToAnalyze'
|
||||
softwareName: 'Visual Studio Tools for Unreal Engine'
|
||||
softwareVersionNum: '2.4'
|
||||
softwareBuildNum: '$(Build.BuildId)'
|
||||
toolVersion: 'Latest'
|
||||
env:
|
||||
AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId)
|
||||
- task: SDLNativeRules@3
|
||||
displayName: 'Run the PREfast SDL Native Rules for MSBuild'
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
publishXML: true
|
||||
userProvideBuildInfo: auto
|
||||
rulesetName: Recommended
|
||||
setupCommandlinePicker: 'vs2022'
|
||||
- task: PublishSecurityAnalysisLogs@3
|
||||
displayName: 'Publish security analysis logs'
|
||||
inputs:
|
||||
ArtifactName: 'CodeAnalysisLogs'
|
||||
ArtifactType: 'Container'
|
||||
AllTools: true
|
||||
ToolLogsNotFoundAction: 'Standard'
|
||||
- task: TSAUpload@2
|
||||
displayName: 'TSA upload'
|
||||
inputs:
|
||||
GdnPublishTsaOnboard: True
|
||||
GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)/azure-pipelines/TSAOptions.json'
|
||||
@@ -0,0 +1,117 @@
|
||||
# IMPORTANT:
|
||||
# Do not run BinSkim because we do not control producing the binaries. That process is owned by
|
||||
# Epic. We just provide source code. Since we do not control the build, BinSkim is not needed.
|
||||
|
||||
# Manual trigger for now
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
parameters:
|
||||
- name: SignTypeOverride
|
||||
displayName: Signing Type (default is real for the main branch and test otherwise)
|
||||
type: string
|
||||
default: default
|
||||
values:
|
||||
- default
|
||||
- test
|
||||
- real
|
||||
|
||||
variables:
|
||||
# MicroBuild requires TeamName to be set.
|
||||
- name: TeamName
|
||||
value: C++ Unreal Engine
|
||||
- name: TagName
|
||||
value: $[replace(variables['Build.SourceBranch'], 'refs/tags/', '')]
|
||||
# If the user didn't override the signing type, then only real-sign on main.
|
||||
- ${{ if ne(parameters.SignTypeOverride, 'default') }}:
|
||||
- name: SignType
|
||||
value: ${{ parameters.SignTypeOverride }}
|
||||
- ${{ if and(eq(parameters.SignTypeOverride, 'default'), or(eq(variables['Build.SourceBranchName'], 'main'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))) }}:
|
||||
- name: SignType
|
||||
value: real
|
||||
- ${{ if and(eq(parameters.SignTypeOverride, 'default'), not(or(eq(variables['Build.SourceBranchName'], 'main'), startsWith(variables['Build.SourceBranch'], 'refs/tags')))) }}:
|
||||
- name: SignType
|
||||
value: test
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: 1ESPipelineTemplates
|
||||
type: git
|
||||
name: 1ESPipelineTemplates/1ESPipelineTemplates
|
||||
ref: refs/tags/release
|
||||
|
||||
extends:
|
||||
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
|
||||
parameters:
|
||||
pool:
|
||||
name: VSEngSS-MicroBuild2022-1ES
|
||||
os: windows
|
||||
customBuildTags:
|
||||
- ES365AIMigrationTooling
|
||||
stages:
|
||||
- stage: stage
|
||||
jobs:
|
||||
- job: 'UnrealEngine_VisualStudioTools_Release'
|
||||
timeoutInMinutes: 1440
|
||||
templateContext:
|
||||
outputParentDirectory: $(Agent.BuildDirectory)/out/
|
||||
outputs:
|
||||
- output: pipelineArtifact
|
||||
displayName: 'Publish signed plugin files'
|
||||
targetPath: $(Agent.BuildDirectory)/out
|
||||
artifactName: SignedPlugin
|
||||
artifactType: container
|
||||
sbomEnabled: false
|
||||
steps:
|
||||
- powershell: |
|
||||
Write-Host "##vso[task.setVariable variable=SHA1]$(git rev-parse HEAD)"
|
||||
displayName: Save tag commit hash
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
- task: MicroBuildSigningPlugin@4
|
||||
displayName: Install MicroBuild Signing
|
||||
inputs:
|
||||
signType: $(SignType)
|
||||
zipSources: true
|
||||
- task: PowerShell@2
|
||||
displayName: "Create zip excluding .git folder"
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$destinationDirectory = "$(Agent.BuildDirectory)/out"
|
||||
New-Item -Path $destinationDirectory -ItemType Directory
|
||||
git archive -o "$destinationDirectory/VisualStudioTools.zip" $(SHA1)
|
||||
- powershell: New-FileCatalog -Path .\VisualStudioTools.zip -CatalogFilePath .\VisualStudioTools.zip.cat -CatalogVersion 2.0
|
||||
displayName: Create standalone catalog
|
||||
workingDirectory: '$(Agent.BuildDirectory)\out'
|
||||
- task: NuGetToolInstaller@1
|
||||
inputs:
|
||||
versionSpec: 5.7
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'NuGet Restore MicroBuild Signing Extension'
|
||||
inputs:
|
||||
command: 'restore'
|
||||
restoreSolution: 'Scripts/SignDetached.proj'
|
||||
feedsToUse: 'config'
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\Scripts\packages'
|
||||
- task: MSBuild@1
|
||||
displayName: Sign catalogs
|
||||
inputs:
|
||||
solution: Scripts/SignDetached.proj
|
||||
msbuildArguments: /p:SignType=$(SignType) /p:BaseOutputDirectory=$(Agent.BuildDirectory)\out
|
||||
# === Disabled for now in favor of uploading the artifacts to the pipeline ===
|
||||
# - task: GitHubRelease@1
|
||||
# displayName: Pre-release to public repo
|
||||
# condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
|
||||
# inputs:
|
||||
# gitHubConnection: GitHub-VSCodeExtensions
|
||||
# repositoryName: microsoft/vc-ue-extensions
|
||||
# action: create
|
||||
# target: $(SHA1)
|
||||
# tagSource: gitTag
|
||||
# tag: $(TagName)
|
||||
# title: $(TagName)
|
||||
# isDraft: true
|
||||
# isPreRelease: true
|
||||
# assets: |
|
||||
# $(Agent.BuildDirectory)\out\VisualStudioTools.zip
|
||||
# $(Agent.BuildDirectory)\out\VisualStudioTools.zip.cat
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PluginFolder>$(MSBuildProjectDirectory)</PluginFolder>
|
||||
<PluginFile>$([System.IO.Path]::Combine($(PluginFolder), `VisualStudioTools.uplugin`))</PluginFile>
|
||||
<OutputPath Condition=" '$(OutputPath)'=='' ">$([System.IO.Path]::Combine($(PluginFolder), "bin"))</OutputPath>
|
||||
<EnginePath>$(UnrealEngine)</EnginePath>
|
||||
<EnginePath Condition="!Exists('$(EnginePath)')">
|
||||
$([MSBuild]::GetRegistryValue('HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\Unreal Engine\$(UnrealEngine)', 'InstalledDirectory'))
|
||||
</EnginePath>
|
||||
<UATScript>$(EnginePath.Trim())\Engine\Build\BatchFiles\RunUAT.bat</UATScript>
|
||||
<UnversionedFlag Condition=" '$(Versioned)' != 'true'">-Unversioned</UnversionedFlag>
|
||||
<AdditionalFlags Condition=" '$(VulkanReadyBinaries)' == 'true'">-ubtargs="-LinkerArguments=\"/profile\" "</AdditionalFlags>
|
||||
</PropertyGroup>
|
||||
<Target Name="Build">
|
||||
<Error Text="Cannot locate the RunUAT.bat script at $(UATScript). Check if the $UnrealEngine property is a valid path or installed version." Condition="!Exists('$(UATScript)')"></Error>
|
||||
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
|
||||
<Exec
|
||||
Command=""$(UATScript)" BuildPlugin -Plugin="$(PluginFile)" -TargetPlatforms=Win64 -Package="$(OutputPath)" $(AdditionalFlags) $(UnversionedFlag) -FromMsBuild"
|
||||
EnvironmentVariables="VSTUE_IsCustomDevBuild=1"/>
|
||||
</Target>
|
||||
<Target Name="Clean" >
|
||||
<RemoveDir Directories="$(OutputPath);"/>
|
||||
</Target>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user