[UE] Weapon Plugin 10: 삽입,삭제,복제 기능 추가, HitData 카테고리 추가

Weapon Plugin 내 항목을 삽입, 삭제, 복제하는 기능을 추가한다. HitData 관련 카테고리를 추가하여 플러그인에서 HitData에서 관리하게 만든다.
목차
Plugins |
||||
Weapon |
||||
Resource |
||||
Icon128.png weapon_thumbnail_icon.png |
||||
Source |
||||
Weapon | ||||
SWeaponCheckBoxes.h .cpp SWeaponDetailsView.h .cpp SWeaponDoActionData.h .cpp SWeaponEquipmentData.h .cpp SWeaponHitData.h .cpp 생성 SWeaponLeftArea.h .cpp Weapon.Build.cs WeaponAssetEditor.h .cpp WeaponAssetFactory.h .cpp WeaponCommand.h .cpp WeaponContextMenu.h .cpp WeaponModule.h .cpp WeaponStyle.h .cpp |
||||
Source | ||
Characters | ||
CAnimInstance.h .cpp CEnemy.h .cpp CPlayer.h .cpp ICharacter.h .cpp |
||
Components | ||
CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CWeaponComponent.h .cpp |
||
Notifies | ||
CAnimNotifyState_BeginAction.h .cpp CAnimNotify_CameraShake.h .cpp CAnimNotifyState_EndAction.h .cpp CAnimNotify_EndState.h .cpp CAnimNotifyState_Collision.h .cpp CAnimNotifyState_Combo.h .cpp CAnimNotifyState_Equip.h .cpp |
||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CDoAction_Combo.h .cpp CAttachment.h .cpp CDoAction.h .cpp CEquipment.h .cpp CWeaponAsset.h .cpp CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp .Build.cs |
||
.uproject | ||
삽입, 삭제, 복제 기능 추가하기
Plugin의 참조 방식
Plugin는 서로를 직접 참조하지 않고 중앙 Editor를 통해서 참조한다.
CWeaponAsset.h .cpp 내의
if WITH_EDITOR ~ #endif 구동방식
void PostEditChangeChainProperty()

Plugin과 Editor의 연관성을 줄인다.
커플링 방지.
CWeaponAsset
CWeaponAsset.h
#pragma once #include "CoreMinimal.h" #include "Engine/DataAsset.h" #include "Weapons/CWeaponStructures.h" #include "CWeaponAsset.generated.h" UCLASS() class U2212_06_API UCWeaponAsset : public UDataAsset { GENERATED_BODY() private: UPROPERTY(EditAnywhere) TSubclassOf<class ACAttachment> AttachmentClass; UPROPERTY(EditAnywhere) FEquipmentData EquipmentData; UPROPERTY(EditAnywhere) TSubclassOf<class UCEquipment> EquipmentClass; UPROPERTY(EditAnywhere) TSubclassOf<class UCDoAction> DoActionClass; UPROPERTY(EditAnywhere) TArray<FDoActionData> DoActionDatas; //CWeaopnStructure내의 FDoActionData UPROPERTY(EditAnywhere) TArray<FHitData> HitDatas; //CWeaopnStructure내의 FHitData public: FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }//외부에 생성된 것을 리턴해줌. FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }//외부에 생성된 것을 리턴해줌. FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }//외부에 생성된 것을 리턴해줌. public: UCWeaponAsset(); void BeginPlay(class ACharacter* InOwner); private: //UPROPERTY를 붙여 가비지 콜렉터가 제거하기 전까지 물고 있게 만든다. //UWeaponAsset은 UObject로부터 상속받아 Actor의 생성주기에 영향을 받지 않아 가비지 콜렉터에 영향을 받는다. UPROPERTY() class ACAttachment* Attachment; UPROPERTY() class UCEquipment* Equipment; UPROPERTY() class UCDoAction* DoAction; #if WITH_EDITOR //Editor 내에서만 수행 void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override; #endif };
변경사항 없음.
CWeaponAsset.cpp
#include "Weapons/CWeaponAsset.h" #include "Global.h" #include "CAttachment.h" #include "CEquipment.h" #include "CDoAction.h" #include "GameFramework/Character.h" UCWeaponAsset::UCWeaponAsset() { AttachmentClass = ACAttachment::StaticClass();//기본값 EquipmentClass = UCEquipment::StaticClass();//기본값 DoActionClass = UCDoAction::StaticClass();//기본값 } void UCWeaponAsset::BeginPlay(ACharacter* InOwner) { if (!!AttachmentClass)//AttachmentClass가 선택되어 있다면 { FActorSpawnParameters params; params.Owner = InOwner; Attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params); } if (!!EquipmentClass)//EquipmentClass가 선택되어 있다면 { Equipment = NewObject<UCEquipment>(this, EquipmentClass); Equipment->BeginPlay(InOwner, EquipmentData); if (!!Attachment)//Attachment가 있다면 { Equipment->OnEquipmentBeginEquip.AddDynamic(Attachment, &ACAttachment::OnBeginEquip); Equipment->OnEquipmentUnequip.AddDynamic(Attachment, &ACAttachment::OnUnequip); } } if(!!DoActionClass) { DoAction = NewObject<UCDoAction>(this, DoActionClass); DoAction->BeginPlay(Attachment, Equipment, InOwner, DoActionDatas, HitDatas); if (!!Attachment) { Attachment->OnAttachmentBeginCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginCollision); Attachment->OnAttachmentEndCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndCollision); Attachment->OnAttachmentBeginOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginOverlap); Attachment->OnAttachmentEndOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndOverlap); } } } #if WITH_EDITOR //Editor 내에서만 수행 void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) { Super::PostEditChangeChainProperty(PropertyChangedEvent); CheckTrue(FApp::IsGame());//게임이 실행중이면 실행하면 안 되기 때문에 체크 bool bRefresh = false; bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("DoActionDatas") == 0; bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("HitDatas") == 0;//수정하려는 변수명 == 0 이면 이름이 동일하다는 의미. if (bRefresh) { bool bCheck = false; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayRemove; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayClear; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::Duplicate; if (bCheck) { FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");//WITH_EDITOR로 Editor 내에서 수행하기 때문에 사용 가능. TSharedPtr<IDetailsView> detailsView = prop.FindDetailView("WeaponAssetEditorDetailsView");//WeaponAssetEditor.cpp에서 설정한 arg.ViewIdentifier이름 WeaponAssetEditorDetailsView 사용. WeaponAssetEditorDetailsView를 찾는다. if (detailsView.IsValid())//detailsView 창이 그려졌다면 detailsView->ForceRefresh();//새로고침 } } } #endif
Editor 내에서만 수행하는 함수
- #if WITH_EDITOR
void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
#endif
실행화면

Hit Data 만들기
SWeaponHitData 생성
새 C++ 클래스 - 없음 - SWeaponHitData 생성

SWeaponHitData.h
#pragma once #include "CoreMinimal.h" #include "IPropertyTypeCustomization.h" class WEAPON_API SWeaponHitData : public IPropertyTypeCustomization { public: static TSharedRef<IPropertyTypeCustomization> MakeInstance(); static TSharedPtr<class SWeaponCheckBoxes> AddCheckBoxes(); static void EmptyCheckBoxes();//한번에 비워주는 역할의 함수 void CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils) override; void CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils) override; private: static TArray<TSharedPtr<class SWeaponCheckBoxes>> CheckBoxes; };
SWeaponHitData.cpp
#include "SWeaponHitData.h" #include "WeaponStyle.h" #include "IPropertyUtilities.h" #include "IDetailPropertyRow.h" #include "IDetailChildrenBuilder.h" #include "SWeaponCheckBoxes.h" #include "DetailWidgetRow.h" TArray<TSharedPtr<SWeaponCheckBoxes>> SWeaponHitData::CheckBoxes; TSharedRef<IPropertyTypeCustomization> SWeaponHitData::MakeInstance() { //자신의 클래스 타입을 만들어서 return해준다. return MakeShareable(new SWeaponHitData()); } TSharedPtr<SWeaponCheckBoxes> SWeaponHitData::AddCheckBoxes() { TSharedPtr<SWeaponCheckBoxes> checkBoxes = MakeShareable(new SWeaponCheckBoxes()); int32 index = CheckBoxes.Add(checkBoxes); return CheckBoxes[index]; } void SWeaponHitData::EmptyCheckBoxes() { for (TSharedPtr<SWeaponCheckBoxes> ptr : CheckBoxes) { if (ptr.IsValid()) ptr.Reset(); } CheckBoxes.Empty(); } void SWeaponHitData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils) { if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false) return;//CanDraw가 false면 그리지 않고 리턴. int32 index = InPropertyHandle->GetIndexInArray();//GetIndexInArray()는 Array 안에서의 번호를 리턴. CheckBoxes[index]->SetUtilities(InCustomizationUtils.GetPropertyUtilities());//Header,Children,Header,Children..순서로 콜된다. FString name = InPropertyHandle->GetPropertyDisplayName().ToString();//0,1,2..표시하는 name name = "Hit Data - " + name; InHeaderRow .NameContent() [ SNew(SBorder)//SBorder는 한 줄을 출력할 때 사용. .BorderImage(FWeaponStyle::Get()->Array_Image.Get()) [ InPropertyHandle->CreatePropertyNameWidget(FText::FromString(name)) ] ] .ValueContent() .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X) .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y) [ CheckBoxes[index]->Draw(true)//CheckBoxes를 그려준다(=생성한다).bBackground에 true값을 넣어주어 alpha값이 0.1f인 흰색 이미지가 겹쳐저 해당 줄이 띠처럼 보이게 만들어준다. ]; } void SWeaponHitData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils) { if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false) return;//CanDraw가 false면 그리지 않고 리턴. int32 index = InPropertyHandle->GetIndexInArray();//GetIndexInArray()는 Array 안에서의 번호를 리턴. CheckBoxes[index]->DrawProperties(InPropertyHandle, &InChildBuilder); }
SWeaponDetailsView
SWeaponDetailsView.cpp
#pragma once #include "CoreMinimal.h" #include "IDetailCustomization.h" class WEAPON_API SWeaponDetailsView : public IDetailCustomization { public: static TSharedRef<IDetailCustomization> MakeInstance(); void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; public: //외부에서 CheckBoxes 바꿔주는 역할 static void OnRefreshByCheckBoxes() { bRefreshByCheckBoxes = true; } static void OffRefreshByCheckBoxes() { bRefreshByCheckBoxes = false; } private: static bool bRefreshByCheckBoxes; };
SWeaponDetailsView.cpp
#include "SWeaponDetailsView.h" #include "SWeaponCheckBoxes.h" #include "SWeaponEquipmentData.h" #include "SWeaponDoActionData.h" #include "SWeaponHitData.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "IDetailPropertyRow.h" #include "Weapons/CWeaponAsset.h" #include "Animation/AnimMontage.h" #include "Particles/ParticleSystem.h" #include "NiagaraSystem.h" #include "Sound/SoundWave.h" bool SWeaponDetailsView::bRefreshByCheckBoxes = false;//static 변수 초기화. TSharedRef<IDetailCustomization> SWeaponDetailsView::MakeInstance() { //자신의 클래스 타입을 만들어서 return해준다. return MakeShareable(new SWeaponDetailsView()); } void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { UClass* type = UCWeaponAsset::StaticClass();//UClass 타입 하나를 받아온다. //DetailBuilder.HideCategory("CWeaponAsset");//CWeaponAsset 카테고리를 숨겨준다. 현재는 모든 카테고리를 노출시키기 때문에 사용하지 않는다. //Class Settings { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return //ClassSettings는 없으므로 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("ClassSettings", FText::FromString("Class Settings")); //CWeaponAsset에서 직렬화된 변수들을 카테고리에 추가한다. category.AddProperty("AttachmentClass", type);//CWeaponAsset의 AttachmentClass를 카테고리에 추가. category.AddProperty("EquipmentClass", type);//CWeaponAsset의 EquipmentClass를 카테고리에 추가. category.AddProperty("DoActionClass", type);//CWeaponAsset의 DoActionClass를 카테고리에 추가. } //EquipmentData { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("EquipmentData", FText::FromString("Equipment Data")); IDetailPropertyRow& row = category.AddProperty("EquipmentData", type); if (bRefreshByCheckBoxes == false)//새로고침이 아닐 때 { TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponEquipmentData::CreateCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다. checkBoxes->AddProperties(row.GetPropertyHandle());//checkBoxes에 실제로 가진 Handle를 추가 FEquipmentData data;//변수 선언한다. FEquipmentData의 변수 data 기본값을 아래에서 사용한다. int32 index = 0; checkBoxes->CheckDefaultObject(index++, data.Montage); checkBoxes->CheckDefaultValue(index++, data.PlayRate); checkBoxes->CheckDefaultValue(index++, data.bCanMove); checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation); } } //DoActionData { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("DoActionData", FText::FromString("DoAction Data")); IDetailPropertyRow& row = category.AddProperty("DoActionDatas", type);//변수 추가 //WeaponAsset에 있는 데이터명과 일치시킨다. DoActionDatas if (bRefreshByCheckBoxes == false) { uint32 count = 0; row.GetPropertyHandle()->GetNumChildren(count);//전체 개수를 구한다. SWeaponHitData::EmptyCheckBoxes();//비워놓고 시작. FDoActionData data;//기본값 사용할 변수 for (uint32 i =0; i < count; i++)//자식Handle를 for문 돌리기 { TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);//헤더의 handle TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponHitData::AddCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다. checkBoxes->AddProperties(handle); int32 index = 0; checkBoxes->CheckDefaultObject(index++, data.Montage); checkBoxes->CheckDefaultValue(index++, data.PlayRate); checkBoxes->CheckDefaultValue(index++, data.bCanMove); checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation); checkBoxes->CheckDefaultValue(index++, data.bFixedCamera); checkBoxes->CheckDefaultObject(index++, data.Effect); checkBoxes->CheckDefaultValue(index++, data.EffectLocation); checkBoxes->CheckDefaultValue(index++, data.EffectScale); } }//if(bRefreshByCheckBoxes) } //HitData { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("HitData", FText::FromString("Hit Data")); IDetailPropertyRow& row = category.AddProperty("HitDatas", type);//변수 추가 //WeaponAsset에 있는 데이터명과 일치시킨다. DoActionDatas if (bRefreshByCheckBoxes == false) { uint32 count = 0; row.GetPropertyHandle()->GetNumChildren(count);//전체 개수를 구한다. SWeaponHitData::EmptyCheckBoxes();//비워놓고 시작. FHitData data;//기본값 사용할 변수 for (uint32 i = 0; i < count; i++)//자식Handle를 for문 돌리기 { TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);//헤더의 handle TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponHitData::AddCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다. checkBoxes->AddProperties(handle); int32 index = 0; checkBoxes->CheckDefaultObject(index++, data.Montage); checkBoxes->CheckDefaultValue(index++, data.PlayRate); checkBoxes->CheckDefaultValue(index++, data.Power); checkBoxes->CheckDefaultValue(index++, data.Launch); checkBoxes->CheckDefaultValue(index++, data.StopTime); checkBoxes->CheckDefaultObject(index++, data.Sound); checkBoxes->CheckDefaultObject(index++, data.Effect); checkBoxes->CheckDefaultValue(index++, data.EffectLocation); checkBoxes->CheckDefaultValue(index++, data.EffectScale); } }//if(bRefreshByCheckBoxes) } }
헤더 추가
- #include "SWeaponHitData.h"
- #include "Sound/SoundWave.h"
WeaponAssetEditor
WeaponAssetEditor.cpp
#pragma once #include "CoreMinimal.h" #include "Toolkits/AssetEditorToolkit.h" class WEAPON_API FWeaponAssetEditor : public FAssetEditorToolkit { public: static void OpenWindow(FString InAssetName = ""); static void Shutdown(); private: static TSharedPtr<FWeaponAssetEditor> Instance; private: void Open(FString InAssetName); protected: bool OnRequestClose() override;//창이 닫힐 때 콜이 되게 설계. public: void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override; private: TSharedRef<SDockTab> Spawn_LeftAreaTab(const FSpawnTabArgs& InArgs); TSharedRef<SDockTab> Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs); private: void OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr);//LeftArea에서 선택한 FWeaponRowDataPtr public: FName GetToolkitFName() const override; FText GetBaseToolkitName() const override; FString GetWorldCentricTabPrefix() const override; FLinearColor GetWorldCentricTabColorScale() const override; private: TSharedPtr<class SWeaponLeftArea> LeftArea; TSharedPtr<class IDetailsView> DetailsView;//IDetailsView는 DetailView의 최상위 자료형. private: static const FName EditorName; static const FName LeftAreaTabId; static const FName DetailTabId; private: FReply OnClicked(); };
WeaponAssetEditor.cpp
#include "WeaponAssetEditor.h" #include "SWeaponLeftArea.h" #include "SWeaponDetailsView.h" #include "SWeaponEquipmentData.h" #include "SWeaponDoActionData.h" #include "SWeaponHitData.h" #include "Weapons/CWeaponAsset.h" //설정한 이름들 const FName FWeaponAssetEditor::EditorName = "WeaponAssetEditor"; const FName FWeaponAssetEditor::LeftAreaTabId = "LeftArea"; const FName FWeaponAssetEditor::DetailTabId = "Details"; TSharedPtr<FWeaponAssetEditor> FWeaponAssetEditor::Instance = nullptr;//헤더에서 선언한 static 외부 초기화. void FWeaponAssetEditor::OpenWindow(FString InAssetName) { //아래의 코드 대신에 Shutdown()을 사용하면 안 된다! Shutdown()은 다른 곳에서도 콜이 되기 때문에 아래의 코드 대신에 사용하면 문제가 된다. //창이 한번이라도 열리면 인스턴스가 할당된다. if (Instance.IsValid()) //창이 만들어졌다면 { if (Instance->LeftArea.IsValid())//LeftArea가 존재한다면 { FWeaponRowDataPtr ptr = nullptr; if (InAssetName.Len() > 0)//콘텐츠 브라우저를 더블클릭 했다면 ptr = Instance->LeftArea->GetRowDataPtrByName(InAssetName);//InAssetName으로 ptr if (ptr.IsValid() == false)//더블클릭 안 했다면 또는 InAssetName을 못 찾았다면 ptr = Instance->LeftArea->GetFirstDataPtr();//첫번째꺼 Instance->LeftArea->SelectDataPtr(ptr->Asset); return; } Instance->CloseWindow();//창을 닫는다. Instance.Reset(); Instance = nullptr; } Instance = MakeShareable(new FWeaponAssetEditor()); Instance->Open(InAssetName); } void FWeaponAssetEditor::Shutdown() { if (Instance.IsValid()) { Instance->CloseWindow(); Instance.Reset(); Instance = nullptr; } } void FWeaponAssetEditor::Open(FString InAssetName) { LeftArea = SNew(SWeaponLeftArea)//SWeaponLeftArea에서 받은 자료형을 생성하여 넣어준다. .OnSelectedItem(this, &FWeaponAssetEditor::OnListViewSelectedItem);//LeftArea에서 선택한 데이터 FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); //DetailsView { FDetailsViewArgs args(false, false, true, FDetailsViewArgs::HideNameArea);//기본값 설정. ActorsUserNameArea, ObjectsUserNameArea, HideNameArea args.ViewIdentifier = "WeaponAssetEditorDetailsView";//식별자 설정. 게임 Editor쪽에서 DetailView 접근시 이 식별자로 찾을 수 있다. DetailsView = prop.CreateDetailView(args);//Detail 창 띄우기. FOnGetDetailCustomizationInstance detailView; detailView.BindStatic(&SWeaponDetailsView::MakeInstance);//Static은 객체가 필요없다. 그래서 함수 주소로 바로 연결한다. DetailsView->SetGenericLayoutDetailsDelegate(detailView);//Delegate를 연결해준다. } //EquipmentData { FOnGetPropertyTypeCustomizationInstance instance; instance.BindStatic(&SWeaponEquipmentData::MakeInstance); prop.RegisterCustomPropertyTypeLayout("EquipmentData", instance);//instance를 delegate 등록 } //DoActionData { FOnGetPropertyTypeCustomizationInstance instance; instance.BindStatic(&SWeaponDoActionData::MakeInstance); prop.RegisterCustomPropertyTypeLayout("DoActionData", instance);//instance를 delegate 등록 } //HitData { FOnGetPropertyTypeCustomizationInstance instance; instance.BindStatic(&SWeaponHitData::MakeInstance); prop.RegisterCustomPropertyTypeLayout("HitData", instance);//instance를 delegate 등록 } //Layout 설정 TSharedRef<FTabManager::FLayout> layout = FTabManager::NewLayout("WeaponAssetEditor_Layout") ->AddArea //전체화면의 메인 영역 ( FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.1f)//10%만 사용하겠다. ->AddTab(GetToolbarTabId(), ETabState::OpenedTab) ) ->Split ( FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.175f)//왼쪽 17.5% 사용 ->AddTab(LeftAreaTabId, ETabState::OpenedTab)//ListViewTabId ->SetHideTabWell(true) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.725f)//오른쪽 72.5% 사용 ->AddTab(DetailTabId, ETabState::OpenedTab)//DetailTabId ->SetHideTabWell(true) ) ) ); UCWeaponAsset* asset = nullptr; if (InAssetName.Len() > 0)//받은 InAssetName의 문자열이 0보다 크다면(=에셋이 있다는 의미) { FWeaponRowDataPtr ptr = LeftArea->GetRowDataPtrByName(InAssetName); if (LeftArea->SelectedRowDataPtrName() == InAssetName)//LeftArea의 선택된 이름과 InAssetName이 같다면 return; if (ptr.IsValid()) asset = ptr->Asset;//ptr의 Asset을 넣어준다. } if (asset == nullptr) asset = LeftArea->GetFirstDataPtr()->Asset;//LeftArea의 첫번째 데이터 선택 FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), EditorName, layout, true, true, asset); //DetailsView->SetObject(asset);//어느 DetailView든 asset객체가 세팅된다. 창은 여러개지만 실제 관리는 내부적으로 하나로 관리한다. 그래서 창이 종료될 때 DetailsView가 해제 안 되어 있으면 터진다. LeftArea->SelectDataPtr(asset); } bool FWeaponAssetEditor::OnRequestClose() { if (!!DetailsView) { //AssetEditorSubsystem안에(=DetailView 안에) //GetEditingObject()가 등록되어 있었다면 해제하고 Editor에 알린다. if (!!GEditor && !!GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(GetEditingObject(), this); //해당 모듈이 읽힌적이 있다면 해제. if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) { FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");//해당모듈을 가져온다. prop.UnregisterCustomClassLayout("EquipmentData");//등록 해제 prop.UnregisterCustomClassLayout("DoActionData");//등록 해제 prop.UnregisterCustomClassLayout("HitData");//등록 해제 } } if (LeftArea.IsValid()) LeftArea.Reset(); if (DetailsView.IsValid()) DetailsView.Reset(); return true; //false 리턴인 경우 창이 닫힐 수 없다는 것을 의미한다. } void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) { FAssetEditorToolkit::RegisterTabSpawners(InTabManager); FOnSpawnTab tab; tab.BindSP(this, &FWeaponAssetEditor::Spawn_LeftAreaTab); TabManager->RegisterTabSpawner(LeftAreaTabId, tab); FOnSpawnTab tab2; tab2.BindSP(this, &FWeaponAssetEditor::Spawn_DetailsViewTab); TabManager->RegisterTabSpawner(DetailTabId, tab2); } TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_LeftAreaTab(const FSpawnTabArgs& InArgs) { //TSharedPtr<SDockTab> tab = SNew(SDockTab) //[ // SNew(SButton) // .OnClicked(this, &FWeaponAssetEditor::OnClicked)//OnClicked 함수 연결 // [ // SNew(STextBlock) // .Text(FText::FromString("Test")) // ] //]; //return tab.ToSharedRef(); return SNew(SDockTab) [ LeftArea.ToSharedRef() ]; } TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_DetailsViewTab(const FSpawnTabArgs& InArgs) { return SNew(SDockTab) [ DetailsView.ToSharedRef() ]; } void FWeaponAssetEditor::OnListViewSelectedItem(FWeaponRowDataPtr InDataPtr) { if (InDataPtr == nullptr)//LeftArea에서 선택한게 없다면(또는 빈 공간을 선택했다면) return; if (!!GetEditingObject())//편집하는 객체가 있다면 RemoveEditingObject(GetEditingObject());//현재 창에서 편집중인 애들 제거한다. AddEditingObject(InDataPtr->Asset);//현재 창에 편집해줄 객체를 등록해준다. DetailsView->SetObject(InDataPtr->Asset);//창 안의 DetailsView도 변경해준다. } FName FWeaponAssetEditor::GetToolkitFName() const { return EditorName;//외부에서 어떤 에디터 이름을 쓸 지 정해준다. } FText FWeaponAssetEditor::GetBaseToolkitName() const { return FText::FromName(EditorName);//외부에서 어떤 에디터 이름을 쓸 지 정해준다. } FString FWeaponAssetEditor::GetWorldCentricTabPrefix() const { return EditorName.ToString();////외부에서 어떤 식별자를 쓸 지 정해준다. } FLinearColor FWeaponAssetEditor::GetWorldCentricTabColorScale() const { return FLinearColor(0, 0, 1);//파란색으로 설정. } FReply FWeaponAssetEditor::OnClicked() { GLog->Log("Test"); return FReply::Handled();//Handled()는 처리하고 끝낸다. }
헤더 추가
- #include "SWeaponHitData.h"
HitData 등록: instance를 delegate 등록
- FOnGetPropertyTypeCustomizationInstance instance;
instance.BindStatic(&SWeaponHitData::MakeInstance);
prop.RegisterCustomPropertyTypeLayout("HitData", instance);
bool FWeaponAssetEditor::OnRequestClose()에 HItData 해제
- if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) {
FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
prop.UnregisterCustomClassLayout("HitData"); //등록 해제 }
void FWeaponAssetEditor::OpenWindow(FString InAssetName)
WeaponContextMenu
WeaponContextMenu.cpp
#pragma once #include "CoreMinimal.h" #include "AssetTypeActions_Base.h" #include "AssetTypeCategories.h" //_Base 붙는것은 Abstract 지칭하는 네이밍컨벤션 class WEAPON_API FWeaponContextMenu : public FAssetTypeActions_Base { public: FWeaponContextMenu(EAssetTypeCategories::Type InCategory); public: virtual FText GetName() const override; virtual UClass* GetSupportedClass() const override; virtual FColor GetTypeColor() const override; uint32 GetCategories() override; void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override; private: EAssetTypeCategories::Type Category; };
함수 추가
- void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects,
TSharedPtr<IToolkitHost> EditWithinLevelEditor)
WeaponContextMenu.cpp
#include "WeaponContextMenu.h" #include "WeaponAssetEditor.h" #include "Weapons/CWeaponAsset.h" FWeaponContextMenu::FWeaponContextMenu(EAssetTypeCategories::Type InCategory) { Category = InCategory; } FText FWeaponContextMenu::GetName() const { return FText::FromString("DataAsset"); } UClass* FWeaponContextMenu::GetSupportedClass() const { return UCWeaponAsset::StaticClass();//factory가 동작할 클래스로 리턴. } FColor FWeaponContextMenu::GetTypeColor() const { return FColor::Blue; } uint32 FWeaponContextMenu::GetCategories() { return Category;//만든 것이 소속될 (우클릭해서 나오는)카테고리를 리턴. } void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor) { /*for (UObject* obj : InObjects) { if (!!obj) GLog->Log(obj->GetName()); }*/ //위에서 타입을 "DataAsset"으로만 적용해서 DataAsset만 작동한다. if (InObjects.Num() < 1)//1 미만으로 선택되었다면(=선택된게 없다면) return; FWeaponAssetEditor::OpenWindow(InObjects[0]->GetName());//선택된것에 이름 설정해준다. 하나만 선택되었다는 전제(더블클릭해서 열기 때문). }
헤더 추가
- #include "WeaponAssetEditor.h"
함수 정의
- void FWeaponContextMenu::OpenAssetEditor(const TArray<UObject*>& InObjects,
TSharedPtr<IToolkitHost> EditWithinLevelEditor)
SWeaponLeftArea
SWeaponLeftArea.h
#pragma once #include "CoreMinimal.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/Views/STableRow.h" struct FWeaponRowData { int Number; FString Name; class UCWeaponAsset* Asset; FWeaponRowData() { } FWeaponRowData(int32 InNumber, FString InName, class UCWeaponAsset* InAsset) : Number(InNumber), Name(InName), Asset(InAsset) { } static TSharedPtr<FWeaponRowData> Make(int32 InNumber, FString InName, class UCWeaponAsset* InAsset) { return MakeShareable(new FWeaponRowData(InNumber, InName, InAsset)); } }; typedef TSharedPtr<FWeaponRowData> FWeaponRowDataPtr;//실제 출력할 자료형 /////////////////////////////////////////////////////////////////////////////// //한줄에 여러개 SMultiColumnTableRow class WEAPON_API SWeaponTableRow : public SMultiColumnTableRow<FWeaponRowDataPtr> { public: SLATE_BEGIN_ARGS(SWeaponTableRow) { }//{ }는 초기화영역 SLATE_ARGUMENT(FWeaponRowDataPtr, RowData) SLATE_END_ARGS() public: void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTable); protected: TSharedRef<SWidget> GenerateWidgetForColumn(const FName& InColumnName) override;//STableRow.h에서 상속받아 오버라이드. private: FWeaponRowDataPtr Data;//실제로 받아서 쓸 자료형 FWeaponRowDataPtr의 변수 Data. }; //이벤트용 델리게이트. FWeaponRowDataPtr이 눌렸을때 호출. DECLARE_DELEGATE_OneParam(FOnWeaponListViewSelectedItem, FWeaponRowDataPtr) class WEAPON_API SWeaponLeftArea : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SWeaponLeftArea) {} SLATE_EVENT(FOnWeaponListViewSelectedItem, OnSelectedItem) SLATE_END_ARGS() public: void Construct(const FArguments& InArgs); public: bool HasRowDataPtr() { return RowDatas.Num() > 0; }//하나라도 데이터 있는지 체크 FWeaponRowDataPtr GetFirstDataPtr() { return RowDatas[0]; }//첫번째 데이터 리턴 void SelectDataPtr(class UCWeaponAsset* InAsset); FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName); FString SelectedRowDataPtrName(); private: TSharedRef<ITableRow> OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable); void OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType); FText OnGetAssetCount() const; void OnTextChanged(const FText& InText);//입력한 텍스트가 들어와 바뀐다. void OnTextCommitted(const FText& InText, ETextCommit::Type InType);//입력완료(enter 등)시켰을 때 호출되는 이벤트. private: void ReadDataAssetList(); private: FOnWeaponListViewSelectedItem OnListViewSelectedItem; private: TArray<FWeaponRowDataPtr> RowDatas;//데이터 자료형을 받는 배열변수 TSharedPtr<SListView<FWeaponRowDataPtr>> ListView; private: TSharedPtr<class SSearchBox> SearchBox; FText SearchText;//검색 문자열을 관리할 변수 };
함수 추가
- FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName);
- TArray<FWeaponRowDataPtr> RowDataPtr 변수를 사용하기 위해 만든 함수.
- FString SelectedRowDataPtrName();
- 선택된 이름을 반환하는 함수를 만든다.
SWeaponLeftArea.cpp
#include "SWeaponLeftArea.h" #include "Weapons/CWeaponAsset.h" #include "EngineUtils.h" #include "Widgets/Input/SSearchBox.h" void SWeaponTableRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTable) { Data = InArgs._RowData;//InArgs를 통해서 들어온다. SMultiColumnTableRow<FWeaponRowDataPtr>::Construct ( FSuperRowType::FArguments().Style(FEditorStyle::Get(), "TableView.DarkRow"), InOwnerTable ); } TSharedRef<SWidget> SWeaponTableRow::GenerateWidgetForColumn(const FName& InColumnName) { //InColumnName에 아래 Construct 내부의 SHeaderRow::Column("이름, 번호 등")이 들어간다. FString str; if (InColumnName == "Number")//InColumnName이 숫자면 str = FString::FromInt(Data->Number);//str에 숫자를 넣어준다. else if (InColumnName == "Name")//InColumnName이 이름이면 str = Data->Name;//str에 이름을 넣어준다. return SNew(STextBlock) .Text(FText::FromString(str));//출력할 문자str를 생성하여 리턴해준다. } /*모양을 디자인 해주는 역할*/ void SWeaponLeftArea::Construct(const FArguments& InArgs) { OnListViewSelectedItem = InArgs._OnSelectedItem;//InArgs안의 OnSelectionItem 사용. ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(2, 0) [ SAssignNew(SearchBox, SSearchBox)//SearchBox를 가져와 동적할당하여 사용한다. .SelectAllTextWhenFocused(true)//검색창 클릭 시 텍스트 입력되있어도 자동으로 입력되게 해주는 기능. .OnTextChanged(this, &SWeaponLeftArea::OnTextChanged) .OnTextCommitted(this, &SWeaponLeftArea::OnTextCommitted) ] + SVerticalBox::Slot() .FillHeight(1)//1이 100%를 의미한다. 한줄을 다 채우겠다는 의미. [ SAssignNew(ListView, SListView<FWeaponRowDataPtr>)//ListView를 가져와 동적할당하여 사용한다. 자료형은 FWeaponRowDataPtr .HeaderRow ( SNew(SHeaderRow) + SHeaderRow::Column("Number") .DefaultLabel(FText::FromString("")) .ManualWidth(40)//칸의 너비 + SHeaderRow::Column("Name") .DefaultLabel(FText::FromString("Name")) ) .ListItemsSource(&RowDatas) .OnGenerateRow(this, &SWeaponLeftArea::OnGenerateRow)//한줄한줄 어떻게 표현할지 모양을 정해달라는 의미. .OnSelectionChanged(this, &SWeaponLeftArea::OnSelectionChanged) .SelectionMode(ESelectionMode::Single) ] +SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) .HAlign(HAlign_Right) .Padding(FMargin(8, 2)) [ SNew(STextBlock) .Text(this, &SWeaponLeftArea::OnGetAssetCount)//const TAttribute<FText>. 여기의 text 내용이 바뀌면 자동으로 갱신해준다. Attribute의 특성 때문이다. Attribute를 함수로 줄 수 있다. 여기서는 &SWeaponLeftArea::OnGetAssetCount. ] ]; ReadDataAssetList(); } void SWeaponLeftArea::SelectDataPtr(UCWeaponAsset* InAsset) { if (HasRowDataPtr() == false) return; for (FWeaponRowDataPtr ptr : RowDatas) { if (ptr->Asset == InAsset) { ListView->SetSelection(ptr); return; } } } FWeaponRowDataPtr SWeaponLeftArea::GetRowDataPtrByName(FString InAssetName) { for (FWeaponRowDataPtr ptr : RowDatas)//RowDatas를 모두 탐색했을때 { if (ptr->Name == InAssetName)//ptr의 이름이 받은 InAssetName이 같다면 return ptr;//그 데이터인 ptr를 리턴. } return nullptr; } FString SWeaponLeftArea::SelectedRowDataPtrName() { TArray<FWeaponRowDataPtr> ptrs = ListView->GetSelectedItems(); if (ptrs.Num() > 0)//선택된게 있을때 return ptrs[0]->Asset->GetName();//선택된 것의 이름을 리턴 return "";//빈 문자열 리턴 } TSharedRef<ITableRow> SWeaponLeftArea::OnGenerateRow(FWeaponRowDataPtr InRow, const TSharedRef<STableViewBase>& InTable) { /*실제모양을 만들어 리턴*/ return SNew(SWeaponTableRow, InTable) .RowData(InRow); } void SWeaponLeftArea::ReadDataAssetList() { RowDatas.Empty();//데이터를 불러오기 전에 비워주고 시작. //"/Game"은 콘텐츠 폴더. 모든 에셋들을 찾아 objects에 리턴하겠다. ///ATL_Class는 클래스 타입, ATL_Regular는 나머지 타입(에셋을 불러와야 하므로 이것 사용). TArray<UObject*> objects; EngineUtils::FindOrLoadAssetsByPath("/Game/Weapons/", objects, EngineUtils::ATL_Regular); int32 index = 0; for (UObject* obj : objects) { UCWeaponAsset* asset = Cast<UCWeaponAsset>(obj); if (asset == nullptr) continue; FString name = asset->GetName(); if (SearchText.IsEmpty() == false)//검색하는 문자열(=SearchText)이 비워져있지 않다면(=있다면) { //에셋에 SearchText 문자열이 포함되는지 확인. 포함되지 않는다면 보여줄게 아니기 때문에 continue; if (name.Contains(SearchText.ToString()) == false) continue; } RowDatas.Add(FWeaponRowData::Make(++index, name, asset));//데이터를 하나씩 추가해준다. } RowDatas.Sort([](const FWeaponRowDataPtr& A, const FWeaponRowDataPtr& B) { return A->Number < B->Number;//오름차순 정렬 }); ListView->RequestListRefresh();//다른곳에서 사용하기 때문에 재갱신를 해준다. } FText SWeaponLeftArea::OnGetAssetCount() const { FString str = FString::Printf(L"%d 에셋", RowDatas.Num()); return FText::FromString(str); } void SWeaponLeftArea::OnTextChanged(const FText& InText) { if (SearchText.CompareToCaseIgnored(InText) == 0)//기존 문자열과 현재 입력된 문자열이 같다면 return;//리턴으로 끝내버린다. SearchText = InText; ReadDataAssetList();//재검색하도록 ReadDataAssetList()를 콜 한다. } void SWeaponLeftArea::OnTextCommitted(const FText& InText, ETextCommit::Type InType) { OnTextChanged(InText); } void SWeaponLeftArea::OnSelectionChanged(FWeaponRowDataPtr InDataPtr, ESelectInfo::Type InType) { //InDataPtr로 선택된 데이터가 들어온다. //주의사항: 빈 구역을 클릭해도 InDataPtr의 데이터가 들어온다. 빈 구역 클릭 시 null로 값이 들어온다. if (InDataPtr.IsValid() == false) return; OnListViewSelectedItem.ExecuteIfBound(InDataPtr); }
함수 정의
- FWeaponRowDataPtr GetRowDataPtrByName(FString InAssetName);
- TArray<FWeaponRowDataPtr> RowDataPtr 변수를 사용하기 위해 만든 함수.
- FString SelectedRowDataPtrName();
실행화면

플러그인 내 Effect Location 정보가 제대로 나오지 않는 문제해결
Effect Location 정보가 나오지 않음.

Effect Location 정보가 나오지 않음.
SWeaponCheckBoxes
SWeaponCheckBoxes.h
변동사항 없음.
SWeaponCheckBoxes.cpp
#include "SWeaponCheckBoxes.h" #include "WeaponStyle.h" #include "SWeaponDetailsView.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "IPropertyUtilities.h" #include "IDetailPropertyRow.h" #include "IDetailChildrenBuilder.h" #include "DetailWidgetRow.h" void SWeaponCheckBoxes::AddProperties(TSharedPtr<IPropertyHandle> InHandle) { uint32 number = 0; InHandle->GetNumChildren(number);//핸들 내의 자식 property가 몇 개 있는지 가져온다. for (uint32 i = 0; i < number; i++) InternalDatas.Add(FInternalData(InHandle->GetChildHandle(i))); } TSharedRef<SWidget> SWeaponCheckBoxes::Draw(bool bBackground) { TSharedPtr<SUniformGridPanel> panel; SAssignNew(panel, SUniformGridPanel);//SNew와 다르게 변수를 선언해 놓는 SAssignNew panel->SetMinDesiredSlotWidth(150);//최소폭 150으로 설정. for (int32 i = 0; i < InternalDatas.Num(); i++) { panel->AddSlot(i, 0)//한줄만 사용, 여러줄사용하려면 ex) i % 5 [ SNew(SCheckBox)//체크박스는 컨텐츠 영역을 가지고 있다. .IsChecked(InternalDatas[i].bChecked)//체크 여부 출력, 0과 1로만 판단 .OnCheckStateChanged(this, &SWeaponCheckBoxes::OnCheckStateChanged, i)//체크 했는지 판단 [ SNew(STextBlock) .Text(FText::FromString(InternalDatas[i].Name))//InternalDatas의 이름들 출력 ] ]; } if(bBackground == false) return panel.ToSharedRef();//추가를 하게되면, 추가된 그자체를 return TSharedPtr<SBorder> border = SNew(SBorder)//한 줄 생성. .BorderImage(FWeaponStyle::Get()->Array_Image.Get()) [ panel.ToSharedRef()//panel를 컨텐츠 영역으로 넣어준다. ]; return border.ToSharedRef();//border를 리턴. } void SWeaponCheckBoxes::DrawProperties(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder* InChildrenBuilder) { for (int32 i = 0; i < InternalDatas.Num(); i++) { // 체크박스에 체크를 바꾸게 되는지 확인, 바꾸지 안았다면 그릴 필요가 없다. if (InternalDatas[i].bChecked == false)//그릴 필요가 없는 경우 continue; // 각 줄에 식별자 TSharedPtr<IPropertyHandle> handle = InPropertyHandle->GetChildHandle(i); // 자식부분을 담당, 이 Handle이 기본모양을 추가해서 만들어준다. 커스텀 마이징도 가능하다 IDetailPropertyRow& row = InChildrenBuilder->AddProperty(handle.ToSharedRef()); //초기세팅1//FString name = FString("Name ") + FString::FromInt(i + 1); TSharedPtr<SWidget> name; TSharedPtr<SWidget> value; row.GetDefaultWidgets(name, value); row.CustomWidget() .NameContent() [ //초기세팅1//handle->CreatePropertyNameWidget() name.ToSharedRef() ] //줄이거나 늘렸을 때 Min 이하로는 고정. Max 이상으로는 고정. .ValueContent() .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X) .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y) [ //초기세팅1//handle->CreatePropertyValueWidget() value.ToSharedRef() ]; } } void SWeaponCheckBoxes::SetUtilities(TSharedPtr<IPropertyUtilities> InUtilities) { //외부 객체 받아오기 Utilities = InUtilities; } void SWeaponCheckBoxes::OnCheckStateChanged(ECheckBoxState InState, int32 InIndex) { //상태를 바뀌는걸 확인했기때문에, 값을 뒤집어 줘서 다시 안그려지게 만들어준다. InternalDatas[InIndex].bChecked = !InternalDatas[InIndex].bChecked; SWeaponDetailsView::OnRefreshByCheckBoxes(); { // ForceRefresh이 콜이되면 새로고침이 되면서 다시 그려진다. Utilities->ForceRefresh(); } SWeaponDetailsView::OffRefreshByCheckBoxes(); } bool SWeaponCheckBoxes::CanDraw(TSharedPtr<IPropertyHandle> InHandle, int InCount) { bool bCheck = true; bCheck &= InCount > 0;//배열의 개수 InCount가 0보다 큰지 int32 index = InHandle->GetIndexInArray();//InHandle에서 배열 인덱스번호를 가져온다. bCheck &= index >= 0; bCheck &= index < InCount;//인덱스가 InCount 배열의 개수보다 작은지//크면 범위를 벗어나서 그릴 수 없다. return bCheck; } void SWeaponCheckBoxes::CheckDefaultObject(int32 InIndex, UObject* InValue) { UObject* val = nullptr;//기본값 InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값 if (!!val && InValue != val)//nullptr이거나 선택한 값이랑 기본값이 다르다면 InternalDatas[InIndex].bChecked = true; } void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, float InValue) { float val = 0.0f;//기본값 InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값 if (InValue != val)//선택한 값이랑 기본값이 다르다면 InternalDatas[InIndex].bChecked = true; } void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, bool InValue) { bool val = false;//기본값 InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값 if (InValue != val)//선택한 값이랑 기본값이 다르다면 InternalDatas[InIndex].bChecked = true; } void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, const FVector& InValue) { FVector val = FVector::ZeroVector;//기본값 InternalDatas[InIndex].Handle->GetValue(val);//해당 Property에 선택한 값 if (InValue != val)//선택한 값이랑 기본값이 다르다면 InternalDatas[InIndex].bChecked = true; }
초기세팅: 테스팅용
- FString name = FString("Name ") + FString::FromInt(i + 1);
- .NameContent()
[
handle->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
.MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
[
handle->CreatePropertyValueWidget()
]; - CreatePropertyNameWidget()은 기본 자료형만 지원할뿐 다른 자료형은 지원하지 않음.
수정 후
- TSharedPtr<SWidget> name;
- TSharedPtr<SWidget> value;
- row.GetDefaultWidgets(name, value);
- GetDefaultWidgets는 언리얼이 띄워주는 자료형에 대한 기본모양을 리턴해준다.
- GetDefaultWidgets는 언리얼이 띄워주는 자료형에 대한 기본모양을 리턴해준다.
- row.CustomWidget()
.NameContent()
[
name.ToSharedRef()
]
//줄이거나 늘렸을 때 Min 이하로는 고정. Max 이상으로는 고정.
.ValueContent()
.MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
.MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
[
value.ToSharedRef()
];
실행화면

'⭐ Unreal Engine > UE Plugin - Slate UI' 카테고리의 다른 글
[UE] Weapon Plugin 9: DoAction Data 연동하기 II (0) | 2023.06.14 |
---|---|
[UE] Weapon Plugin 8: DoAction Data 연동하기 (0) | 2023.06.13 |
[UE] Weapon Plugin 7: Check Box와 데이터 연결하기 (0) | 2023.06.12 |
[UE] Weapon Plugin 6: 체크박스 만들기 (0) | 2023.06.08 |
[UE] Weapon Plugin 5: 창 내부 Header, Child 구분하기 (0) | 2023.06.07 |
댓글
이 글 공유하기
다른 글
-
[UE] Weapon Plugin 9: DoAction Data 연동하기 II
[UE] Weapon Plugin 9: DoAction Data 연동하기 II
2023.06.14 -
[UE] Weapon Plugin 8: DoAction Data 연동하기
[UE] Weapon Plugin 8: DoAction Data 연동하기
2023.06.13 -
[UE] Weapon Plugin 7: Check Box와 데이터 연결하기
[UE] Weapon Plugin 7: Check Box와 데이터 연결하기
2023.06.12목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cppSWeaponDetailsView.h .cppSWeaponEquipmentData.h .cppSWeaponLeftArea.h .cppWeapon.Build.csWeaponAssetEditor.h .cppWeaponAssetFactory.h .cppWeaponCommand.h .cpp WeaponContextMenu.h .cppWeaponModule.h .cppWeaponStyle.h .cpp CheckBox와 데이터 연결하기 일반적인 프로그래밍과 게임 프로그래밍의 차이 일반적인… -
[UE] Weapon Plugin 6: 체크박스 만들기
[UE] Weapon Plugin 6: 체크박스 만들기
2023.06.08목차 Plugins Weapon Resource Icon128.pngweapon_thumbnail_icon.png Source Weapon SWeaponCheckBoxes.h .cpp 생성SWeaponDetailsView.h .cppSWeaponEquipmentData.h .cppSWeaponLeftArea.h .cppWeapon.Build.csWeaponAssetEditor.h .cppWeaponAssetFactory.h .cppWeaponCommand.h .cpp WeaponContextMenu.h .cppWeaponModule.h .cppWeaponStyle.h .cpp 체크박스 만들기 SWeaponCheckBoxes 생성 새 C++ 클래스 - 없음 …
댓글을 사용할 수 없습니다.