ぼっちプログラマのメモ

UE4とかUE5とかについて書いたり書かなかったり。

【UE5】Collision PresetにおけるCustomを無効化する方法について

本記事はUnreal Engine (UE)のカレンダー | Advent Calendar 2023 - Qiita シリーズ 5の 1日目の記事です

はじめに


ホグワーツレガシーの開発者による講演「Collision Data in UE5: Practical Tips for Managing Collision Settings & Queries(Unreal Engine 5での衝突データ: 衝突設定とクエリの管理に関する実用的なアドバイス)」にて、「エンジン改造してCollision PresetのCustomを禁止するのはいいぞ」という内容がありましたが、具体的にどうすればいいのかは説明がなかったので…調べて記事にしました!

動いてる様子

なぜCustomを禁止にすると良いのか?など講演の内容に関しては下記記事でまとめてるのでどうぞ!
(Customが混在してるとバグの温床になるし、調査もしづらい的な理由です)
pafuhana1213.hatenablog.com

実現方法

Customを選択不可能に

Engine\Source\Editor\DetailCustomizations\Private\BodyInstanceCustomization.cpp

void FBodyInstanceCustomization::OnCollisionProfileChanged( TSharedPtr<FString> NewSelection, ESelectInfo::Type SelectInfo, IDetailGroup* CollisionGroup )
{
	// if it's set from code, we did that on purpose
	if (SelectInfo != ESelectInfo::Direct)
	{
		// add 
		if( NewSelection == CollisionProfileComboList[GetCustomIndex()])
		{
			return;
		}
		//
...

Customをグレーアウトに

TSharedRef<SWidget> FBodyInstanceCustomization::MakeCollisionProfileComboWidget(TSharedPtr<FString> InItem)
{
	FString ProfileMessage;

	FCollisionResponseTemplate ProfileData;
	if (CollisionProfile->GetProfileTemplate(FName(**InItem), ProfileData))
	{
		ProfileMessage = ProfileData.HelpMessage;
	}

	return
		SNew(STextBlock)
		.Text(FText::FromString(*InItem))
		// add
		.IsEnabled(!InItem->Equals(TEXT("Custom...")))
		//
		.ToolTipText(FText::FromString(ProfileMessage))
		.Font(IDetailLayoutBuilder::GetDetailFont());
}

Customをリストから消しちゃう

既にCustomを使用しているプロジェクトだと何らかの問題起きそう

void FBodyInstanceCustomization::RefreshCollisionProfiles()
{
	int32 NumProfiles = CollisionProfile->GetNumOfProfiles();

	bool bCanUseDefaultCollision = CanUseDefaultCollision();

	// first create profile combo list
	CollisionProfileComboList.Empty(NumProfiles + (bCanUseDefaultCollision ? 2 : 1));	//If we can use default collision we'll add a "Default" option

	// first one is default one
	if(bCanUseDefaultCollision)
	{
		CollisionProfileComboList.Add(MakeShareable(new FString(TEXT("Default"))));
	}

	// comment out
	//CollisionProfileComboList.Add(MakeShareable(new FString(TEXT("Custom..."))));
	//

以降は上記コード変更に対しての簡単な補足

補足

「Customを選択不可能に」の補足

FBodyInstanceCustomization::OnCollisionProfileChanged は Collision Presetのリストから選択した際に呼ばれる処理なので、Customだったら処理スキップしちゃえという強引実装です。ちなみに、!ProfileData.bCanModify だとDefaultもグレーアウトされちゃいました。残念

あ、FBodyInstanceCustomization::AddCollisionCategory の 後半に上記関数を登録している処理があります

SAssignNew(CollsionProfileComboBox, SComboBox< TSharedPtr<FString> >)
.OptionsSource(&CollisionProfileComboList)
.OnGenerateWidget(this, &FBodyInstanceCustomization::MakeCollisionProfileComboWidget)
.OnSelectionChanged(this, &FBodyInstanceCustomization::OnCollisionProfileChanged, &CollisionGroup)
.OnComboBoxOpening(this, &FBodyInstanceCustomization::OnCollisionProfileComboOpening)
.InitiallySelectedItem(DisplayName)
.Content()
[
	SNew(STextBlock)
	.Text(this, &FBodyInstanceCustomization::GetCollisionProfileComboBoxContent)
	.Font(IDetailLayoutBuilder::GetDetailFont())
	.ToolTipText(this, &FBodyInstanceCustomization::GetCollisionProfileComboBoxToolTip)
]

「Customをグレーアウトに」の補足

↑のコードにある通り、MakeCollisionProfileComboWidgetはComboBox内の各選択項目の構築処理です。

.OnGenerateWidget(this, &FBodyInstanceCustomization::MakeCollisionProfileComboWidget)

で、CustomだったらEnableを falseにしてグレーアウトしちゃえという強引実装です

「Customをリストから消しちゃう」の補足

FBodyInstanceCustomization::RefreshCollisionProfiles()では ComboBoxで表示するCollision Presetの配列(TArray TSharedPtr>CollisionProfileComboList)を構築していて、Customだけ特殊で明示的に追加処理があるので、そこを消しちゃえという強引実装です

もっとダイナミックに拡張したいぜ!という方は、FBodyInstanceCustomization::AddCollisionCategory後半のSlate構築処理を弄っていくことになるかと思います!ちなみに構築時のコールスタックはこんな感じ

FBodyInstanceCustomization::AddCollisionCategory(TSharedRef<IPropertyHandle,1>,IDetailChildrenBuilder &,IPropertyTypeCustomizationUtils &) BodyInstanceCustomization.cpp:128
FBodyInstanceCustomization::CustomizeChildren(TSharedRef<IPropertyHandle,1>,IDetailChildrenBuilder &,IPropertyTypeCustomizationUtils &) BodyInstanceCustomization.cpp:267
FDetailPropertyRow::OnItemNodeInitialized(TSharedRef<FDetailCategoryImpl,1>,const TAttribute<bool> &,TSharedPtr<IDetailGroup,1>) DetailPropertyRow.cpp:323
FDetailItemNode::InitPropertyEditor() DetailItemNode.cpp:154
FDetailItemNode::Initialize() DetailItemNode.cpp:45
FDetailCategoryImpl::GenerateNodesFromCustomizations(const TArray<FDetailLayoutCustomization,TSizedDefaultAllocator<32> > &,TArray<TSharedRef<FDetailTreeNode,1>,TSizedDefaultAllocator<32> > &) DetailCategoryBuilderImpl.cpp:1001
FDetailCategoryImpl::GenerateChildrenForSingleLayout(const FDetailLayout &,const TArray<FDetailLayoutCustomization,TSizedDefaultAllocator<32> > &,TArray<TSharedRef<FDetailTreeNode,1>,TSizedDefaultAllocator<32> > &) DetailCategoryBuilderImpl.cpp:1023
FDetailCategoryImpl::GenerateChildrenForLayouts() DetailCategoryBuilderImpl.cpp:1044
FDetailCategoryImpl::GenerateLayout() DetailCategoryBuilderImpl.cpp:1210
FDetailLayoutBuilderImpl::BuildCategories(const TMap<FName,TSharedPtr<FDetailCategoryImpl,1>,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<FName,TSharedPtr<FDetailCategoryImpl,1>,0> > &,TArray<TSharedRef<FDetailCategoryImpl,1>,TSizedDefaultAllocator<32> > &,TArray<TSharedRef<FDetailCategoryImpl,1>,TSizedDefaultAllocator<32> > &) DetailLayoutBuilderImpl.cpp:316
FDetailLayoutBuilderImpl::GenerateDetailLayout() DetailLayoutBuilderImpl.cpp:456
SDetailsViewBase::UpdateSinglePropertyMap(TSharedPtr<FComplexPropertyNode,1>,FDetailLayoutData &,bool) SDetailsViewBase.cpp:665
SDetailsViewBase::UpdatePropertyMaps() SDetailsViewBase.cpp:608
...

Unreal Engine (UE) Advent Calendar 2023、既にたくさん記事が公開されてるので是非見てみてください!
明日以降の記事もすごく楽しみですー!
qiita.com

おしまい