어제 만든 기본세팅을 사용하여 Plugin 버튼을 만들어보자. 이번에 만들 Plugin 버튼은 Unreal Editor 상단에 Ribbon Button 형태로 만들어질 것이다. 오늘의 목표는 버튼을 만들어 창을 띄우기다.

 

목차

     

     


     

     
    Plugins
     
      Weapon
     
        Resource
     
          Icon128.png
    weapon_thumbnail_icon.png
     
        Source
     
          Weapon  
          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
     

     

     

     

    Weapon Plugin 만들기

     

     


     

    Slate UI 의 디자인 패턴 - 재귀적 호출

     

    new A()  →  Add("  ")  →   Add("  ")

    • Slate UI의 디자인 패턴을 만들 때 자기 자료형으로 리턴하는 재귀적 호출을 사용한다.

     


     

    Slate의 동적할당?

     

    SNew(SButton)
    .Content()
    [
    	SNew(STextBlock)
    	.Text( )  //여기위치
    ]

     

    SLATE_ARGUMENT

    SLATE_EVENT

    SLATE_ATTRIBUTE

    • TAttibute<FText>
    • 외부에서 값이 바뀌면 적용된다.

     

     


     

     

    Weapon.Build.cs

     

    Weapon.Build.cs

    더보기
    using UnrealBuildTool;
    
    public class Weapon : ModuleRules
    {
    	public Weapon(ReadOnlyTargetRules Target) : base(Target)
    	{
    		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
    
            PrivateIncludePaths.Add(ModuleDirectory);
    
    		PublicDependencyModuleNames.Add("Core");
    
    		PrivateDependencyModuleNames.Add("U2212_06");//프로젝트명
    
    		PrivateDependencyModuleNames.Add("CoreUObject");
            PrivateDependencyModuleNames.Add("Engine");
            PrivateDependencyModuleNames.Add("Slate");
            PrivateDependencyModuleNames.Add("SlateCore");
    
            PrivateDependencyModuleNames.Add("UnrealEd");//UFactory 
            PrivateDependencyModuleNames.Add("EditorStyle");// 
        }
    }

     

     PrivateDependencyModuleNames.Add("EditorStyle"); 추가

     


     

     

     

    WeaponCommand 생성

     

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

     

    WeaponCommand .h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Framework/Commands/Commands.h"
    
    class WEAPON_API FWeaponCommand
    	: public TCommands<FWeaponCommand>
    {
    public:
    	FWeaponCommand();
    	~FWeaponCommand();
    
    	void Startup();
    
    public:
    	void RegisterCommands() override;
    
    private:
    	TSharedPtr<FExtender> Extender;
    
    	TSharedPtr<FUICommandList> Command;
    	TSharedPtr<FUICommandInfo> Id;
    
    private:
    	void AddToolBar(FToolBarBuilder& InBuilder);//툴바에 버튼 추가
    	void OnClicked();//버튼이 눌렸을때
    };

     

     

    WeaponCommand .cpp

    더보기
    #include "WeaponCommand.h"
    #include "WeaponStyle.h"
    #include "WeaponAssetEditor.h"
    #include "LevelEditor.h"
    
    FWeaponCommand::FWeaponCommand()
    	: TCommands("Toolbar_Buttons", FText::FromString(""), NAME_None, FEditorStyle::GetStyleSetName())
    {
    	Command = MakeShareable(new FUICommandList());
    }
    
    FWeaponCommand::~FWeaponCommand()
    {
    	if (Command.IsValid())
    		Command.Reset();
    
    	if (Extender.IsValid())
    		Extender.Reset();
    }
    
    void FWeaponCommand::Startup()
    {
    	FWeaponCommand::RegisterCommands();
    
    	Extender = MakeShareable(new FExtender());
    
    
    	FToolBarExtensionDelegate toolbar = FToolBarExtensionDelegate::CreateRaw(this, &FWeaponCommand::AddToolBar);
    	Extender->AddToolBarExtension("Settings", EExtensionHook::After, Command, toolbar);
    
    	FLevelEditorModule& levelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
    	levelEditor.GetToolBarExtensibilityManager()->AddExtender(Extender);
    }
    
    void FWeaponCommand::RegisterCommands()
    {
    #define LOCTEXT_NAMESPACE ""
    	UI_COMMAND(Id, "Weapon", "", EUserInterfaceActionType::Button, FInputChord());
    #undef LOCTEXT_NAMESPACE
    
    
    	FExecuteAction action;
    	action.BindRaw(this, &FWeaponCommand::OnClicked);
    
    	Command->MapAction(Id, action, FCanExecuteAction());
    }
    
    void FWeaponCommand::AddToolBar(FToolBarBuilder& InBuilder)
    {
    	FString name = TEXT("웨폰");
    
    	InBuilder.AddSeparator();
    	InBuilder.AddToolBarButton(Id, NAME_None, FText::FromString(name), FText::FromString("Weapon Asset Editor"), FWeaponStyle::Get()->ToolBar_Icon, NAME_None);
    }
    
    void FWeaponCommand::OnClicked()
    {
    	FWeaponAssetEditor::OpenWindow();
    }

     

     

    ※ 툴바에 한글이 포함되는 경우

    한글이 포함되는 파일을 다른 이름으로 저장 - 고급 저장 옵션: 유니코드(서명 있는 UTF-8) - 코드 페이지65001 적용

     


     

     

    WeaponAssetEditor 생성

     

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

     

    WeaponAssetEditor.h

    더보기
    #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);
    
    public:
    	void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
    
    private:
    	TSharedRef<SDockTab> Spawn_ListViewTab(const FSpawnTabArgs& InArgs);
    
    public:
    	FName GetToolkitFName() const override;
    	FText GetBaseToolkitName() const override;
    	FString GetWorldCentricTabPrefix() const override;
    	FLinearColor GetWorldCentricTabColorScale() const override;
    
    private:
    	static const FName EditorName;
    	static const FName ListViewTabId;
    	static const FName DetailTabId;
    
    private:
    	FReply OnClicked();
    };

    부모로 설정한 : public FAssetEditorToolkit는 추상 클래스다.

    • FAssetEditorToolkit에서 선언한 아래의 함수들을 WeaponAssetEditor에서 재정의한다.
    • FName GetToolkitFName() const override;
    • FText GetBaseToolkitName() const override;
    • FString GetWorldCentricTabPrefix() const override;
    • FLinearColor GetWorldCentricTabColorScale() const override;
    • void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) 

     

     

    WeaponAssetEditor.cpp

    더보기
    #include "WeaponAssetEditor.h"
    #include "Weapons/CWeaponAsset.h"
    
    //설정한 이름들
    const FName FWeaponAssetEditor::EditorName = "WeaponAssetEditor";
    const FName FWeaponAssetEditor::ListViewTabId = "ListView";
    const FName FWeaponAssetEditor::DetailTabId = "Details";
    
    TSharedPtr<FWeaponAssetEditor> FWeaponAssetEditor::Instance = nullptr;//헤더에서 선언한 static 외부 초기화.
    
    void FWeaponAssetEditor::OpenWindow(FString InAssetName)
    {
    	//아래의 코드 대신에 Shutdown()을 사용하면 안 된다! Shutdown()은 다른 곳에서도 콜이 되기 때문에 아래의 코드 대신에 사용하면 문제가 된다.
    	if (Instance.IsValid()) //창이 만들어졌다면
    	{
    		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)
    {
    	//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(ListViewTabId, ETabState::OpenedTab)//ListViewTabId
    					->SetHideTabWell(true)
    				)
    				->Split
    				(
    					FTabManager::NewStack()
    					->SetSizeCoefficient(0.725f)//오른쪽 72.5% 사용
    					->AddTab(DetailTabId, ETabState::OpenedTab)//DetailTabId
    					->SetHideTabWell(true)
    				)
    			)
    		);
    
    	UCWeaponAsset* asset = NewObject<UCWeaponAsset>();//현재 없는 상태이므로 일단 빈 상태로 만들어준다.
    	FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), EditorName, layout, true, true, asset);
    
    }
    
    void FWeaponAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
    {
    	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
    
    	FOnSpawnTab tab;
    	tab.BindSP(this, &FWeaponAssetEditor::Spawn_ListViewTab);
    	TabManager->RegisterTabSpawner(ListViewTabId, tab);
    }
    
    TSharedRef<SDockTab> FWeaponAssetEditor::Spawn_ListViewTab(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);
    }
    
    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()는 처리하고 끝낸다.
    }

     

     

    ※ 참고

    Editor에서는 Super:: 를 사용하면 안 된다.

     

     

    ※ 참고 - Handled(), Unhandled()

    • Handled()를 사용하면 처리하고 끝낸다.
    • Unhandled()를 사용하면 처리하지 않으면 끝낸다.

    https://docs.unrealengine.com/4.26/en-US/BlueprintAPI/Widget/EventReply/Unhandled/

     

    Unhandled

    Unhandled

    docs.unrealengine.com


     

     

    WeaponModule

     

    WeaponModule.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Modules/ModuleManager.h"
    
    class FWeaponModule : public IModuleInterface
    {
    public:
    	virtual void StartupModule() override;
    	virtual void ShutdownModule() override;
    
    private:
    	TSharedPtr<class FWeaponContextMenu> ContextMenu;//언리얼 내 우클릭 대분류목록
    	TSharedPtr<class FWeaponCommand> Command;//언리얼 내 우클릭 대분류목록
    };

    언리얼 내 우클릭 대분류목록

    • TSharedPtr<class FWeaponCommand> Command;

     

     

    WeaponModule.cpp

    더보기
    #include "WeaponModule.h"
    #include "WeaponContextMenu.h"
    #include "WeaponStyle.h"
    #include "WeaponCommand.h"
    #include "IAssetTools.h"
    #include "AssetToolsModule.h"
    
    #define LOCTEXT_NAMESPACE "FWeaponModule"
    IMPLEMENT_MODULE(FWeaponModule, Weapon)
    
    void FWeaponModule::StartupModule()
    {
    	IAssetTools& assetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
    	EAssetTypeCategories::Type categories = assetTools.RegisterAdvancedAssetCategory("WeaponAsset", FText::FromString("Weapon"));//
    
    	ContextMenu = MakeShareable(new FWeaponContextMenu(categories));
    	assetTools.RegisterAssetTypeActions(ContextMenu.ToSharedRef());
    
    	Command = MakeShareable(new FWeaponCommand());
    	Command->Startup();
    }
    
    void FWeaponModule::ShutdownModule()
    {
    	if (ContextMenu.IsValid())
    		ContextMenu.Reset();
    
    	if (Command.IsValid())
    		Command.Reset();
    
    	FWeaponStyle::Shutdown();
    }
    
    #undef LOCTEXT_NAMESPACE

     

     


     

     

    실행화면