[UE] 주먹 난타 스킬 구현하기

마우스 우클릭으로 각 무기 당 스킬을 구현할 것이다. 스킬들은 SubAction으로 묶는다. 첫번째로 주먹 스킬을 구현해보자.
목차
Plugins |
||||
Weapon |
||||
Resource |
||||
Icon128.png weapon_thumbnail_icon.png |
||||
Source |
||||
Weapon | ||||
SWeaponCheckBoxes.h .cpp SWeaponDetailsView.h .cpp SWeaponDoActionData.h .cpp SWeaponEquipmentData.h .cpp SWeaponHitData.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 |
||||
Source | ||
U2212_06 | ||
Characters | ||
CAnimInstance.h .cpp CEnemy.h .cpp CPlayer.h .cpp ICharacter.h .cpp |
||
Components | ||
CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CStatusComponent.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 CAnimNotifyState_SubAction.h .cpp 생성 |
||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CDoAction_Combo.h .cpp CSubAction_Fist.h .cpp 생성 CAttachment.h .cpp CDoAction.h .cpp CEquipment.h .cpp CSubAction.h .cpp 생성 CWeaponAsset.h .cpp CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
주먹 난타 스킬 구현하기 - SubAction 구조 생성하기
CSubAction 생성
새 C++ 클래스 - Object - CSubAction 생성

CSubAction.h
#pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "CSubAction.generated.h" UCLASS(Abstract)//객체화하지 않는 경우, Abstract 명시 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: //NativeEvent는 가상화. 정의할테니 원하면 가져다가 써라는 의미. //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; };
CASubAtion는 객체화하지 않는 클래스다.
- UCLASS(Abstract)
- 객체화하지 않는 경우, Abstract 명시
- ※ 주의: 추상화 클래스에 기본값을 넣으면 안 된다. null이어야 한다. 만약 기본값을 설정하여 객체화가 일어나면 컴파일러는 "객체화 될 수 없다"고 경고 문구가 나온다.
UObject 상속 클래스에서의 Begin Play 사용
- UObject 상속 클래스는 Begin Play 함수가 자동으로 설정되지 않는다.
- 해당 경우 Begin Play를 사용하려면 ACharacter를 변수로 받아서 사용한다.
- virtual void BeginPlay(class ACharacter* InOwner, class ACAttachment* InAttachment, class UCDoAction* InDoAction);
CSubAction.cpp
#include "Weapons/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); }
CSubAction_Fist 생성
CSubAction 파생 C++ 클래스 생성 - CSubAction_Fist 생성


CSubAction_Fist.h
#pragma once #include "CoreMinimal.h" #include "Weapons/CSubAction.h" #include "Weapons/CWeaponStructures.h" #include "CSubAction_Fist.generated.h" UCLASS(Blueprintable)//블루프린트화해서 설정할 수 있도록 Blueprintable 명시 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; };
함수 오버라이드
- void Pressed() override;
- void End_SubAction_Implementation() override;
CSubAction_Fist.cpp
#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()); CheckTrue(State->IsSubActionMode()); Super::Pressed(); State->SetActionMode(); State->OnSubActionMode(); ActionData.DoAction(Owner); } void UCSubAction_Fist::End_SubAction_Implementation() { Super::End_SubAction_Implementation(); //원래 상태로 돌려준다. State->SetIdleMode(); State->OffSubActionMode(); Movement->Move(); Movement->DisableFixedCamera(); }
함수 정의
- 키를 눌렀을 때 상태를 바꾸고 동작을 실행시킨다.
- void UCSubAction_Fist::Pressed()
- 끝난 후 상태를 돌려준다.
- void UCSubAction_Fist::End_SubAction_Implementation()
BP_CSubAction_Fist 생성
CSubAction_Fist 기반 블루프린트 클래스 생성 - BP_CSubAction_Fist 생성


문제상황:
BP_CSubAction_Fist를 처음 열면 ActionData 항목이 보이지만
웨폰 플러그인을 실행하고 다시 열면 ActionData 항목이 보이지 않는다.
실행화면



BP_CSubAction_Fist를 처음 열면 ActionData 항목이 보이지만
웨폰 플러그인을 실행하고 다시 열면 ActionData 항목이 보이지 않는다.
문제상황이 발생하는 이유
Weapon Asset 창이 열릴 때 우리가 등록한 임의의 자료가 커스터마이징되게 해두었다.
문제는 Weapon Asset(여기서는 SubAction)을 등록을 할 수 없으면 그리지 않고 리턴하기 때문에 창이 열려도 항목이 그려지지 않는다.
SWeaponDoActionData와 SWeaponHitData 부분을 수정한다.
변경 전
void SWeaponDoActionData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils) { if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false) return;//CanDraw가 false면 그리지 않고 리턴. //... } void SWeaponDoActionData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils) { if (SWeaponCheckBoxes::CanDraw(InPropertyHandle, CheckBoxes.Num()) == false) return;//CanDraw가 false면 그리지 않고 리턴. //... }
변경 후
void SWeaponDoActionData::CustomizeHeader(TSharedRef<IPropertyHandle> InPropertyHandle, FDetailWidgetRow& InHeaderRow, IPropertyTypeCustomizationUtils& InCustomizationUtils) { 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;//CanDraw가 false면 그리지 않고 리턴. } //... } void SWeaponDoActionData::CustomizeChildren(TSharedRef<IPropertyHandle> InPropertyHandle, IDetailChildrenBuilder& InChildBuilder, IPropertyTypeCustomizationUtils& InCustomizationUtils) { 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;//CanDraw가 false면 그리지 않고 리턴. } //... }
아래의 SWeaopnDoActionData와 SWeaponHitData 항목에 수정된 전체 코드가 적혀있다.
SWeaponDetailsView
SWeaponDetailsView.h
변동사항 없음.
SWeaponDetailsView.cpp
#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 "Animation/AnimMontage.h" #include "Particles/ParticleSystem.h" #include "NiagaraSystem.h" #include "Sound/SoundWave.h" bool SWeaponDetailsView::bRefreshByCheckBoxes = false;//static 변수 초기화. 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를 카테고리에 추가. category.AddProperty("SubActionClass", type);//CWeaponAsset의 SubActionClass를 카테고리에 추가. } //EquipmentData { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("EquipmentData", FText::FromString("Equipment Data")); IDetailPropertyRow& row = category.AddProperty("EquipmentData", type); if (bRefreshByCheckBoxes == false)//새로고침이 아닐 때 { TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponEquipmentData::CreateCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다. checkBoxes->AddProperties(row.GetPropertyHandle());//checkBoxes에 실제로 가진 Handle를 추가 FEquipmentData data;//변수 선언한다. FEquipmentData의 변수 data 기본값을 아래에서 사용한다. int32 index = 0; checkBoxes->CheckDefaultObject(index++, data.Montage); checkBoxes->CheckDefaultValue(index++, data.PlayRate); checkBoxes->CheckDefaultValue(index++, data.bCanMove); checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation); } } //DoActionData { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("DoActionData", FText::FromString("DoAction Data")); IDetailPropertyRow& row = category.AddProperty("DoActionDatas", type);//변수 추가 //WeaponAsset에 있는 데이터명과 일치시킨다. DoActionDatas if (bRefreshByCheckBoxes == false) { uint32 count = 0; row.GetPropertyHandle()->GetNumChildren(count);//전체 개수를 구한다. SWeaponDoActionData::EmptyCheckBoxes();//비워놓고 시작. FDoActionData data;//기본값 사용할 변수 for (uint32 i =0; i < count; i++)//자식Handle를 for문 돌리기 { TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);//헤더의 handle TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponDoActionData::AddCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다. checkBoxes->AddProperties(handle); int32 index = 0; checkBoxes->CheckDefaultObject(index++, data.Montage); checkBoxes->CheckDefaultValue(index++, data.PlayRate); checkBoxes->CheckDefaultValue(index++, data.bCanMove); checkBoxes->CheckDefaultValue(index++, data.bUseControlRotation); checkBoxes->CheckDefaultValue(index++, data.bFixedCamera); checkBoxes->CheckDefaultObject(index++, data.Effect); checkBoxes->CheckDefaultValue(index++, data.EffectLocation); checkBoxes->CheckDefaultValue(index++, data.EffectScale); } }//if(bRefreshByCheckBoxes) } //HitData { //.EditCategory 해당 타입에 해당 카테고리가 있으면 그 카테고리를 return, 없으면 새로 만들어서 return IDetailCategoryBuilder& category = DetailBuilder.EditCategory("HitData", FText::FromString("Hit Data")); IDetailPropertyRow& row = category.AddProperty("HitDatas", type);//변수 추가 //WeaponAsset에 있는 데이터명과 일치시킨다. DoActionDatas if (bRefreshByCheckBoxes == false) { uint32 count = 0; row.GetPropertyHandle()->GetNumChildren(count);//전체 개수를 구한다. SWeaponHitData::EmptyCheckBoxes();//비워놓고 시작. FHitData data;//기본값 사용할 변수 for (uint32 i = 0; i < count; i++)//자식Handle를 for문 돌리기 { TSharedPtr<IPropertyHandle> handle = row.GetPropertyHandle()->GetChildHandle(i);//헤더의 handle TSharedPtr<SWeaponCheckBoxes> checkBoxes = SWeaponHitData::AddCheckBoxes();//카테고리가 처음에 만들어질 때 checkBox를 만든다. checkBoxes->AddProperties(handle); int32 index = 0; checkBoxes->CheckDefaultObject(index++, data.Montage); checkBoxes->CheckDefaultValue(index++, data.PlayRate); checkBoxes->CheckDefaultValue(index++, data.Power); checkBoxes->CheckDefaultValue(index++, data.Launch); checkBoxes->CheckDefaultValue(index++, data.StopTime); checkBoxes->CheckDefaultObject(index++, data.Sound); checkBoxes->CheckDefaultObject(index++, data.Effect); checkBoxes->CheckDefaultValue(index++, data.EffectLocation); checkBoxes->CheckDefaultValue(index++, data.EffectScale); } }//if(bRefreshByCheckBoxes) } }
웨폰 플러그인에 SubAction 항목 추가
- void SWeaponDetailsView::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{ //...
//Class Settings{
IDetailCategoryBuilder& category = DetailBuilder.EditCategory("ClassSettings", FText::FromString("Class Settings"));
category.AddProperty("AttachmentClass", type); //CWeaponAsset의 AttachmentClass를 카테고리에 추가.
category.AddProperty("EquipmentClass", type); //CWeaponAsset의 EquipmentClass를 카테고리에 추가.
category.AddProperty("DoActionClass", type); //CWeaponAsset의 DoActionClass를 카테고리에 추가.
category.AddProperty("SubActionClass", type); //CWeaponAsset의 SubActionClass를 카테고리에 추가.
}
Fist 스킬 구현하기 - Weapon Plugin
CWeaponAsset
CWeaponAsset.h
#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 ACAttachment> AttachmentClass; UPROPERTY(EditAnywhere) FEquipmentData EquipmentData; UPROPERTY(EditAnywhere) TSubclassOf<class UCEquipment> EquipmentClass; UPROPERTY(EditAnywhere) TSubclassOf<class UCDoAction> DoActionClass; UPROPERTY(EditAnywhere) TArray<FDoActionData> DoActionDatas; //CWeaopnStructure내의 FDoActionData UPROPERTY(EditAnywhere) TArray<FHitData> HitDatas; //CWeaopnStructure내의 FHitData UPROPERTY(EditAnywhere) TSubclassOf<class UCSubAction> SubActionClass; public: FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }//외부에 생성된 것을 리턴해줌. FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }//외부에 생성된 것을 리턴해줌. FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }//외부에 생성된 것을 리턴해줌. FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; }//외부에 생성된 것을 리턴해줌. public: UCWeaponAsset(); void BeginPlay(class ACharacter* InOwner); private: //UPROPERTY를 붙여 가비지 콜렉터가 제거하기 전까지 물고 있게 만든다. //UWeaponAsset은 UObject로부터 상속받아 Actor의 생성주기에 영향을 받지 않아 가비지 콜렉터에 영향을 받는다. UPROPERTY() class ACAttachment* Attachment; UPROPERTY() class UCEquipment* Equipment; UPROPERTY() class UCDoAction* DoAction; UPROPERTY() class UCSubAction* SubAction; #if WITH_EDITOR //Editor 내에서만 수행 void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override; #endif };
배열 변수 추가
- UPROPERTY(EditAnywhere)
TSubclassOf<class UCSubAction> SubActionClass;
인라인 함수 추가
- FORCEINLINE class UCSubAction* GetSubAction() { return SubAction; } //외부에 생성된 것을 리턴해줌.
변수 추가
- UPROPERTY()
class UCSubAction* SubAction;
CWeaponAsset.cpp
#include "Weapons/CWeaponAsset.h" #include "Global.h" #include "CAttachment.h" #include "CEquipment.h" #include "CDoAction.h" #include "CSubAction.h" #include "GameFramework/Character.h" UCWeaponAsset::UCWeaponAsset() { AttachmentClass = ACAttachment::StaticClass();//기본값 EquipmentClass = UCEquipment::StaticClass();//기본값 } void UCWeaponAsset::BeginPlay(ACharacter* InOwner) { if (!!AttachmentClass)//AttachmentClass가 선택되어 있다면 { FActorSpawnParameters params; params.Owner = InOwner; Attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params); } if (!!EquipmentClass)//EquipmentClass가 선택되어 있다면 { Equipment = NewObject<UCEquipment>(this, EquipmentClass); Equipment->BeginPlay(InOwner, EquipmentData); if (!!Attachment)//Attachment가 있다면 { Equipment->OnEquipmentBeginEquip.AddDynamic(Attachment, &ACAttachment::OnBeginEquip); Equipment->OnEquipmentUnequip.AddDynamic(Attachment, &ACAttachment::OnUnequip); } } if(!!DoActionClass) { DoAction = NewObject<UCDoAction>(this, DoActionClass); DoAction->BeginPlay(Attachment, Equipment, InOwner, DoActionDatas, HitDatas); if (!!Attachment) { Attachment->OnAttachmentBeginCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginCollision); Attachment->OnAttachmentEndCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndCollision); Attachment->OnAttachmentBeginOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginOverlap); Attachment->OnAttachmentEndOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndOverlap); } } if(!!SubActionClass) { SubAction = NewObject<UCSubAction>(this, SubActionClass); SubAction->BeginPlay(InOwner, Attachment, DoAction); } } #if WITH_EDITOR //Editor 내에서만 수행 void UCWeaponAsset::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) { Super::PostEditChangeChainProperty(PropertyChangedEvent); CheckTrue(FApp::IsGame());//게임이 실행중이면 실행하면 안 되기 때문에 체크 bool bRefresh = false; bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("DoActionDatas") == 0; bRefresh |= PropertyChangedEvent.GetPropertyName().Compare("HitDatas") == 0;//수정하려는 변수명 == 0 이면 이름이 동일하다는 의미. if (bRefresh) { bool bCheck = false; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayRemove; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayClear; bCheck |= PropertyChangedEvent.ChangeType == EPropertyChangeType::Duplicate; if (bCheck) { FPropertyEditorModule& prop = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");//WITH_EDITOR로 Editor 내에서 수행하기 때문에 사용 가능. TSharedPtr<IDetailsView> detailsView = prop.FindDetailView("WeaponAssetEditorDetailsView");//WeaponAssetEditor.cpp에서 설정한 arg.ViewIdentifier이름 WeaponAssetEditorDetailsView 사용. WeaponAssetEditorDetailsView를 찾는다. if (detailsView.IsValid())//detailsView 창이 그려졌다면 detailsView->ForceRefresh();//새로고침 } } } #endif
BeginPlay에 SubAction 생성
- void UCWeaponAsset::BeginPlay(ACharacter* InOwner)
{
if(!!SubActionClass) {
SubAction = NewObject<UCSubAction>(this, SubActionClass);
SubAction->BeginPlay(InOwner, Attachment, DoAction);
}
}
CWeaponComponent
CWeaponComponent.h
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "CWeaponComponent.generated.h" UENUM(BlueprintType) enum class EWeaponType : uint8 { Fist, Sword, Hammer, Warp, Around, Bow, Max, }; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType); UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) class U2212_06_API UCWeaponComponent : public UActorComponent { GENERATED_BODY() private://DataAsset을 받아온다. UPROPERTY(EditAnywhere, Category = "DataAsset") class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max]; public: //무기 Type이 맞는지 확인해주는 함수들 FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; } FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; } FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; } FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; } FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; } FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; } FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; } public: UCWeaponComponent(); protected: virtual void BeginPlay() override; private: bool IsIdleMode();//StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다. public: class ACAttachment* GetAttachment(); class UCEquipment* GetEquipment(); class UCDoAction* GetDoAction(); class UCSubAction* GetSubAction(); public: //무기 세팅 void SetUnarmedMode(); void SetFistMode(); void SetSwordMode(); void SetHammerMode(); void SetWarpMode(); void SetAroundMode(); void SetBowMode(); void DoAction(); void SubAction_Pressed(); void SubAction_Released(); private: void SetMode(EWeaponType InType); void ChangeType(EWeaponType InType); public: //무기가 바뀌었을때 통보해줄 delegate FWeaponTypeChanged OnWeaponTypeChange; private: class ACharacter* OwnerCharacter; EWeaponType Type = EWeaponType::Max; };
함수 추가
- class UCSubAction* GetSubAction();
함수 추가
- void SubAction_Pressed();
- void SubAction_Released();
CWeaponComponent.cpp
#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" UCWeaponComponent::UCWeaponComponent() { } void UCWeaponComponent::BeginPlay() { Super::BeginPlay(); OwnerCharacter = Cast<ACharacter>(GetOwner()); for (int32 i=0; i < (int32)EWeaponType::Max; i++) { if (!!DataAssets[i]) //DataAssets[i]이 있다면(=무기가 할당되어 있다면) DataAssets[i]->BeginPlay(OwnerCharacter);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다. } } bool UCWeaponComponent::IsIdleMode() { return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode(); } ACAttachment* UCWeaponComponent::GetAttachment() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!DataAssets[(int32)Type], nullptr); return DataAssets[(int32)Type]->GetAttachment(); } UCEquipment* UCWeaponComponent::GetEquipment() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!DataAssets[(int32)Type], nullptr); return DataAssets[(int32)Type]->GetEquipment(); } UCDoAction* UCWeaponComponent::GetDoAction() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!DataAssets[(int32)Type], nullptr); return DataAssets[(int32)Type]->GetDoAction(); } UCSubAction* UCWeaponComponent::GetSubAction() { CheckTrueResult(IsUnarmedMode(), nullptr); CheckFalseResult(!!DataAssets[(int32)Type], nullptr); return DataAssets[(int32)Type]->GetSubAction(); } void UCWeaponComponent::SetUnarmedMode() { GetEquipment()->Unequip(); ChangeType(EWeaponType::Max); } void UCWeaponComponent::SetFistMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Fist); } void UCWeaponComponent::SetSwordMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Sword); } void UCWeaponComponent::SetHammerMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Hammer); } void UCWeaponComponent::SetWarpMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Warp); } void UCWeaponComponent::SetAroundMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Around); } void UCWeaponComponent::SetBowMode() { CheckFalse(IsIdleMode()); SetMode(EWeaponType::Bow); } void UCWeaponComponent::SetMode(EWeaponType InType) { if (Type == InType) { SetUnarmedMode(); return; } else if (IsUnarmedMode() == false) { GetEquipment()->Unequip(); } if (!!DataAssets[(int32)InType]) { DataAssets[(int32)InType]->GetEquipment()->Equip(); ChangeType(InType); } } void UCWeaponComponent::ChangeType(EWeaponType InType) { EWeaponType prevType = Type; Type = InType; if (OnWeaponTypeChange.IsBound()) OnWeaponTypeChange.Broadcast(prevType, InType); } void UCWeaponComponent::DoAction() { if (!!GetDoAction()) GetDoAction()->DoAction(); } void UCWeaponComponent::SubAction_Pressed() { if (!!GetSubAction()) GetSubAction()->Pressed(); } void UCWeaponComponent::SubAction_Released() { if (!!GetSubAction()) GetSubAction()->Released(); }
헤더 추가
- #include "CSubAction.h"
함수 정의
- UCSubAction* UCWeaponComponent::GetSubAction()
함수 정의
- void UCWeaponComponent::SubAction_Pressed()
- void UCWeaponComponent::SubAction_Released()
CPlayer - 키 할당
CPlayer.h
변동사항 없음.
CPlayer.cpp
#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/CMontagesComponent.h" #include "Components/CMovementComponent.h" #include "Components/CWeaponComponent.h" ACPlayer::ACPlayer() { CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetMesh()); CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm); CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon"); CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montages"); CHelpers::CreateActorComponent<UCMovementComponent>(this, &Movement, "Movement"); CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State"); GetMesh()->SetRelativeLocation(FVector(0, 0, -90)); GetMesh()->SetRelativeRotation(FRotator(0, -90, 0)); USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'"); GetMesh()->SetSkeletalMesh(mesh); TSubclassOf<UCAnimInstance> animInstance; CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/ABP_Character.ABP_Character_C'"); GetMesh()->SetAnimClass(animInstance); SpringArm->SetRelativeLocation(FVector(0, 0, 140)); SpringArm->SetRelativeRotation(FRotator(0, 90, 0)); SpringArm->TargetArmLength = 200; SpringArm->bDoCollisionTest = false; SpringArm->bUsePawnControlRotation = true; SpringArm->bEnableCameraLag = true; GetCharacterMovement()->RotationRate = FRotator(0, 720, 0); } void ACPlayer::BeginPlay() { Super::BeginPlay(); Movement->OnRun(); //Movement의 기본을 Run으로 설정 Movement->DisableControlRotation();//Movement의 기본을 DisableControlRotation으로 설정 State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged); } void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", Movement, &UCMovementComponent::OnMoveForward); PlayerInputComponent->BindAxis("MoveRight", Movement, &UCMovementComponent::OnMoveRight); PlayerInputComponent->BindAxis("VerticalLook", Movement, &UCMovementComponent::OnVerticalLook); PlayerInputComponent->BindAxis("HorizontalLook", Movement, &UCMovementComponent::OnHorizontalLook); PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Pressed, Movement, &UCMovementComponent::OnSprint); PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Released, Movement, &UCMovementComponent::OnRun); PlayerInputComponent->BindAction("Avoid", EInputEvent::IE_Pressed, this, &ACPlayer::OnAvoid); PlayerInputComponent->BindAction("Fist", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetFistMode); PlayerInputComponent->BindAction("Sword", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetSwordMode); PlayerInputComponent->BindAction("Hammer", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetHammerMode); PlayerInputComponent->BindAction("Action", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::DoAction); PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SubAction_Pressed); PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::SubAction_Released); } void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType) { switch (InNewType) { case EStateType::BackStep: BackStep(); break; } } void ACPlayer::OnAvoid() { CheckFalse(State->IsIdleMode()); CheckFalse(Movement->CanMove()); CheckTrue(InputComponent->GetAxisValue("MoveForward") >= 0.0f);//뒷방향을 입력했다면 State->SetBackStepMode();//State을 BackStepMode로 변경한다. } void ACPlayer::BackStep() { Movement->EnableControlRotation();//정면을 바라본 상태로 뒤로 뛰어야하기 때문에 EnableControlRotation으로 만들어준다. Montages->PlayBackStepMode();//PlayBackStepMode()를 통해 몽타주 재생. } void ACPlayer::End_BackStep() { Movement->DisableControlRotation();//Backstep이 끝나면 원래대로 돌려준다. State->SetIdleMode();//Idle상태로 돌려줌. }
SubAction 키 할당
- void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) {
- PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SubAction_Pressed);
PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::SubAction_Released); } - UCWeaponComponent의 SubAction_Presssed와 SubAction_Released 함수를 사용한다.
- PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SubAction_Pressed);
CStateComponent
CStateComponent.h
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "CStateComponent.generated.h" UENUM() enum class EStateType : uint8 { Idle = 0, BackStep, Equip, Hitted, Dead, Action, Max, }; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FStateTypeChanged, EStateType, InPrevType, EStateType, InNewType); UCLASS() class U2212_06_API UCStateComponent : public UActorComponent { GENERATED_BODY() public: FORCEINLINE bool IsIdleMode() { return Type == EStateType::Idle; } FORCEINLINE bool IsBackstepMode() { return Type == EStateType::BackStep; } FORCEINLINE bool IsEquipMode() { return Type == EStateType::Equip; } FORCEINLINE bool IsHittedMode() { return Type == EStateType::Hitted; } FORCEINLINE bool IsDeadMode() { return Type == EStateType::Dead; } FORCEINLINE bool IsActionMode() { return Type == EStateType::Action; } FORCEINLINE bool IsSubActionMode() { return bInSubActionMode; } public: UCStateComponent(); protected: virtual void BeginPlay() override; public: void SetIdleMode(); void SetBackStepMode(); void SetEquipMode(); void SetHittedMode(); void SetDeadMode(); void SetActionMode(); void OnSubActionMode(); void OffSubActionMode(); private: void ChangeType(EStateType InType); public: FStateTypeChanged OnStateTypeChanged; private: EStateType Type; private: bool bInSubActionMode; };
인라인 함수 추가
- FORCEINLINE bool IsSubActionMode() { return bInSubActionMode; }
함수 추가
- void OnSubActionMode();
- void OffSubActionMode();
변수 추가
- bool bInSubActionMode;
CStateComponent.cpp
#include "Components/CStateComponent.h" #include "Global.h" UCStateComponent::UCStateComponent() { } void UCStateComponent::BeginPlay() { Super::BeginPlay(); } void UCStateComponent::SetIdleMode() { ChangeType(EStateType::Idle); } void UCStateComponent::SetBackStepMode() { ChangeType(EStateType::BackStep); } void UCStateComponent::SetEquipMode() { ChangeType(EStateType::Equip); } void UCStateComponent::SetHittedMode() { ChangeType(EStateType::Hitted); } void UCStateComponent::SetDeadMode() { ChangeType(EStateType::Dead); } void UCStateComponent::SetActionMode() { ChangeType(EStateType::Action); } void UCStateComponent::OnSubActionMode() { bInSubActionMode = true; } void UCStateComponent::OffSubActionMode() { bInSubActionMode = false; } void UCStateComponent::ChangeType(EStateType InType) { EStateType prevType = Type; Type = InType; if (OnStateTypeChanged.IsBound()) OnStateTypeChanged.Broadcast(prevType, Type); }
Editor 작업: 몽타주 및 SubAction 데이터 할당
Fist_Skill_Montage






AnimNotifyState: SubAction과 Timed Particle Effect 추가
- 오늘 만든 CAnimNotifyState_SubAction 할당.
- Timed Particle Effect에 새롭게 생성한 소켓을 넣어준다.
- Timed Particle Effect에서 파티클 이펙트의 크기를 조절할 수 없기 때문에 소켓을 생성해서 넣어주는 방식으로 해결하였다. 소켓에서는 파티클 이펙트의 스케일 조정이 가능하다.
※ 참고 - 애님 노티파이의 Timed Particle Effect에서 Destroy Immediately 옵션

- Destroy Immediately 옵션을 체크하면 이펙트 내부 시간설정과 상관없이 AnimNotifyState이 끝나면 이펙트가 즉시 사라진다.
- 자연스럽게 사라지게 하려면 체크를 해제하고 AnimNotifyState 범위를 줄이는게 낫다.
BP_CSubAction_Fist

BP_CSubAction_Fist 에 Action Data를 넣어준다.
DA_Fist 에 SubAction Class 할당

CAnimNotifyState_SubAction 생성: SubAction 동작 끝내기에 사용.
새 C++ 클래스 - AnimNotifyState - CAnimNotifyState_SubAction 생성

CAnimNotifyState_SubAction.h
#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; };
CAnimNotifyState_SubAction.cpp
#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(); }
실행화면

'⭐ Unreal Engine > UE RPG Skill' 카테고리의 다른 글
[UE] 해머 스킬 만들기 (0) | 2023.06.27 |
---|---|
[UE] 일섬 스킬 충돌, 해머 스킬 만들기 (0) | 2023.06.26 |
[UE] 주먹 스킬 충돌처리, 전진 찌르기 스킬 구현하기 (0) | 2023.06.23 |
[UE] 잔상효과 구현하기 (0) | 2023.06.22 |
[UE] 카메라 애니메이션 구현하기 (0) | 2023.06.21 |
댓글
이 글 공유하기
다른 글
-
[UE] 일섬 스킬 충돌, 해머 스킬 만들기
[UE] 일섬 스킬 충돌, 해머 스킬 만들기
2023.06.26 -
[UE] 주먹 스킬 충돌처리, 전진 찌르기 스킬 구현하기
[UE] 주먹 스킬 충돌처리, 전진 찌르기 스킬 구현하기
2023.06.23 -
[UE] 잔상효과 구현하기
[UE] 잔상효과 구현하기
2023.06.22 -
[UE] 카메라 애니메이션 구현하기
[UE] 카메라 애니메이션 구현하기
2023.06.21
댓글을 사용할 수 없습니다.