마우스 우클릭으로 각 무기 당 스킬을 구현할 것이다. 스킬들은 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 항목이 보이지 않는다. 

 


 

실행화면

 

(좌) 변경 전: 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_PresssedSubAction_Released 함수를 사용한다.

 


 

 

 

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();
}

 

 


 

 

 

 

실행화면