초보 코린이의 성장 일지
UE4 SubAction Fist 본문
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 |