캐릭터의 메시데이터에 지정된 소켓에 원하는 무장을 부착할 수 있다.
무장을 해제하였을 때, 해당 무장이 그 위치에 떨어지게 할 수 있다.
BaseCharacter
베이스 케릭터에 Attach를 시켜야 하기 때문에, 일정 버튼을 눌렀을 때, 버튼인풋을 확인하여 길게 누르기 짧게 누르기 버튼을 때기 로 총 3가지의 상황을 판별해야 한다.
한번만 눌렀을때 에는 해당 무기의 정보를 띄우고, 길게 눌렀을 때 에는 해당 무기를 장착하게 하기 위함.
#pragma once
#include "CoreMinimal.h"
#include "Datas/WeaponDatas.h"
#include "GameFramework/Character.h"
#include "Interface/IInteract.h"
#include "BaseCharacter.generated.h"
#define SKELETAL_MESH_COMP(NAME) \
UPROPERTY(VisibleDefualtsOnly) \
TObjectPtr<USkeletalMeshComponent> NAME;
DECLARE_DYNAMIC_DELEGATE(FFinishInteract);
class UInteractComponent;
class UAttachmentComponent;
class UWeaponDataAsset;
class ABaseWeapon;
class UWeaponComponent;
enum class EAttachType : uint8;
struct FWeaponPair;
UCLASS()
class FTPSGAME_API ABaseCharacter : public ACharacter
{
GENERATED_BODY()
protected:
SKELETAL_MESH_COMP(Head)
SKELETAL_MESH_COMP(LeftGrenade)
SKELETAL_MESH_COMP(RightGrenade)
SKELETAL_MESH_COMP(Tablet)
SKELETAL_MESH_COMP(LeftBag)
SKELETAL_MESH_COMP(RightBag)
SKELETAL_MESH_COMP(VestBag)
SKELETAL_MESH_COMP(ExoLegs)
UPROPERTY(EditDefaultsOnly, Category = Components)
TObjectPtr<UAttachmentComponent> AttachmentComponent;
UPROPERTY(EditDefaultsOnly, Category = Components)
TObjectPtr<UInteractComponent> InteractComponent;
UPROPERTY(EditDefaultsOnly, Category = Weapon)
FWeaponPair EquippedWeapon;
//////////// Interact ////////////
IInteract* InteractObject;
bool bCanInteract = true;
bool bInteractProcessing = false;
//////////////////////////////////
public:
FFinishInteract FinishInteract;
public:
ABaseCharacter();
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void AttachWeapon(FWeaponPair InWeapon, EAttachType Type);
virtual FVector GetControlLocation();
void RequestPickUpWeapon(ABaseWeapon* InNewWeapon);
void SwapWeapon(ABaseWeapon* InNewWeapon);
protected:
virtual void BeginPlay() override;
virtual void Interact();
virtual void InteractCanceled();
UFUNCTION()
virtual void InteractComplete();
private:
};
해당 캐릭터에 다이나믹 델리게이트를 선언해준다. 해당 델리게이트를 타고 들어가 Character가 이 델리게이트가 호출되었는지 확인할 것 이다.
Interact(), INteractCanceled(), InteractComplete() 를 구현하여, 해당 함수가 E버튼을 눌렀을 경우 무기가 변경되는데 변경되는 무기가 계속 변경되는( 바닥에 떨어지기 전에 공중에서 계속 스왑현상이 일어날 수 있음 ) 상황을 막기위해 부착해제가 되었을 때 bool값으로 조절한다.
Interact()가 실행되었을 때 bool값 두개( bCanInteract, bInteractProcessing) 을 사용하여 무기장착이 실행되었는지 아닌지 판별한다.
#include "Actors/BaseCharacter.h"
#include "BaseWeapon.h"
#include "Attachments/Backpacks/DefaultBackpack.h"
#include "Components/AttachmentComponent.h"
#include "Components/InteractComponent.h"
#include "Datas/Weapon/WeaponDataAsset.h"
#include "Utilities/Helper.h"
#define CREATE_MESH_COMP(Name) \
Name = Helper::CreateSceneComponent<USkeletalMeshComponent> \
(this, #Name, GetMesh());
ABaseCharacter::ABaseCharacter()
{
// Create Component
AttachmentComponent = Helper::CreateActorComponent<UAttachmentComponent>(this, "Attachment Component");
InteractComponent = Helper::CreateSceneComponent<UInteractComponent>(this, "Interact Collision", GetRootComponent());
// Create Skeletal Mesh Comp
{
CREATE_MESH_COMP(Head)
CREATE_MESH_COMP(LeftGrenade)
CREATE_MESH_COMP(RightGrenade)
CREATE_MESH_COMP(Tablet)
CREATE_MESH_COMP(LeftBag)
CREATE_MESH_COMP(RightBag)
CREATE_MESH_COMP(VestBag)
CREATE_MESH_COMP(ExoLegs)
}
// Mesh Load
{
USkeletalMesh* BodyMesh = Helper::GetAsset<USkeletalMesh>("/Game/Meshes/Elite_Solders/Body/SKM_UE5__Elite_Soldier_Body");
GetMesh()->SetSkeletalMesh(BodyMesh);
USkeletalMesh* HeadMesh = Helper::GetAsset<USkeletalMesh>("/Game/Meshes/Elite_Solders/Heads/SKM_UE5__Elite_Soldier_Head_01");
Head->SetSkeletalMesh(HeadMesh);
}
// Set Leader
{
Head->SetLeaderPoseComponent(GetMesh());
LeftGrenade->SetLeaderPoseComponent(GetMesh());
RightGrenade->SetLeaderPoseComponent(GetMesh());
Tablet->SetLeaderPoseComponent(GetMesh());
LeftBag->SetLeaderPoseComponent(GetMesh());
RightBag->SetLeaderPoseComponent(GetMesh());
VestBag->SetLeaderPoseComponent(GetMesh());
ExoLegs->SetLeaderPoseComponent(GetMesh());
}
// Set Mesh Location & Rotation
{
GetMesh()->SetRelativeLocation(FVector(0, 0, -90));
GetMesh()->SetRelativeRotation(FRotator(0, -90, 0));
}
}
void ABaseCharacter::BeginPlay()
{
Super::BeginPlay();
// Spawn Defaults Weapon
if (EquippedWeapon.DataAsset)
{
EquippedWeapon.Weapon = EquippedWeapon.DataAsset->CreateWeapon(this);
AttachWeapon(EquippedWeapon, EAttachType::Handle);
}
FinishInteract.BindUFunction(this, "InteractComplete");
}
void ABaseCharacter::Interact()
{
if (bCanInteract == true)
{
InteractObject = InteractComponent->GetWinObject();
bCanInteract = false;
bInteractProcessing = true;
}
if (InteractObject != nullptr && bInteractProcessing == true)
{
InteractObject->Interact(this);
}
}
void ABaseCharacter::InteractCanceled()
{
bCanInteract = true;
InteractObject = nullptr;
}
void ABaseCharacter::InteractComplete()
{
bInteractProcessing = false;
}
void ABaseCharacter::AttachWeapon(FWeaponPair InWeapon, EAttachType Type)
{
if (InWeapon.Weapon == nullptr)
{
Helper::Log("InWeapon Is NULL");
return;
}
switch (Type)
{
case EAttachType::Handle :
InWeapon.Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, InWeapon.DataAsset->GetHandleSocketName());
break;
case EAttachType::MainHolder :
InWeapon.Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, FName(TEXT("MainHolder")));
break;
case EAttachType::SubHolder :
InWeapon.Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, FName(TEXT("SubHolder")));
break;
}
InWeapon.Weapon->SetActorRelativeRotation(FRotator(0));
}
FVector ABaseCharacter::GetControlLocation()
{
return GetActorLocation();
}
void ABaseCharacter::RequestPickUpWeapon(ABaseWeapon* InNewWeapon)
{
if (AttachmentComponent->IsBackpackEquipped())
{
EAttachType Space = AttachmentComponent->GetBackpack()->IsAnySpace();
Helper::Print(UEnum::GetValueAsString(Space));
if (Space != EAttachType::Handle)
{
AttachmentComponent->GetBackpack()->SwapWeapon(Space, InNewWeapon);
return;
}
}
SwapWeapon(InNewWeapon);
}
void ABaseCharacter::SwapWeapon(ABaseWeapon* InNewWeapon)
{
// Drop Weapon
EquippedWeapon.Weapon->ConversionItem(EItemPositionType::Dropped);
EquippedWeapon.DataAsset = nullptr;
// Equip Weapon
EquippedWeapon.Weapon = InNewWeapon;
EquippedWeapon.Weapon->SetOwner(this);
EquippedWeapon.Weapon->ConversionItem(EItemPositionType::Equipped, EAttachType::Handle, GetMesh());
EquippedWeapon.DataAsset = InNewWeapon->GetWeaponData();
}
void ABaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
Interact()를 확인해보면, true일 경우, 실행하고 InteractObject에서 Interact가 실행되는걸 확인할 수 있다.
#include "Actors/BaseWeapon.h"
#include "BaseCharacter.h"
#include "Components/ArrowComponent.h"
#include "Datas/ItemEnum.h"
#include "Datas/Weapon/WeaponDataAsset.h"
#include "Utilities/Helper.h"
ABaseWeapon::ABaseWeapon()
{
Body = Helper::CreateSceneComponent<USkeletalMeshComponent>(this, "Body");
Body->SetSimulatePhysics(true);
Body->SetCollisionProfileName("InteractCollision");
Body->SetGenerateOverlapEvents(true);
}
void ABaseWeapon::Interact(ABaseCharacter* InteractingActor)
{
InteractingActor->RequestPickUpWeapon(this);
if (InteractingActor->FinishInteract.IsBound())
InteractingActor->FinishInteract.Execute();
}
void ABaseWeapon::ConversionItem(EItemPositionType Type, EAttachType InHolderType, USceneComponent* InParentComponent)
{
switch (Type)
{
case EItemPositionType::Equipped :
{
Body->SetSimulatePhysics(false);
Body->SetCollisionProfileName("NoCollision");
FName SocketName = "";
switch (InHolderType)
{
case EAttachType::Handle:
SocketName = MyWeaponData->GetHandleSocketName();
break;
case EAttachType::MainHolder:
SocketName = "MainHolder";
break;
case EAttachType::SubHolder:
SocketName = "SubHolder";
break;
}
AttachToActor(Owner, FAttachmentTransformRules::KeepRelativeTransform);
AttachToComponent(InParentComponent, FAttachmentTransformRules::KeepRelativeTransform, SocketName);
Body->SetRelativeLocation(FVector(0));
Body->SetRelativeRotation(FRotator(0));
break;
}
case EItemPositionType::Dropped:
{
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
Body->SetCollisionProfileName("InteractCollision");
Body->SetSimulatePhysics(true);
break;
}
case EItemPositionType::None:
return;
}
}
void ABaseWeapon::SetCollisionEnable(bool bEnable)
{
FName CollisionTypeName = "";
bEnable ? CollisionTypeName = "InteractObject" : CollisionTypeName = "NoCollision";
Body->SetSimulatePhysics(bEnable);
Body->SetCollisionProfileName(CollisionTypeName);
}
void ABaseWeapon::BeginPlay()
{
Super::BeginPlay();
}
void ABaseWeapon::LoadData(EWeaponName Name)
{
UDataTable* Table = Helper::GetAsset<UDataTable>("/Script/Engine.DataTable'/Game/Weapons/1_Blueprints/WeaponAssets.WeaponAssets'");
if (Table)
{
int32 Value = static_cast<int32>(Name);
FName RowName = FName(FString::FromInt(Value));
if (const FWeaponAssets* AssetPtr = Table->FindRow<FWeaponAssets>(RowName, ""))
Assets = *AssetPtr;
}
}
void ABaseWeapon::SetBody()
{
Body->SetSkeletalMesh(Assets.WeaponMesh);
}
여기에서 InteractObject는, BaseWeapon임으로, Base_Weapon에서 INteractingActor를 확인하여, FinishInteract를 호출하고, RequeastPickUpWeapon을 호출하는걸 확인할 수 있다.
FinishInteract를 호출했을 때,
FFinishInteract FinishInteract;
는, 델리게이트 임으로, 델리게이트에 접속하여 호출하게되는것 임을 알 수 있다.