[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