목차

     

     


     

     

     
    Plugins
     
      Weapon
     
        Resource
     
          Icon128.png
    weapon_thumbnail_icon.png
     
        Source
     
          Weapon  
          SWeaponCheckBoxes.h .cpp 생성
    SWeaponDetailsView.h .cpp

    SWeaponEquipmentData.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 
     
       

     

     

     

    체크박스 만들기

     

     


     

     

    SWeaponCheckBoxes 생성

     

    새 C++ 클래스 - 없음 - SWeaponCheckBoxes 생성

     

     

    SWeaponCheckBoxes.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    
    class WEAPON_API SWeaponCheckBoxes
    	: public TSharedFromThis<SWeaponCheckBoxes>
    {
    public:
    	//IPropertyHandle는 하나의 Property에 대한 식별자
    	void AddProperties(TSharedPtr<IPropertyHandle> InHandle);
    
    	TSharedRef<SWidget> Draw(bool bBackground = false);
    
    private:
    	void OnCheckStateChanged(ECheckBoxState InState, int32 InIndex);//CheckBox의 true, false를 바꾸어주는 함수
    
    private:
    	//내부 구조체
    	struct FInternalData
    	{
    		bool bChecked;//체크 되었는가
    		FString Name;//이름
    		TSharedPtr<IPropertyHandle> Handle;//식별자
    
    		FInternalData(TSharedPtr<IPropertyHandle> InHandle)
    		{
    			bChecked = false;//기본값은 false로 설정.
    			Handle = InHandle;
    
    			Name = Handle->GetPropertyDisplayName().ToString();//핸들 내의 DisplayName을 출력이름으로 설정.
    		}
    	};
    	TArray<FInternalData> InternalDatas;//구조체 Data 전부를 포괄한 배열변수
    };

    : public TSharedFromThis<SWeaponCheckBoxes>으로부터 상속받는 이유는? 

    • 스마트 포인터 내부에서 this를 참조해야 되는 경우 다음과 같이 상속받는다.
      • TSharedFromThis<자기 자신>
      • 자기 자신을 명시해서 상속받는다.
      • 위의 내용 중요!!
    • 아래에 정리한 ' TSharedPtr 사용 시 발생하는 침범형 접근자(Intrusive Accessors) 문제 ' 참고.

     

     

     

    SWeaponCheckBoxes.cpp

    더보기
    #include "SWeaponCheckBoxes.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);
    	panel->SetMinDesiredSlotWidth(150);//최소폭 150으로 설정.
    
    	for(int32 i=0; i< InternalDatas.Num(); i++)
    	{
    		//그려서 넣는 부분
    		panel->AddSlot(i, 0)//한줄로 넣는다.
    		[
    			SNew(SCheckBox)
    			.IsChecked(InternalDatas[i].bChecked)
    			.OnCheckStateChanged(this, &SWeaponCheckBoxes::OnCheckStateChanged, i)//i는 가변 파라미터
    			[
    				SNew(STextBlock)
    				.Text(FText::FromString(InternalDatas[i].Name))//InternalDatas의 이름들 출력
    			]
    		];
    	}
    
    	return panel.ToSharedRef();
    }
    
    void SWeaponCheckBoxes::OnCheckStateChanged(ECheckBoxState InState, int32 InIndex)
    {
    	GLog->Log(FString::FromInt(InIndex));
    	GLog->Log(StaticEnum<ECheckBoxState>()->GetValueAsString(InState));
    }

     

     

     

     

    ※ 체크박스 내의 체크박스 영역과 콘텐츠 영역

    panel->AddSlot(i, 0)//한줄로 넣는다.
    [
        SNew(SCheckBox)
        .IsChecked(InternalDatas[i].bChecked)
        .OnCheckStateChanged(this, &SWeaponCheckBoxes::OnCheckStateChanged, i)//가변 파라미터?
        [
            SNew(STextBlock)
            .Text(FText::FromString(InternalDatas[i].Name))//InternalDatas의 이름들 출력
        ]
    ];

     

    체크박스 영역

    • InternalDatas의  bChecked 들을 출력한다.
    • OnCheckStateChanged를 사용하여 체크 유무를 변화시켜 준다.

     

     

    콘텐츠 영역

    • 체크박스는 내용을 출력해주기 위해 콘텐츠 영역을 같이 가지고 있다.
    • 위의 코드 중 아래부분이 콘텐츠 영역에 들어갈 Text를 넣어주는 부분이다.

     


     

     

    SWeaponEquipmentData

     

    SWeaponEquipmentData.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "IPropertyTypeCustomization.h"
    
    class WEAPON_API SWeaponEquipmentData
    	: public IPropertyTypeCustomization
    {
    public:
    	static TSharedRef<IPropertyTypeCustomization> MakeInstance();
    	static TSharedPtr<class SWeaponCheckBoxes> CreateCheckBoxes();
    
    	void CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils) override;
    
    	void CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils) override;
    
    private:
    	static TSharedPtr<class SWeaponCheckBoxes> CheckBoxes;
    };

     

     

     

    SWeaponEquipmentData.cpp

    더보기
    #include "SWeaponEquipmentData.h"
    #include "IPropertyUtilities.h"
    #include "IDetailPropertyRow.h"
    #include "IDetailChildrenBuilder.h"
    #include "SWeaponCheckBoxes.h"
    #include "DetailWidgetRow.h"
    
    TSharedPtr<SWeaponCheckBoxes> SWeaponEquipmentData::CheckBoxes;//초기화
    
    TSharedRef<IPropertyTypeCustomization> SWeaponEquipmentData::MakeInstance()
    {
    	//자신의 클래스 타입을 만들어서 return해준다.
    	return MakeShareable(new SWeaponEquipmentData());
    }
    
    TSharedPtr<SWeaponCheckBoxes> SWeaponEquipmentData::CreateCheckBoxes()
    {
    	if (CheckBoxes.IsValid())
    	{
    		CheckBoxes.Reset();
    		CheckBoxes = nullptr;
    	}
    
    	return CheckBoxes = MakeShareable(new SWeaponCheckBoxes());
    }
    
    void SWeaponEquipmentData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	InHeaderRow
    	.NameContent()
    	[
    		InPropertyHandle->CreatePropertyNameWidget()
    	]
    	.ValueContent()
    	.MinDesiredWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
    	.MaxDesiredWidth(FEditorStyle::GetFloat("StandardDialog.MaxDesiredSlotWidth"))
    	[
    		CheckBoxes->Draw()//CheckBoxes를 그려준다(=생성한다).
    	];
    }
    
    void SWeaponEquipmentData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils)
    {
    	uint32 number = 0;
    	InPropertyHandle->GetNumChildren(number);
    
    	for(uint32 i = 0; i < number; i++)
    	{
    		TSharedPtr<IPropertyHandle> handle = InPropertyHandle->GetChildHandle(i);
    		IDetailPropertyRow& row = InChildBuilder.AddProperty(handle.ToSharedRef());//handle를 가지고 기본모양을 추가하여 만들어준다. 
    		
    		row.CustomWidget()
    		.NameContent()
    		[
    			handle->CreatePropertyNameWidget()
    		]
    		//줄이거나 늘렸을 때 Min 이하로는 고정. Max 이상으로는 고정.
    		.ValueContent()
    		.MinDesiredWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
    		.MaxDesiredWidth(FEditorStyle::GetFloat("StandardDialog.MaxDesiredSlotWidth"))
    		[
    			handle->CreatePropertyValueWidget()
    		];
    	}
    }

    void SWeaponEquipmentData::CustomizeHeader()에서 CheckBox를 생성한다.

    • CheckBoxes->Draw()

     

     

     

     


     

     

     

    SWeaponDetailsView

     

    SWeaponDetailsView.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "IDetailCustomization.h"
    
    class WEAPON_API SWeaponDetailsView
    	: public IDetailCustomization
    {
    public:
    	static TSharedRef<IDetailCustomization> MakeInstance();
    	void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
    };

    변경사항 없음.

     

     

    SWeaponDetailsView.cpp

    더보기
    #include "SWeaponDetailsView.h"
    #include "SWeaponCheckBoxes.h"
    #include "SWeaponEquipmentData.h"
    #include "DetailLayoutBuilder.h"
    #include "DetailCategoryBuilder.h"
    #include "IDetailPropertyRow.h"
    #include "Weapons/CWeaponAsset.h"
    
    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);
    
    		TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponEquipmentData::CreateCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다.
    		checkBoxes->AddProperties(row.GetPropertyHandle());//checkBoxes에 실제로 가진 Handle를 추가
    	}
    }

    void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)

    • EquipmentData를 넣는 위치에 Checkbox 데이터를 넣는다.
    • TSharedPtr<SWeaponCheckBoxescheckBoxes = SWeaponEquipmentData::CreateCheckBoxes();
      • 카테고리가 처음에 만들어질 때 checkBox를 만든다.
    • checkBoxes->AddProperties(row.GetPropertyHandle());
      • checkBoxes에 실제로 가진 Handle를 추가해준다. 

     


     

     

    IProperty 

     

    Unreal Engine에서 IPropertyHandle은 Unreal Editor의 속성 시스템에서 사용되는 인터페이스다. IPropertyHandle은 단일 속성 값을 나타내며, 특정 객체의 속성을 나타내는 데 사용된다.

    IPropertyHandle 인터페이스를 사용하여 속성의 값을 읽고 쓸 수 있으며, 속성에 대한 변경 사항을 추적하고 UI 컨트롤과 연결하여 속성 값을 편집할 수 있다. 이 인터페이스는 주로 Unreal Editor의 속성 창에서 사용되는 속성 에디터 위젯과 상호 작용하기 위해 사용된다.

    IPropertyHandle 인터페이스는 주로 UProperty와 함께 사용된다. UProperty는 Unreal Engine에서 객체의 속성을 정의하는 데 사용되는 Meta Data를 포함하는 클래스다. IPropertyHandle를 사용하면 UProperty와 관련된 값을 검색하고 설정할 수 있다.

    IPropertyHandle 사용예시

    // UProperty를 사용하여 IPropertyHandle를 만듭니다.
    UProperty* Property = FindSomeProperty();
    IPropertyHandle* Handle = Property->CreatePropertyHandle();
    
    // 속성 값 읽기
    FString Value;
    Handle->GetValue(Value);
    
    // 속성 값 설정
    FString NewValue = "Hello World";
    Handle->SetValue(NewValue);
    
    // 속성 값 변경 사항 추적
    Handle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([]() {
        // 속성 값이 변경되었을 때 실행될 코드
    }));

     

    IPropertyHandle은 Unreal Editor에서 속성 값을 읽고 쓰는 데 사용되는 강력한 도구다. 속성 시스템을 사용하여 사용자 정의 속성 에디터를 만들거나 객체의 속성을 동적으로 편집할 때 유용하게 사용할 수 있다.

     


     

    TSharedPtr 사용 시 발생하는 침범형 접근자(Intrusive Accessors) 문제

     

    TSharedPtr<____ > s = 

    • SharedPtr는 비침범형(non-intrusive)다. 외부에서 SharedPtr 내부의 직접 접근이 불가능하다.
    • 오브젝트가 스마트 포인터(SharedPtr 등등)의 소유 하에 있는지 알 수 없다.

    MakeShareable(new ____ )

     

    SCheckBox 로 하면 문제가 발생한다.

     

     

    AsShared

    • 함수에서 문제가 생긴다. 

     

    SharedThis

    • 함수에서 문제가 생긴다.
    • 공유하기 위해 사용하는 함수.

     

    쉐어된 this 포인터인지 체크해줘야 한다.   

     

    원본을 찾아가는 기능을 추가하기 위해 사용한다.

     

     

    침범형 접근자(Intrusive Accessors)
    쉐어드 포인터는 비침범형(non-intrusive)으로, 오브젝트가 스마트 포인터의 소유 하에 있는지 알 수 없다는 뜻입니다. 이런 속성은 일반적으로 문제가 없지만, 오브젝트를 쉐어드 레퍼런스 또는 쉐어드 포인터로서 접근하려는 경우가 있을 수도 있습니다. 이러한 경우에는, 오브젝트의 클래스를 템블릿 매개변수로 사용하여 TSharedFromThis`에서 오브젝트의 클래스를 파생시켜야 합니다. TSharedFromThis 는 두 가지 함수 AsShared 및 SharedThis`를 제공하며, 두 함수로 오브젝트를 쉐어드 레퍼런스로 변환하고, 쉐어드 레퍼런스를 또 쉐어드 포인터로 변환할 수 있습니다. 이는 항상 쉐어드 레퍼런스를 반환하는 클래스 팩토리나 쉐어드 레퍼런스 또는 쉐어드 포인터를 요구하는 시스템에 오브젝트를 넣을 때 특히나 유용합니다. AsShared`는 호출되는 오브젝트의 페어런트 타입일 수 있는 TSharedFromThis`에 템플릿 아규먼트로서 전달된 본래 타입의 클래스를 반환하는 동시에 ‘SharedThis'는 ‘this'에서 타입을 직접 파생키시고 해당 타입의 오브젝트를 참조하는 스마트 포인터를 반환합니다. 다음은 두 함수의 사용 방법을 보여주는 예제 코드입니다:

     

    출처:  https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/SmartPointerLibrary/

     

    언리얼 스마트 포인터 라이브러리

    위크 포인터 및 Null이 불가능한(non-nullable) 쉐어드 레퍼런스와 같은 쉐어드 포인터들의 커스텀 구현입니다.

    docs.unrealengine.com

     


     

     

    실행화면