프로그래밍 공부
카테고리
작성일
2024. 5. 9. 16:40
작성자
WDmil
728x90

캐릭터의 메시데이터에 지정된 소켓에 원하는 무장을 부착할 수 있다.

무장을 해제하였을 때, 해당 무장이 그 위치에 떨어지게 할 수 있다.

 


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;

는, 델리게이트 임으로, 델리게이트에 접속하여 호출하게되는것 임을 알 수 있다.

Base_Charcter의 델리게이트 이다.


테스트

728x90