초보 코린이의 성장 일지

UE4 SubAction Fist 본문

언리얼

UE4 SubAction Fist

코오린이 2023. 6. 20. 17:51

Fist 스킬을 만들어 볼 것이다. 

 

1. 화면에 배치하는게 아닌 기능만을 제공해주기 위해 생성해 줄 것이다.

2. 배치하는게 아니므로 Actor로 상속받는게 아닌 Object로 받아준다. 직렬화를 해줘야 하기 때문.

더보기
#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CSubAction.generated.h"

UCLASS(Abstract) // 기능이 없으므로, 할당 x
class U2212_06_API UCSubAction : public UObject
{
	GENERATED_BODY()

public:
	UCSubAction();

public:
	virtual void BeginPlay(class ACharacter* InOwner, class ACAttachment* InAttachment, class UCDoAction* InDoAction);

public:
	virtual void Pressed() {}
	virtual void Released() {}

public:
	// BP랑 상호연동 가능하도록,
	// BlueprintNativeEvent로 정의하면 Implementation 붙여주기
	UFUNCTION(BlueprintNativeEvent)
		void Begin_SubAction();
	virtual void Begin_SubAction_Implementation() {} // 자식에서 필요한데로 재정의해서 사용

	UFUNCTION(BlueprintNativeEvent)
		void End_SubAction();
	virtual void End_SubAction_Implementation() {}

	UFUNCTION(BlueprintNativeEvent)
		void Tick(float InDeltaTime);
	virtual void Tick_Implementation(float InDeltaTime) {}

protected:
	class ACharacter* Owner;
	class ACAttachment* Attachment;
	class UCDoAction* DoAction;

	class UCStateComponent* State;
	class UCMovementComponent* Movement;

};
#include "CSubAction.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
#include "Components/CapsuleComponent.h"

UCSubAction::UCSubAction()
{

}

void UCSubAction::BeginPlay(ACharacter* InOwner, ACAttachment* InAttachment, UCDoAction* InDoAction)
{
	// 받아서 저장.
	Owner = InOwner;
	Attachment = InAttachment;
	DoAction = InDoAction;

	State = CHelpers::GetComponent<UCStateComponent>(Owner);
	Movement = CHelpers::GetComponent<UCMovementComponent>(Owner);

}

1. 특정 무기에 액션을 관리해줄 클래스를 생성해주고, 객체를 저장해준다.

2. UCLASS(Abstract) 를 붙여서 할당을 안하게 만든다.

 

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

더보기
#pragma once

#include "CoreMinimal.h"
#include "Weapons/CSubAction.h"
#include "Weapons/CWeaponStructures.h"
#include "CSubAction_Fist.generated.h"

UCLASS(Blueprintable) // BP가 될 수 있도록, 
class U2212_06_API UCSubAction_Fist : public UCSubAction
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, Category = "Action")
		FDoActionData ActionData;

	UPROPERTY(EditAnywhere, Category = "Action")
		TArray<FHitData> HitDatas;

public:
	void Pressed() override;

	void End_SubAction_Implementation() override;

private:
	TArray<class ACharacter*> Hitted;
	int32 HitIndex;
};

1. 여러 조합으로 스킬 및 공격을 구사하게 만들게 될 것이다. 

2. 또한 실행 애니메이션에 타격점이 여러개가 들어져 있는 경우도 있기 때문에, 배열로 만들어서 값을 받아서 실행시킬 것이다.

3. 우선 Header만 정의해 놓고, 컴파일을 시켜준다.

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

1. Action Data는 잘 열리는걸 확인할 수 있다.

 

1. 잘 열렸던 Data가 CSub_Action_Fist를 열게되면 사라지는걸 알 수 있다.

2. 그래서 창이 열려서 그릴수 없는 상황이 발생한다면, 기본 모양정도는 그려주도록 변경해주면 된다.


더보기
#include "SWeaponDoActionData.h"
#include "WeaponStyle.h"
#include "IPropertyUtilities.h"
#include "IDetailPropertyRow.h"
#include "IDetailChildrenBuilder.h"
#include "DetailWidgetRow.h"
#include "SWeaponCheckBoxes.h"
#include "Widgets/Notifications/SNotificationList.h"

void SWeaponDoActionData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils)
{
    // false면 안그리고 끝내버린다, true면 그려준다.
    if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    {
        InHeaderRow
            .NameContent()
            [
                InPropertyHandle->CreatePropertyNameWidget()
            ]
        .ValueContent()
            .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
            .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
            [
                InPropertyHandle->CreatePropertyValueWidget()
            ];

        return;
    }
}

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

    if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    {
        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());

            TSharedPtr<SWidget> name;
            TSharedPtr<SWidget> value;

            row.GetDefaultWidgets(name, value);

            row.CustomWidget()
                .NameContent()
                [
                    name.ToSharedRef()
                ]
            .ValueContent()
                .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
                .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
                [
                    value.ToSharedRef()
                ];
        }//for(i)

        return;
    }

    int32 index = InPropertyHandle->GetIndexInArray(); // 배열안에 번호를 return 해달라,
    CheckBoxes[index]->DrawProperties(InPropertyHandle, &InChildBuilder); // index 배열안에 그려주기
}

1. SWeaponDoActionData에서 기본형으로 그릴 수 있도록 if안에 조건을 추가해준다.


더보기
#include "SWeaponHitData.h"
#include "WeaponStyle.h"
#include "IPropertyUtilities.h"
#include "IDetailPropertyRow.h"
#include "IDetailChildrenBuilder.h"
#include "DetailWidgetRow.h"
#include "SWeaponCheckBoxes.h"
#include "SWeaponHitData.h"
#include "Widgets/Notifications/SNotificationList.h"

void SWeaponHitData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils)
{
    // false면 안그리고 끝내버린다, true면 그려준다.
    if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    {
        InHeaderRow
            .NameContent()
            [
                InPropertyHandle->CreatePropertyNameWidget()
            ]
        .ValueContent()
            .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
            .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
            [
                InPropertyHandle->CreatePropertyValueWidget()
            ];

        return;
    }
}

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

    if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false)
    {
        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());

            TSharedPtr<SWidget> name;
            TSharedPtr<SWidget> value;

            row.GetDefaultWidgets(name, value);

            row.CustomWidget()
                .NameContent()
                [
                    name.ToSharedRef()
                ]
            .ValueContent()
                .MinDesiredWidth(FWeaponStyle::Get()->DesiredWidth.X)
                .MaxDesiredWidth(FWeaponStyle::Get()->DesiredWidth.Y)
                [
                    value.ToSharedRef()
                ];
        }//for(i)

        return;
    }

    int32 index = InPropertyHandle->GetIndexInArray(); // 배열안에 번호를 return 해달라,
    CheckBoxes[index]->DrawProperties(InPropertyHandle, &InChildBuilder); // index 배열안에 그려주기
}

1. SWeaponHitData에서도 동일하게 그릴수 있도록 조건을 추가해준다.

1. 동일한 방법으로 창을 열어도 Data 정보 화면이 기본형으로 나오는걸 확인할 수 있다.


더보기
#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 UCSubAction> SubActionClass;

public:
	// 생성된걸 return 해주기 위해
	FORCEINLINE class UCDSubAction* GetSubAction() { return SubAction; }

private:
	// UObject로부터 상속을 받았기 때문에 생명주기로 관리를 받지 않는다.
	// 그래서 가비지 컬렉터가 조금이라도 사용을 하지 않는다고 판단하면, 임의로 제거시켜버린다.
	// 직렬화를 넣어주게 되면, 직렬화된 상태로 판단하고 가비지 컬렉터가 사용자가 제거하기 전까지 물고 있어주기 때문에 제거되지 않는다.
	UPROPERTY()
		class UCSubAction* SubAction;

#if WITH_EDITOR
	virtual void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override;
#endif // WITH_EDITOR
	 
};

1. 커스텀 마이징을 하기 위해 변수 추가 및 직렬화 시켜준다.

1. DoAction에 기본값이 있으면, 커스텀이 불가능하므로 주석처리해준다.


더보기

 

#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 "Particles/ParticleSystem.h"
#include "NiagaraSystem.h"
#include "Animation/AnimMontage.h"
#include "Sound/SoundWave.h"

void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
    // Class Settings
    {
        // 언리얼에 보여질 식별자 이름, 실제로 보여질 카테고리 이름
        IDetailCategoryBuilder& category = DetailBuilder.EditCategory("ClassSettings", FText::FromString("Class Settings"));
        category.AddProperty("SubActionClass", type);

    }
}

1. WeaponDetailsView에 보일수 있도록 카테고리를 등록해준다.

1. 이제 웨폰을 눌러서 확인해보면, Sub Action Class 카테고리가 생성된걸 확인할 수 있다.

더보기
#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "CDoAction.h"
#include "CSubAction.h"
#include "GameFramework/Character.h"

void UCWeaponAsset::BeginPlay(ACharacter * InOwner)
{	
	if (!!SubActionClass)
	{
		SubAction = NewObject<UCSubAction>(this, SubActionClass);
		SubAction->BeginPlay(InOwner, Attachment, DoAction);
	}
}

1.  Begin에서 콜해준다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CWeaponComponent.generated.h"

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class U2212_06_API UCWeaponComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	// 내부, 외부 둘다 사용하기 위해 생성.
	class UCSubAction* GetSubAction();

public:
	// 외부에서 키가 눌렸는지 확인하기 위해
	void SubAction_Pressed();
	void SubAction_Released();

};
#include "Components/CWeaponComponent.h"
#include "Global.h"
#include "CStateComponent.h"
#include "GameFramework/Character.h"
#include "Weapons/CWeaponAsset.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CEquipment.h"
#include "Weapons/CDoAction.h"
#include "Weapons/CSubAction.h"

UCSubAction* UCWeaponComponent::GetSubAction()
{
	// 현재 선택된 무기 return
	CheckTrueResult(IsUnarmedMode(), nullptr);
	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);

	return DataAssets[(int32)Type]->GetSubAction();
}

void UCWeaponComponent::SubAction_Pressed()
{
	// 키가 눌렀렸는지
	if (!!GetSubAction())
		GetSubAction()->Pressed();
}

void UCWeaponComponent::SubAction_Released()
{
	// 키를 땟는지
	if (!!GetSubAction())
		GetSubAction()->Released();
}

1. 이제 관리해주는 곳 WeaponComponent으로 와서 이어나간다.

2. return 해주는 함수로 선택된 무기를 return 해주고, 키를 눌렀는지 땐 상태인지 확인해준다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CStateComponent.generated.h"

UCLASS()
class U2212_06_API UCStateComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	// 상태 변경 이벤트
	// 액션 모드 체크용도
	FORCEINLINE bool IsSubActionMode() { return bInSubActionMode; }
public:	
	UCStateComponent();

protected:
	virtual void BeginPlay() override;		

public:
	// 외부에서 세팅
	void OnSubActionMode();
	void OffSubActionMode();

private:
	// 액션 구별용도
	bool bInSubActionMode;

};
#include "Components/CStateComponent.h"
#include "Global.h"

void UCStateComponent::OnSubActionMode()
{
	bInSubActionMode = true;
}

void UCStateComponent::OffSubActionMode()
{
	bInSubActionMode = false;
}

1. bool로 받아서 모드를 판단하여 실행할 수 있는지 여부 및 실행도중 다른 액션이 못들어 오도록 만들기 위해 함수와 변수를 생성해준다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "Weapons/CSubAction.h"
#include "Weapons/CWeaponStructures.h"
#include "CSubAction_Fist.generated.h"

UCLASS(Blueprintable) // BP가 될 수 있도록, 
class U2212_06_API UCSubAction_Fist : public UCSubAction
{
	GENERATED_BODY()

public:
	// 키가 눌렸을때, 오버라이딩 해준다.
	void Pressed() override;

};
#include "Weapons/SubActions/CSubAction_Fist.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CDoAction.h"

void UCSubAction_Fist::Pressed()
{
	// 다른 액션이 애니메이션 플레이중에 개입되지 못하도록 조건 처리.
	CheckFalse(State->IsIdleMode());
	// IsSubActionMode가 이미 켜져있으면 들어가면 안된다.
	CheckTrue(State->IsSubActionMode());

	Super::Pressed(); // 부모에 아무런 기능도 없지만 혹시 모르니 콜해준다.

	// 다른 Action이 개입 못하도록 켜준다.
	State->SetActionMode();
	State->OnSubActionMode();

	// 액션 데이터 받기
	ActionData.DoAction(Owner);
}

1. 다른 Action이 개입하지 못하도록 설정 및 조건 처리.


1. 이제 사용할 애니메이션 몽타주를 생성해준다.

1. 몽타주에 스킬 몽타주를 선택해준다.

1. Sub Action Class에 선택해준다.


 

더보기
#include "Characters/CPlayer.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/InputComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"

void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SubAction_Pressed);
	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::SubAction_Released);
}

1. Player로 가서 키 등록을 해준다.


1. 실행해서 키를 눌러보면 몽타주가 실행되는걸 볼 수 있다.


1. 몽타주 시작과 끝을 지정해주기 위해 노티파이를 생성해준다.

더보기
#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "CAnimNotifyState_SubAction.generated.h"

UCLASS()
class U2212_06_API UCAnimNotifyState_SubAction : public UAnimNotifyState
{
	GENERATED_BODY()

public:
	FString GetNotifyName_Implementation() const override;

	virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
	virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
#include "Notifies/CAnimNotifyState_SubAction.h"
#include "Global.h"
#include "Components/CWeaponComponent.h"
#include "Weapons/CSubAction.h"

FString UCAnimNotifyState_SubAction::GetNotifyName_Implementation() const
{
	return "SubAction";
}

void UCAnimNotifyState_SubAction::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
	Super::NotifyBegin(MeshComp, Animation, TotalDuration);
	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
	CheckNull(weapon);
	CheckNull(weapon->GetSubAction());

	weapon->GetSubAction()->Begin_SubAction();
}

void UCAnimNotifyState_SubAction::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	Super::NotifyEnd(MeshComp, Animation);

	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
	CheckNull(weapon);
	CheckNull(weapon->GetSubAction());

	weapon->GetSubAction()->End_SubAction();
}

1. SubAction을 가져다가 Begin, End를 해주면 끝난다. 


더보기
#pragma once

#include "CoreMinimal.h"
#include "Weapons/CSubAction.h"
#include "Weapons/CWeaponStructures.h"
#include "CSubAction_Fist.generated.h"

UCLASS(Blueprintable) // BP가 될 수 있도록, 
class U2212_06_API UCSubAction_Fist : public UCSubAction
{
	GENERATED_BODY()

public:
	void End_SubAction_Implementation() override;

};
#include "Weapons/SubActions/CSubAction_Fist.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CDoAction.h"

void UCSubAction_Fist::End_SubAction_Implementation()
{
	Super::End_SubAction_Implementation();
    
	// 원래 상태로 다시 돌려주기
	State->SetIdleMode();
	State->OffSubActionMode();

	Movement->Move();
	Movement->DisableFixedCamera();
}

1. 원래 상태로 다시 돌려주게 만들어준다.


1. 시작 부분부터 끝까지 노티파이를 넣어준다.

2. 이제 한번 스킬을 사용하고도 다시 사용할 수 있게된다.


1. 파티클을 넣어줄 것이다.

2. 파티클에 스케일을 조절할 수 없게되어 있다. 이럴때는 소켓을 추가해서 늘려주면 된다.

1. Root에 소켓을 만들어주고, 스케일을 조정해준다.

2. 파티클에 소켓 이름을 만들어 준 소켓으로 지정해준다.

1. 파티클이 커진 후 실행되는걸 볼 수 있다.


이제 카메라 효과를 넣어서 스킬 시전중에 효과를 줄 것이다.

1. 시퀀서 카메라가 아닌 다른 방법을 사용할 것이더. 카메라 애님을 눌러서 만들어준다.

1. 이 창에서 작업을 진행할 것이다.

1. 카메라 모양을 클릭해주면, Camera에 싱크를 맞추고 진행할 수 있다.

1. 6초로 늘려준다. 타임라인이라고 생각하면 편하다.

1. 프리뷰 카메라 선택 후 위치를 설정해준다. 이 좌표가 시작지점이 될 것이다.

1. Movement Track를 추가해주면 기본키가 생긴다.

1. 추가된 키를 3지점으로 이동시켜 놓은 후, 오른쪽 위치와 회전을 변경해주고 왼쪽 상단에 키 추가를 눌러서 키 값을 넣어준다.

1. 6지점으로 키를 옮기고 위치와 회전값을 수정 후 키 추가를 해준다.

1. 카메라가 키값에 따라 움직이는 동선이 보여진다.


1. 이제 카메라를 플레이할 클래스를 생성해준다.

더보기
#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "CAnimNotifyState.generated.h"

UCLASS()
class U2212_06_API UCAnimNotifyState : public UAnimNotifyState
{
	GENERATED_BODY()

private:
	UPROPERTY(EditAnywhere, Category = "Camera")
		class UCameraAnim* CameraAnim;

	UPROPERTY(EditAnywhere, Category = "Camera") 
		float PlayRatio = 1; // 속도

	UPROPERTY(EditAnywhere, Category = "Camera")
		float BlendInTime = 0; // 원래 카메라에서 애니메이션 카메라까지 전환되는 시간

	UPROPERTY(EditAnywhere, Category = "Camera")
		float BlendOutTime = 0; // 다시 돌아오는 시간

};

1. 기본 값만 세팅해준다.


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

 

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

UE4 SubAction Fist, Camera Move  (0) 2023.06.21
UE4 SubAction Fist  (0) 2023.06.20
UE4 Weapon Plugin (10)  (0) 2023.06.15
UE4 Weapon Plugin (9)  (0) 2023.06.14
UE4 Weapon Plugin (8)  (2) 2023.06.13
Comments