초보 코린이의 성장 일지

UE4 Weapon Plugin (8) 본문

언리얼

UE4 Weapon Plugin (8)

코오린이 2023. 6. 13. 14:11

기본값에서 변경된 내용을 보여주게 만들어 줄 것이다.

GetValue를 보면 24가지 자료형이 override가 되어있다. 사용하게 될 자료형 몇개만 추려서 선언해 놓고 시작할 것이다.


더보기
#pragma once

#include "CoreMinimal.h"

class WEAPON_API SWeaponCheckBoxes
    : public TSharedFromThis<SWeaponCheckBoxes> // 직접적으로 상속받으면 주소가 일치하게된다.
{

public:
    void CheckDefaultObject(int32 InIndex, UObject* InValue); // float로 cast가 될수도 있기 때문에 UObject로 객체 구분
    void CheckDefaultValue(int32 InIndex, float InValue);
    void CheckDefaultValue(int32 InIndex, bool InValue);
    void CheckDefaultValue(int32 InIndex, const FVector& InValue);

};
#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::CheckDefaultObject(int32 InIndex, UObject* InValue)
{
    UObject* val = nullptr;
    InternalDatas[InIndex].Handle->GetValue(val);

    if (!!val && InValue != val) // return 값이 null 일수도 있고, 선택한 값이 null이라면 기본값으로 간주할 것이므로 조건처리.
        InternalDatas[InIndex].bChecked = true;
}

void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, float InValue)
{
    float val = 0.0f;
    InternalDatas[InIndex].Handle->GetValue(val); // 선택한 값, 해당 정보 얻어오기

    if (InValue != val) // 기본값과 가져온 값이 다르다면 수정되었다는 의미이므로, true 만들어주기.
        InternalDatas[InIndex].bChecked = true;
}

void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, bool InValue)
{
    bool val = false;
    InternalDatas[InIndex].Handle->GetValue(val);

    if (InValue != val)
        InternalDatas[InIndex].bChecked = true;
}

void SWeaponCheckBoxes::CheckDefaultValue(int32 InIndex, const FVector& InValue)
{
    FVector val = FVector::ZeroVector;
    InternalDatas[InIndex].Handle->GetValue(val);

    if (InValue != val)
        InternalDatas[InIndex].bChecked = true;
}

1. 기본값과 선택된 값이 일치하는지 비교 후 조건을 처리해준다.

 


더보기
#include "SWeaponDetailsView.h"
#include "SWeaponCheckBoxes.h"
#include "SWeaponEquipmentData.h"
#include "SWeaponDoActionData.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "IDetailPropertyRow.h"
#include "Weapons/CWeaponAsset.h"

#include "Animation/AnimMontage.h"

void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
    // EquipmentData
    {
        IDetailCategoryBuilder& category = DetailBuilder.EditCategory("EquipmentData", FText::FromString("Equipment Data"));
        IDetailPropertyRow& row = category.AddProperty("EquipmentData", type);

        // 새로고침이 아닐때만 실행되도록, Properties로 인해 추가되므로 한번만 실행되게 해야한다.
        if (bRefreshByCheckBoxes == false)
        {
            // 처음에 만들어질때 CheckBox 생성
            TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponEquipmentData::CreateCheckBoxes();
            checkBoxes->AddProperties(row.GetPropertyHandle()); // 실제로 가진 Handle 추가해준다.

            FEquipmentData data; // 기본값

            int32 index = 0;
            // AddProperties에서 추가할때 변수 순서대로 들어가므로, 순서대로 작성해주면 알아서 처리가 된다.
            checkBoxes->CheckDefaultObject(index++, data.Montage);
            checkBoxes->CheckDefaultValue(index++, data.PlayRate);
            checkBoxes->CheckDefaultValue(index++, data.bCanMove);
            checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation);

        }
    }

1. 체크박스를 만들어 생성해주는 곳으로 가서 SWeaponCheckBoxes에서 받아온 정보값을 넣어준다.

2. 이미 정해놓은 순서대로 정보가 처리되어 들어갈 것이다.

1. Sword는 정보가 들어가 있기 때문에 변경되어서 보이지만, 나머지들은 기본값이 변경되지 않았기 때문에 데이터 정보가 뜨질 않는걸 확인할 수 있다.


더보기
#pragma once

#include "CoreMinimal.h"

class WEAPON_API FWeaponStyle
{
public:
	const FVector2D DesiredWidth = FVector2D(250, 1000); // min을 x, max를 y로 사용
};
#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::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());

        FString name = FString("Name ") + FString::FromInt(i + 1);

        row.CustomWidget()
            .NameContent()
            [
                handle->CreatePropertyNameWidget()
            ]
        .ValueContent()
            .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
            .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
            [
                handle->CreatePropertyValueWidget()
            ];
    }
}

1. 정보를 보여준 창에 크기가  작아서 조금 더 늘리게끔 코드를 변경해 줄 것이다.

2. MinDesiredWidth, MaxDesiredWidth를 FWeaponStyle에서 만들어 준 값으로 대처해준다.


이제는 Data 정보를 보여줄 때 다른 방법으로 보여주게 된다. 구조체를 정보를 배열로 담아서 보여주는 방법으로 접근할 것이며, 아주 어려운 방법이 될 수도 있다.

1. 클래스를 생성해준다.

1. 기본적인 틀은 위 사진처럼 SWeaponEquipmentData와 동일하고 안에 내용만 달라질 것이다.

2. 내용은 부가적인 부분을 다루고 작성해 볼 것이다.


지금까지 해왔던 EquipmentData는 접는 부분을 클릭해서 열어보면 카테고리에 체크박스들이 여러개 있고, 클릭했을시 접는 부분이 열리면서 해당 정보가 보이도록 구현되어있다. 코드로 나타내면 커스텀 마이징 가능한 카테고리 CustomizeHeader이 접는 부분, CustomizeChildren이 그 밑에 식별자로서 데이터를 나타내는 접는 부분이 된다. 이 두가지가 한번 호출됨으로써 보여지게 된다. 하지만 지금 구현하게 되는 방법은 Header, Children, Header, Children 이렇게 한번씩 호출됨으로써 보여지게 될 것이다.


더보기
#include "WeaponAssetEditor.h"
#include "SWeaponLeftArea.h"
#include "SWeaponDetailsView.h"
#include "SWeaponEquipmentData.h"
#include "SWeaponDoActionData.h"
#include "Weapons/CWeaponAsset.h"

void FWeaponAssetEditor::Open(FString InAssetName)
{
	// EquipmentData
	{
		FOnGetPropertyTypeCustomizationInstance instance;
		instance.BindStatic(&SWeaponEquipmentData::MakeInstance); // Type를 만들어서 return 해주기 때문에, BindStatic 사용
		// Editor에 등록 해준다. 변수화된 Type을 다룰것이기 때문에 RegisterCustomPropertyTypeLayout 사용,
		prop.RegisterCustomPropertyTypeLayout("EquipmentData", instance); // 편집할 변수

	}

	// DoActionData
	{
		FOnGetPropertyTypeCustomizationInstance instance;
		instance.BindStatic(&SWeaponDoActionData::MakeInstance); // Type를 만들어서 return 해주기 때문에, BindStatic 사용
		// Editor에 등록 해준다. 변수화된 Type을 다룰것이기 때문에 RegisterCustomPropertyTypeLayout 사용,
		prop.RegisterCustomPropertyTypeLayout("DoActionData", instance); // 편집할 변수

	}
}

bool FWeaponAssetEditor::OnRequestClose()
{
	if (!!DetailsView)
	{
		if (!!GEditor && !!GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
			// 디테일 뷰안에 객체가 등록되어있다면 해제된걸 알려줘라,
			GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(GetEditingObject(), this);

		// 해당 모듈이 읽힌적(불러진적)이 있는지 체크
		if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
		{
			// 등록해제를 안해주면, 다른곳에 등록된 곳에서도 유지되어 버린다. 그래서 창이 열려져서 닫힐 동안만큼은 내가 만든걸 화면에 띄어줄 것이다.
			FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
			prop.UnregisterCustomPropertyTypeLayout("EquipmentData");
			prop.UnregisterCustomPropertyTypeLayout("DoActionData");
		}
	}
}

1. WeaponAssetEditor로 와서 DoActionData를 등록해주고, 사용했으면 해제까지 해준다.


이제 보여지는 DoActionData를 보여줘야 하기 때문에 DetailsView에서 추가해준다.

더보기
#include "SWeaponDetailsView.h"
#include "SWeaponCheckBoxes.h"
#include "SWeaponEquipmentData.h"
#include "SWeaponDoActionData.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "IDetailPropertyRow.h"
#include "Weapons/CWeaponAsset.h"

#include "Animation/AnimMontage.h

void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
    // EquipmentData
    {
        IDetailCategoryBuilder& category = DetailBuilder.EditCategory("EquipmentData", FText::FromString("Equipment Data"));
        IDetailPropertyRow& row = category.AddProperty("EquipmentData", type);

        // 새로고침이 아닐때만 실행되도록, Properties로 인해 추가되므로 한번만 실행되게 해야한다.
        if (bRefreshByCheckBoxes == false)
        {
            // 처음에 만들어질때 CheckBox 생성
            TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponEquipmentData::CreateCheckBoxes();
            checkBoxes->AddProperties(row.GetPropertyHandle()); // 실제로 가진 Handle 추가해준다.

            FEquipmentData data; // 기본값

            int32 index = 0;
            // AddProperties에서 추가할때 변수 순서대로 들어가므로, 순서대로 작성해주면 알아서 처리가 된다.
            checkBoxes->CheckDefaultObject(index++, data.Montage);
            checkBoxes->CheckDefaultValue(index++, data.PlayRate);
            checkBoxes->CheckDefaultValue(index++, data.bCanMove);
            checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation);

        }
    }

    // DoActionData
    {
        IDetailCategoryBuilder& category = DetailBuilder.EditCategory("DoActionData", FText::FromString("DoAction Data"));
        IDetailPropertyRow& row = category.AddProperty("DoActionDatas", type);  // 변수명, WeaponAsset 있는 변수명과 일치 시켜주면 된다.
    }
}

1. EquipmentData처럼 구역을 나눠주고, 비슷한 맥락으로 작성해준다.

1. 데이터 출력 로그를 보면, Swerd는 3가지 DoAction 데이터가 있었으므로, Header, Chuldren이 3번 출력되고, Hammer은 4개의 데이터가 있으므로, 4번 출력되는걸 확인할 수 있다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "IPropertyTypeCustomization.h"

class WEAPON_API SWeaponDoActionData
	: 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; // 여러개 담을 배열로 선언

};
#include "SWeaponDoActionData.h"
#include "IPropertyUtilities.h"
#include "IDetailPropertyRow.h"
#include "IDetailChildrenBuilder.h"
#include "DetailWidgetRow.h"
#include "SWeaponCheckBoxes.h"

TArray<TSharedPtr<class SWeaponCheckBoxes>> SWeaponDoActionData::CheckBoxes; // Static 변수 초기화.

TSharedRef<IPropertyTypeCustomization> SWeaponDoActionData::MakeInstance()
{
    return MakeShareable(new SWeaponDoActionData());
}

TSharedPtr<SWeaponCheckBoxes> SWeaponDoActionData::AddCheckBoxes()
{
    TSharedPtr<SWeaponCheckBoxes> checkBoxes = MakeShareable(new SWeaponCheckBoxes()); // 체크박스 생성
    int32 index = CheckBoxes.Add(checkBoxes);

    return CheckBoxes[index]; // 현재 번호 return 

}

void SWeaponDoActionData::EmptyCheckBoxes()
{
    // 배열 돌면서 제거
    for (TSharedPtr<SWeaponCheckBoxes> ptr : CheckBoxes)
    {
        if (ptr.IsValid())
            ptr.Reset();
    }

    CheckBoxes.Empty();
}

void SWeaponDoActionData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils)
{
    GLog->Log("CustomizeHeader");

}

void SWeaponDoActionData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils)
{
    GLog->Log("CustomizeChildren");

}

1. Header에 체크박스를 넣었었다. Equipment는 Header이 하나뿐이여서 상관없지만, DoAction은 여러개가 나올 것이므로, Array 배열로 변경해준다.

2. Equipment에서는 정보가 들어오면, 이전꺼를 지우고 새로운걸 만들어 줬지만. DoAction은 한번에 비워줄 것이다.


더보기
#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:
	// 외부에서 바꿔줄 수 있도록 return 함수
	static void OnRefreshByCheckBoxes() { bRefreshByCheckBoxes = true; }
	static void OffRefreshByCheckBoxes() { bRefreshByCheckBoxes = false; }


private:
	static bool bRefreshByCheckBoxes;

};
#include "SWeaponDetailsView.h"
#include "SWeaponCheckBoxes.h"
#include "SWeaponEquipmentData.h"
#include "SWeaponDoActionData.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "IDetailPropertyRow.h"
#include "Weapons/CWeaponAsset.h"

#include "Animation/AnimMontage.h"

void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
  
    // DoActionData
    {
        IDetailCategoryBuilder& category = DetailBuilder.EditCategory("DoActionData", FText::FromString("DoAction Data"));
        IDetailPropertyRow& row = category.AddProperty("DoActionDatas", type); // 변수명, WeaponAsset 있는 변수명과 일치 시켜주면 된다.

        if (bRefreshByCheckBoxes == false) // 새로고침 됐는지 체크
        {
            uint32 count = 0;
            row.GetPropertyHandle()->GetNumChildren(count); // 배열의 개수 return,  ChildrenHandle는 배열에 담아져있다.

            SWeaponDoActionData::EmptyCheckBoxes(); // 처음추가 하는 것이므로 새롭게 열릴때 이전에 있는거 다 비워주고 추가.

            // 몇개인지 확인,
            FDoActionData data;
            for (uint32 i = 0; i < count; i++)
            {
                // 현재 HeaderHandle에서 자식꺼 Handle 가져오기
                TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);

                TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponDoActionData::AddCheckBoxes(); // checkBoxes 추가
                checkBoxes->AddProperties(handle); // 몇개의 정보가 남을지,

            }

        } // if (bRefreshByCheckBoxes)
    }
}

1. 배열로 선언하고, 안에 정보들을 받아서 돌려서 확인할 수 있게 만들어준다.

2. 이제 DoActionData로 가서 실제로 그려주면 된다.


https://www.youtube.com/watch?v=xp0X980WVqM 

 

'언리얼' 카테고리의 다른 글

UE4 Weapon Plugin (10)  (0) 2023.06.15
UE4 Weapon Plugin (9)  (0) 2023.06.14
UE4 Weapon Plugin (7)  (0) 2023.06.12
UE4 Weapon Plugin (6)  (0) 2023.06.09
UE4 Weapon Plugin (5)  (2) 2023.06.07
Comments