초보 코린이의 성장 일지

UE4 Bow Aim Bend 본문

언리얼

UE4 Bow Aim Bend

코오린이 2023. 7. 10. 15:57

Bow를 장착했을때, 활 시위를 당겼을때, 당기고 줄이 돌아왔을때 Player 오른손이 붙어있는지와 활과 활줄이 당겨졌다가 원래대로 돌아가는것을 구현해볼 것이다.

1. DoAction을 상속받아 Bow 클래스를 생성한다.

#pragma once

#include "CoreMinimal.h"
#include "Weapons/CDoAction.h"
#include "CDoAction_Bow.generated.h"

UCLASS(Blueprintable)
class U2212_06_API UCDoAction_Bow : public UCDoAction
{
	GENERATED_BODY()

public:
    UCDoAction_Bow();

    void BeginPlay
    (
        class ACAttachment* InAttachment,
        class UCEquipment* InEquipment,
        class ACharacter* InOwner,
        const TArray<FDoActionData>& InDoActionData,
        const TArray<FHitData>& InHitData
    ) override;

    void DoAction() override;
    void Begin_DoAction() override;
    void End_DoAction() override;

    void OnBeginEquip() override;
    void OnUnequip() override;

    void Tick(float InDeltaTime) override;

public:
    void End_BowString();

private:
    class USkeletalMeshComponent* SkeletalMesh;
    class UPoseableMeshComponent* PoseableMesh;

private:
    // 휘어지는거 돌려주기 위해 *로 값 실시간 판단용
    float* Bending;

};
#include "Weapons/DoActions/CDoAction_Bow.h"
#include "Global.h"
#include "Weapons/CEquipment.h"
#include "Weapons/Attachments/CAttachment_Bow.h"
//#include "Weapons/AddOns/CProjectile.h"
#include "GameFramework/Character.h"
#include "Components/PoseableMeshComponent.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"

UCDoAction_Bow::UCDoAction_Bow()
{


}

void UCDoAction_Bow::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionData, const TArray<FHitData>& InHitData)
{
	Super::BeginPlay(InAttachment, InEquipment, InOwner, InDoActionData, InHitData);

	SkeletalMesh = CHelpers::GetComponent<USkeletalMeshComponent>(InAttachment);
	PoseableMesh = CHelpers::GetComponent<UPoseableMeshComponent>(InAttachment);

	ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment);
	Bending = bow->GetBend(); // 활이 휘는 정도

}

void UCDoAction_Bow::DoAction()
{
	CheckFalse(State->IsIdleMode());
	CheckFalse(State->IsSubActionMode());

	Super::DoAction();

	DoActionDatas[0].DoAction(OwnerCharacter);

}

void UCDoAction_Bow::Begin_DoAction()
{
	Super::Begin_DoAction();

}

void UCDoAction_Bow::End_DoAction()
{
	Super::End_DoAction();

}

void UCDoAction_Bow::OnBeginEquip()
{
	Super::OnBeginEquip();

}

void UCDoAction_Bow::OnUnequip()
{
	Super::OnUnequip();

}

void UCDoAction_Bow::Tick(float InDeltaTime)
{
	Super::Tick(InDeltaTime);

}

void UCDoAction_Bow::End_BowString()
{

}

1. 기본적인 생성을 우선 해준다.


1. DoAction을 선택해준다.

2. Data에 활을 당기는 몽타주를 선택.

1. 노티파이를 Begin, End를 알맞은 위치에 넣어준다.

 

1. 캐시 포즈 저장으로 생성하고, Bow Pose로 이름을 정해준다.

2. 캐시포즈를 BowBody에 연결해주고 블렌딩해서 섞어준다.

1. 이제 조준을 하고 공격을 해보면, 활을 발사하면 모션이 나오는걸 확인할 수 있다.

1. 당기는 모션이 생각보다 조금 느리게 보인다. 

2. 발사하는 몽타주로 다시가서 Blend Time를 0.5로 변경해준다.

3. Blend Time은 다른 동작에서 이 몽타주 모션으로 오는데까지 걸리는 시간을 나타낸다.

1. 여전히 조금 느린감이 있어서 몽타주 원본인 애니메이션에서 앞부분 프레임을 조금 잘라준다.

1. 그리고 자른만큼 다시 몽타주가 진행되어야 하므로, 다시 만드는게 아닌 해당 몽타주에 에셋 액션 -> 리로드를

눌러주면 된다.


이제 장착되었을때 활줄이 손에 붙도록 만들어 줄 것이다.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Weapons/CWeaponStructures.h"
#include "CEquipment.generated.h"

UCLASS()
class U2212_06_API UCEquipment : public UObject
{
	GENERATED_BODY()

public:
	FORCEINLINE const bool* GetEquipped() { return &bEquipped; }

private:
	bool bBeginEquip; // Equip 시작했는지
	bool bEquipped;  // Equip가 완료되었는지
};

1. 장착 중에서 해야할 일이므로, Equipment에서 bool 변수 2개를 선언하고 return 함수로 만들어서 넘겨준다.


#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Weapons/CWeaponStructures.h"
#include "CDoAction.generated.h"

UCLASS(Abstract) // 객체화 되면 안되므로 Abstract
class U2212_06_API UCDoAction : public UObject
{
	GENERATED_BODY()
	
public:
	UFUNCTION()
		virtual void OnBeginEquip() { }

	UFUNCTION()
		virtual void OnUnequip() { }

};

1. 함수를 가상으로 선언해주고, 정의는 다른곳에서 해줄 것이다.


#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Weapons/CWeaponStructures.h"
#include "CEquipment.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentEquip);

UCLASS()
class U2212_06_API UCEquipment : public UObject
{
	GENERATED_BODY()

public:
	// 델리게이트 이벤트를 처리할 변수.
	FEquipmentBeginEquip OnEquipmentEquip;
	FEquipmentBeginEquip OnEquipmentBeginEquip;
	FEquipmentUnequip OnEquipmentUnequip;


};
#include "Weapons/CEquipment.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CMovementComponent.h"
#include "Components/CStateComponent.h"
#include "CEquipment.h"

void UCEquipment::Equip_Implementation()
{
	State->SetEquipMode();

	//// BeginEquip 되기전에 처리되어야 한다. 처리되고 난 후에 이벤트 콜하면 의미가 없기 때문.
	if (OnEquipmentEquip.IsBound()) 

}

1. 델리게이트로 OnEquipmentEquip를 생성 후 이벤트 연결해준다.

2. Begin보다 먼저 호출될 수 있도록 처리해준다.


#pragma once

#include "CoreMinimal.h"
#include "Weapons/CDoAction.h"
#include "CDoAction_Bow.generated.h"

UCLASS(Blueprintable)
class U2212_06_API UCDoAction_Bow : public UCDoAction
{
	GENERATED_BODY()

public:
    void OnBeginEquip() override;
    void OnUnequip() override;

};

1. WaeponAsset에서 이벤트를 DoAction을 통해 연결시켜 줄 수 있도록 override 해서 선언해준다.


#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "CDoAction.h"
#include "CSubAction.h"
#include "GameFramework/Character.h"

void UCWeaponAsset::BeginPlay(ACharacter * InOwner)
{
	if (!!DoActionClass)
	{
		// DoAction 생성
		DoAction = NewObject<UCDoAction>(this, DoActionClass);
		DoAction->BeginPlay(Attachment, Equipment, InOwner, DoActionDatas, HitDatas);

		if (!!Equipment)
		{
      	    // Equipment에 DoAction를 연결해준다.
			Equipment->OnEquipmentEquip.AddDynamic(DoAction, &UCDoAction::OnBeginEquip);
			Equipment->OnEquipmentUnequip.AddDynamic(DoAction, &UCDoAction::OnUnequip);

		}
	}
}

1. 만들어준 델리게이트인 이벤트를 WeaponAsset에서 연결시켜준다.


#pragma once

#include "CoreMinimal.h"
#include "Weapons/CDoAction.h"
#include "CDoAction_Bow.generated.h"

UCLASS(Blueprintable)
class U2212_06_API UCDoAction_Bow : public UCDoAction
{
	GENERATED_BODY()

public:
    UCDoAction_Bow();

    void BeginPlay
    (
        class ACAttachment* InAttachment,
        class UCEquipment* InEquipment,
        class ACharacter* InOwner,
        const TArray<FDoActionData>& InDoActionData,
        const TArray<FHitData>& InHitData
    ) override;

    void DoAction() override;
    void Begin_DoAction() override;
    void End_DoAction() override;

    void OnBeginEquip() override;
    void OnUnequip() override;

    void Tick(float InDeltaTime) override;

private:
    FVector OriginLocation;
	
    // 활줄에 손이 붙으려면 true 시작 그래야 조건식에 들어간다
    bool bAttachedString = true;
    
private:
	const bool* bEquipped;
};
#include "Weapons/DoActions/CDoAction_Bow.h"
#include "Global.h"
#include "Weapons/CEquipment.h"
#include "Weapons/Attachments/CAttachment_Bow.h"
//#include "Weapons/AddOns/CProjectile.h"
#include "GameFramework/Character.h"
#include "Components/PoseableMeshComponent.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"

UCDoAction_Bow::UCDoAction_Bow()
{


}

void UCDoAction_Bow::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionData, const TArray<FHitData>& InHitData)
{
	Super::BeginPlay(InAttachment, InEquipment, InOwner, InDoActionData, InHitData);

	SkeletalMesh = CHelpers::GetComponent<USkeletalMeshComponent>(InAttachment);
	PoseableMesh = CHelpers::GetComponent<UPoseableMeshComponent>(InAttachment);

	ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment);
	Bending = bow->GetBend(); // 활이 휘는 정도

	// 위치를 우선 저장
	OriginLocation = PoseableMesh->GetBoneLocationByName("bow_string_mid", EBoneSpaces::ComponentSpace);

	bEquipped = InEquipment->GetEquipped();

}

void UCDoAction_Bow::DoAction()
{
	CheckFalse(State->IsIdleMode());
	CheckFalse(State->IsSubActionMode());

	Super::DoAction();

	DoActionDatas[0].DoAction(OwnerCharacter);

}

void UCDoAction_Bow::Begin_DoAction()
{
	Super::Begin_DoAction();

	bAttachedString = false; // 발사하면 활줄에서 떨어져야하므로,

	*Bending = 0;
	// 활줄 다시 돌아오게,
	PoseableMesh->SetBoneLocationByName("bow_string_mid", OriginLocation, EBoneSpaces::ComponentSpace);
}

void UCDoAction_Bow::End_DoAction()
{
	Super::End_DoAction();

}

void UCDoAction_Bow::OnBeginEquip()
{
	Super::OnBeginEquip();

	// 충돌을 꺼준다.
	OwnerCharacter->GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

void UCDoAction_Bow::OnUnequip()
{
	Super::OnUnequip();

	// 값 0으로 돌려줘서 활을 제자리로 돌려놓는다.
	*Bending = 0;
	//  충돌체를 다시 켜준다.
	OwnerCharacter->GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	// 원래 위치로 활줄을 다시 돌려준다.
	PoseableMesh->SetBoneLocationByName("bow_string_mid", OriginLocation, EBoneSpaces::ComponentSpace);

}

void UCDoAction_Bow::Tick(float InDeltaTime)
{
	Super::Tick(InDeltaTime);

	// 항상 캡처
	PoseableMesh->CopyPoseFromSkeletalComponent(SkeletalMesh);

	// 활줄이 붙을 조건 체크
	bool bCheck = true;
	bCheck &= (*bEquipped == true);
	bCheck &= (bBeginAction == false); // 액션에 들어가면 붙으면 안된다.
	bCheck &= (bAttachedString == true);

	CheckFalse(bCheck); // 위에 조건이 성립하지 않으면 아래 코드 x

	// 손에다가 붙여주기.
	FVector handLocation = OwnerCharacter->GetMesh()->GetSocketLocation("Hand_Bow_Right");
	PoseableMesh->SetBoneLocationByName("bow_string_mid", handLocation, EBoneSpaces::WorldSpace);

}

void UCDoAction_Bow::End_BowString()
{

}

1. 활줄이 붙을 조건을 우선 꼼꼼하게 체크해준다.

2. 활 줄을 당길때 충돌체를 꺼주고, 당기고나서 충돌체를 다시 켜주고 활 줄과 활 위치를 다시 제자리로 보낸다.

 1. 장착 후 조준할때 자세가 나온다. 활을 당길때도 활줄이 당겨지는게 눈에 보일 것이다.


이제 쏘고 나서 활줄이 한번만 돌아오고 다시 돌아오지 않는다. 노티파이로 다시 Attach를 돌려주게 만들어서 고처줄 것이다.

1. 노티파이를 하나 생성해준다.

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "CAnimNotify_BowString.generated.h"

UCLASS()
class U2212_06_API UCAnimNotify_BowString : public UAnimNotify
{
	GENERATED_BODY()

public:
	FString GetNotifyName_Implementation() const override;

	void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

};
#include "Notifies/CAnimNotify_BowString.h"
#include "Global.h"
#include "Components/CWeaponComponent.h"
#include "Weapons/DoActions/CDoAction_Bow.h"

FString UCAnimNotify_BowString::GetNotifyName_Implementation() const
{
	return "End_BowString";
}

void UCAnimNotify_BowString::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	Super::Notify(MeshComp, Animation);
	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
	CheckNull(weapon);
	CheckNull(weapon->GetDoAction());

	UCDoAction_Bow* bow = Cast<UCDoAction_Bow>(weapon->GetDoAction());
	CheckNull(bow);

	bow->End_BowString();
}

1. 노티파이로 액션이 끝난 후 활줄을 돌려주도록 설정.

2. End_BowString에서 Bend 값을 100으로 다시 돌려줌으로써 활 줄이 돌아올 것이다.


#pragma once

#include "CoreMinimal.h"
#include "Weapons/CDoAction.h"
#include "CDoAction_Bow.generated.h"

UCLASS(Blueprintable)
class U2212_06_API UCDoAction_Bow : public UCDoAction
{
	GENERATED_BODY()

private:
    // 휘어지는거 돌려주기 위해 *로 값 실시간 판단용
    float* Bending;

private:
    // 활줄에 손이 붙으려면 true 시작 그래야 조건식에 들어간다
    bool bAttachedString = true;

};
#include "Weapons/DoActions/CDoAction_Bow.h"
#include "Global.h"
#include "Weapons/CEquipment.h"
#include "Weapons/Attachments/CAttachment_Bow.h"
//#include "Weapons/AddOns/CProjectile.h"
#include "GameFramework/Character.h"
#include "Components/PoseableMeshComponent.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"

void UCDoAction_Bow::End_BowString()
{
	*Bending = 100;
	bAttachedString = true; // 활 줄을 붙여준다.

}

1. Bending을 100값으로 돌려주면 활도 제대로 돌아오고. 이제 활줄 또한 돌아올 것이다.


1. 생성해준 노티파이를 끝 지점에 설정해준다.


https://www.youtube.com/watch?v=NIrLhJHtGeI 

 

'언리얼' 카테고리의 다른 글

UE4 Parkour  (0) 2023.07.18
UE4 Bow Arrow  (0) 2023.07.13
UE4 Bow Aim (2)  (0) 2023.07.07
UE4 Bow Aim  (0) 2023.07.06
UE4 Weapon Bow  (0) 2023.07.05
Comments