프로그래밍 공부
작성일
2024. 6. 3. 19:04
작성자
WDmil
728x90
  1. 무기 객체에 애니메이션을 적용하고, 상태에 따라 애니메이션을 실행하게 할 수 있다.
  2. HUD를 띄우고, 조준점을 표시할 수 있다.
  3. HUD의 조준점과 총기의 방향Rotation을 연동할 수 있다.

무기 애니메이션 적용.

 

무기객체 또한 엑터를 상속받아 동작하는만큼, 임의의 ABP를 배정하여 상태에 따라 애니메이션을 실행하게 조정할 수 있다.


WeaponActor

#pragma once

#include "CoreMinimal.h"
#include "Datas/WeaponDatas.h"
#include "GameFramework/Actor.h"
#include "Interface/IInteract.h"
#include "BaseWeapon.generated.h"

enum class EFireMode : uint8;

/*
 *	
 */

class UArrowComponent;
enum class EItemPositionType : uint8;

UCLASS()
class FTPSGAME_API ABaseWeapon : public AActor, public IInteract
{
	GENERATED_BODY()

protected:
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly)
	TObjectPtr<USkeletalMeshComponent> Body;
	
	UPROPERTY(VisibleDefaultsOnly)
	FWeaponAssets Assets;
	
	UPROPERTY(VisibleAnywhere)
	UWeaponDataAsset* MyWeaponData;

	UPROPERTY()
	FTimerHandle FireHandle;

	UPROPERTY(VisibleAnywhere, Category = FireMode, meta=(Bitmask="", BitmaskEnum="/Script/FTPSGame.EFireMode"))
	int32 CanFireMode;

	UPROPERTY(VisibleAnywhere, Category = FireMode)
	int32 CurrentFireMode = 2 ^ EFireMode::Single;

	bool bFire = false;
	bool bEmpty = false;
	
public:	
	ABaseWeapon();

	virtual void Interact(ABaseCharacter* InteractingActor) override;

	void ConversionItem(EItemPositionType Type, EAttachType InHolderType = EAttachType::Handle, USceneComponent* InParentComponent = nullptr);
	void SetCollisionEnable(bool bEnable);
	
	EFireMode GetCurrentFireMode();

	FORCEINLINE UWeaponDataAsset* GetWeaponData()					{ return MyWeaponData; }
	FORCEINLINE void SetWeaponData(UWeaponDataAsset* InWeaponData)  { MyWeaponData = InWeaponData; }

	FORCEINLINE bool IsFire()  const { return bFire;  }
	FORCEINLINE bool IsEmpty() const { return bEmpty; }
	
public:
	UFUNCTION()
	void SpawnProjectile();

	UFUNCTION()
	void SpawnShell();
	
	virtual void Fire();
	virtual void HoldFire();
	virtual void ChangeFireMode();
	
protected:
	virtual void BeginPlay() override;

	void LoadData(EWeaponName Name);
	void SetBody();

	void RotateToCrossHair();
};

 

Weapon의 헤더이다.

 

Fire과 HoldFire, ChangeFireMode를 적용하여 무기객체가 공격상태인지, 멈춤상태인지, 탄약이 없는 상태인지 의 상태변환을 적용할 수 있게 한다.

 

public으로 적용되어 있는, FORCEINLINE bool IsFire(), bool IsEmpty를 통해 현재 발사중인지, 탄약이 부족한지 의 객체상태를 반환받는다.

 

위 함수를 호출하는 주체는, Base_Character를 상속받는 AHero 엑터이다.

void ABaseWeapon::Fire()
{
	bFire = true;
}

void ABaseWeapon::HoldFire()
{
	bFire = false;
}

void ABaseWeapon::ChangeFireMode()
{
	while (true)
	{
		CurrentFireMode = CurrentFireMode << 1;
		if ((CanFireMode & CurrentFireMode) != 0 ||
			CurrentFireMode >= 2 ^ EFireMode::Max)
			break;
	}
	
	CurrentFireMode %= (2 ^ EFireMode::Max) - 2; // None + Max = 2
}

 

객체의 무기상태와 ChangeFireMode를 통해, 무기상태를 변환할 수 있게 한다.

void AHero::BindingDefaultActions(UEnhancedInputComponent* EnhancedInputComponent)
{
	// Jumping
	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);

	// Moving
	EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AHero::Move);

	// Looking
	EnhancedInputComponent->BindAction(ViewAction, ETriggerEvent::Triggered, this, &AHero::View);
}

void AHero::BindingCombatActions(UEnhancedInputComponent* EnhancedInputComponent)
{
	// Interact
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("Interact"), ETriggerEvent::Ongoing,  this, &AHero::Interact);
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("Interact"), ETriggerEvent::Canceled, this, &AHero::InteractCanceled);

	// Select Holder
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("MainHolder"), ETriggerEvent::Triggered, this, &AHero::SelectMainHolder);
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("SubHolder"),  ETriggerEvent::Triggered, this, &AHero::SelectSubHolder);

	// ADS
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("AimingDownSight"), ETriggerEvent::Ongoing,   this, &AHero::Aiming);
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("AimingDownSight"), ETriggerEvent::Completed, this, &AHero::HipFire);

	// Fire
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("Fire"), ETriggerEvent::Started, this, &AHero::Fire);
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("Fire"), ETriggerEvent::Completed, this, &AHero::HoldFire);

	// Fire Mode
	EnhancedInputComponent->BindAction(*CombatInput.InputActionMap.Find("ChangeFireMode"), ETriggerEvent::Triggered, this, &AHero::ChangeFireMode);
}

Input키 할당은, Base_Character를 할당받는, Hero에서 키값 할당을 받는다.


ABP설정


CPP로 기본적인 ABP값을 설정해준다. 틱마다 해당 객체를 가지는 무기에서 임의의 값을 가져온다.

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "Datas/WeaponDatas.h"
#include "WeaponAnimInstance.generated.h"

class ABaseWeapon;
enum class EFireMode : uint8;

UCLASS()
class FTPSGAME_API UWeaponAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	TObjectPtr<ABaseWeapon> Weapon;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	float SingleFirePlayRate;
	
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	float AutoFirePlayRate;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	EFireMode FireMode;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	bool bEmpty = false;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	bool bFire = false;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	TObjectPtr<UAnimSequence> SingleFireAnimation;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
	TObjectPtr<UAnimSequence> AutoFireAnimation;
	
public:
	virtual void NativeInitializeAnimation() override;
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;
};

가져와야 하는 값은 위와 같다.

 

자기자신을 사용하는 무기,

단발 발사속도,

자동발사 속도

발사형태

발사여부

탄여부

그리고 애니메이션들 이다.

#include "Animation/Weapons/WeaponAnimInstance.h"
#include "Actors/BaseWeapon.h"
#include "Datas/Weapon/WeaponDataAsset.h"
#include "Utilities/Helper.h"

void UWeaponAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();

	USkeletalMeshComponent* Skel = Cast<USkeletalMeshComponent>(GetOuter());
	Weapon = Cast<ABaseWeapon>(Skel->GetOwner());
}

void UWeaponAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);
	
	if (Weapon == nullptr)
	{
		USkeletalMeshComponent* Skel = Cast<USkeletalMeshComponent>(GetOuter());
		Weapon = Cast<ABaseWeapon>(Skel->GetOwner());
		
		return;
	}

	float RPM = 60 / Weapon->GetWeaponData()->GetRoundPerMinute();
	
	if (SingleFireAnimation)
		SingleFirePlayRate = SingleFireAnimation->GetPlayLength() / RPM;

	if (AutoFireAnimation)
		AutoFirePlayRate = AutoFireAnimation->GetPlayLength() / RPM;
	
	FireMode = Weapon->GetCurrentFireMode();
	
	bFire = Weapon->IsFire();
	bEmpty = Weapon->IsEmpty();
}

 

RPM을 기준으로 FireRate를 정의해준다. 현재 애니메이션이 정의되어있을 때 만, 해당 값을 정의해주면 된다.


ABP 애님그래프 연결

애님 그래프는 위와같이 연결한다.

 

Idle상태 일 때, if값으로 현재 SIngle인지 Auto인지 판별하고 해당 애니메이션을 재생한다.

 

현재 Empty상태일 경우, NeedReload로 이동. Empty상태 애니메이션을 재생한다.

 


Hud띄우기

 

Hud를 제작한다.

 

임의의 코드를 설정할 수도 있음으로, CPP파일로 베이스를 생성하고 해당 CPP를 상속받는 HUD를 제작한다.

 

HUD의 경우 블루프린트 노드로 생성 정의하는것 이 더 효과적임으로 대부분의 작업은 블루프린트 로 진행한다.

 

 

생성한 HUD는 PlayerHudManager시스템을 사용하여 전역적으로 관리해준다.

 


#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CrossHair.generated.h"

/**
 * 
 */
UCLASS()
class FTPSGAME_API UCrossHair : public UUserWidget
{
	GENERATED_BODY()

	
};

 

HUD를 정의만 해준다.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "PlayerHUD.generated.h"

class UCrossHair;

/**
 * 
 */
UCLASS()
class FTPSGAME_API APlayerHUD : public AHUD
{
	GENERATED_BODY()

	UPROPERTY()
	TObjectPtr<UCrossHair> CrossHair;

	UClass* CrossHairClass;

public:
	APlayerHUD();

protected:
	virtual void BeginPlay() override;
};

 

Player Hud를 생성해서. 만든 hud를 생성, 관리한다.

#include "Widgets/PlayerHUD.h"

#include "Actors/Characters/Hero/Hero.h"
#include "Blueprint/UserWidget.h"
#include "Kismet/GameplayStatics.h"
#include "Utilities/Helper.h"
#include "Widgets/CrossHair/CrossHair.h"

APlayerHUD::APlayerHUD()
{
	CrossHairClass = Helper::GetClass<UUserWidget>("/Game/Widget/CrossHair/WB_CrossHair");
}

void APlayerHUD::BeginPlay()
{
	Super::BeginPlay();

	AHero* Hero = Cast<AHero>(UGameplayStatics::GetPlayerCharacter(this, 0));
	
	CrossHair = CreateWidget<UCrossHair>(Hero->GetWorld(), CrossHairClass, "Cross Hair");

	CrossHair->AddToViewport(0);
	CrossHair->SetVisibility(ESlateVisibility::Visible);
}

CPP의 생성자에서 Class를 할당해주고, BeginPlay에서 할당된 Class를 생성 Viewport에 추가. Visible처리해준다.

 


위젯 블루프린트는, 캔버스 패널을 사용하여 내부에 3x3공간을 만들고 3x3공간에 각각의 이미지를 배정해준다.

 

이를통해 에임을 구현할 수 있다.

728x90