[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 -
[UE] Weapon Plugin 6: 체크박스 만들기
[UE] Weapon Plugin 6: 체크박스 만들기
2023.06.08