초보 코린이의 성장 일지
UE4 Bow Arrow 본문
화살을 만들어서 날린 후 충돌처리까지 구현해 볼 것이다.
1. 만들어 놓은 에임 오프셋인 AO_Aim을 최종 애니메이션 전인 블렌딩하고 나서 붙여준다.
2. 그래야만 몽타주까지 포함해서 AO가 진행된다.
1. 이제 조준 후 상하로 이동해도 에임한 자세로 자연스럽게 나오는걸 확인할 수 있다.
1. Actor를 상속받아 화살을 만들기 위해 생성해준다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CArrow.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FProjectileHit, class AActor*, InCauser, class ACharacter*, InOtherCharacter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FProjectileEndPlay, class ACArrow*, InDestroyer);
UCLASS()
class U2212_06_API ACArrow : public AActor
{
GENERATED_BODY()
private:
UPROPERTY(EditDefaultsOnly, Category = "LifeSpan")
// 충돌한 후에 몇초 지난 후 사라질지
float LifeSpanAfterCollision = 1;
private:
UPROPERTY(VisibleDefaultsOnly)
class UCapsuleComponent* Capsule;
UPROPERTY(VisibleDefaultsOnly)
class UProjectileMovementComponent* Projectile;
public:
FORCEINLINE void AddIgnoreActor(AActor* InActor) { Ignores.Add(InActor); }
public:
ACArrow();
protected:
virtual void BeginPlay() override;
// 종료될때 부모에서 소유하고 있는 목록을 제거해주기 위해
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
void Shoot(const FVector& InForward);
private:
UFUNCTION()
void OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
public:
FProjectileHit OnHit; // Hit 이벤트
FProjectileEndPlay OnEndPlay; // EndPlay에 대한 이벤트
private:
TArray<AActor*> Ignores;
};
#include "Weapons/AddOns/CArrow.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/CapsuleComponent.h"
ACArrow::ACArrow()
{
PrimaryActorTick.bCanEverTick = true;
CHelpers::CreateComponent<UCapsuleComponent>(this, &Capsule, "Capsule");
CHelpers::CreateActorComponent<UProjectileMovementComponent>(this, &Projectile, "Projectile");
Projectile->ProjectileGravityScale = 0;
Capsule->BodyInstance.bNotifyRigidBodyCollision = true; // c++에서는 반드시 켜줘야 충돌된다.(블록연산이 일어남)
Capsule->SetCollisionProfileName("BlockAll"); // Block로 Collision을 처리하면 충돌시 바로 멈춘다.
}
void ACArrow::BeginPlay()
{
Super::BeginPlay();
// 시작시에는 충돌을 꺼준다. 화살이 붙어있어야 하므로,
Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Capsule->OnComponentHit.AddDynamic(this, &ACArrow::OnComponentHit); // Hit 이벤트 연결
Projectile->Deactivate(); // 비활성화
}
void ACArrow::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// 소유자 이벤트를 콜해서 자기자신을 소유자 목록에서 지워준다.
if (OnEndPlay.IsBound())
OnEndPlay.Broadcast(this);
}
void ACArrow::Shoot(const FVector& InForward)
{
Projectile->Velocity = InForward * Projectile->InitialSpeed;
Projectile->Activate();
Capsule->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}
void ACArrow::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
SetLifeSpan(LifeSpanAfterCollision);
for (AActor* actor : Ignores)
CheckTrue(actor == OtherActor);
// 화살 충돌 시 충돌 off
Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// hit 이벤트 연결된거 콜 해준다.
ACharacter* character = Cast<ACharacter>(OtherActor);
if (!!character && OnHit.IsBound())
OnHit.Broadcast(this, character);
}
1. 충돌은 이번에 BlockAll로 설정해준다.
2. 화살 충돌시 Hit 이벤트 콜 해준다.
3. 충돌이 끝나면 꺼주고 반복.
#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:
UPROPERTY(EditDefaultsOnly, Category = "Arrow")
TSubclassOf<class ACArrow> ArrowClass;
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:
void CreateArrow(); // 화살 생성
class ACArrow* GetAttachedArrow(); // 활에 붙어있는거 찾아오기
private:
// 화살에 연결할 이벤트들,
UFUNCTION()
void OnArrowHit(class AActor* InCauser, class ACharacter* InOtherCharacter);
UFUNCTION()
void OnArrowEndPlay(class ACArrow* InDestroyer)
private:
TArray<class ACArrow*> Arrows; // 화살에 대한 목록
};
#include "Weapons/DoActions/CDoAction_Bow.h"
#include "Global.h"
#include "Weapons/CEquipment.h"
#include "Weapons/Attachments/CAttachment_Bow.h"
#include "Weapons/AddOns/CArrow.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);
CheckNull(ArrowClass);
ACArrow* arrow = GetAttachedArrow(); // Attach된걸 먼저 가져온다.
// 그다음 Detach 시켜준다, KeepWorld로 해줘야 World 공간인 손에서 화살이 떨어질 것이다.
arrow->DetachFromActor(FDetachmentTransformRules(EDetachmentRule::KeepWorld, true));
// 자신꺼 이벤트 연결
arrow->OnHit.AddDynamic(this, &UCDoAction_Bow::OnArrowHit);
arrow->OnEndPlay.AddDynamic(this, &UCDoAction_Bow::OnArrowEndPlay);
// forward 방향으로 발사
FVector forward = FQuat(OwnerCharacter->GetControlRotation()).GetForwardVector();
arrow->Shoot(forward);
}
void UCDoAction_Bow::End_DoAction()
{
Super::End_DoAction();
CreateArrow(); // 쏘고나서 다시 생성
}
void UCDoAction_Bow::OnBeginEquip()
{
Super::OnBeginEquip();
// 충돌을 꺼준다.
OwnerCharacter->GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
CreateArrow();
}
void UCDoAction_Bow::OnUnequip()
{
Super::OnUnequip();
// 값 0으로 돌려줘서 활을 제자리로 돌려놓는다.
*Bending = 0;
// 충돌체를 다시 켜준다.
OwnerCharacter->GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
// 원래 위치로 활줄을 다시 돌려준다.
PoseableMesh->SetBoneLocationByName("bow_string_mid", OriginLocation, EBoneSpaces::ComponentSpace);
// 화살 역으로 찾아서 붙어있는거 지워준다.
for (int32 i = Arrows.Num() - 1; i >= 0; i--)
{
if (!!Arrows[i]->GetAttachParentActor())
Arrows[i]->Destroy();
}
}
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()
{
*Bending = 100;
bAttachedString = true; // 활 줄을 붙여준다.
}
void UCDoAction_Bow::CreateArrow()
{
// true면 World가 종료되었다는 의미, 월드가 종료되는 조건은 게임이 끝나거나 맵 이동을 했을때,
if (World->bIsTearingDown == true)
return;
FTransform transform;
ACArrow* arrow = World->SpawnActorDeferred<ACArrow>(ArrowClass, transform, NULL, NULL, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
CheckNull(arrow);
arrow->AddIgnoreActor(OwnerCharacter);
FAttachmentTransformRules rule = FAttachmentTransformRules(EAttachmentRule::KeepRelative, true);
arrow->AttachToComponent(OwnerCharacter->GetMesh(), rule, "Hand_Bow_Right_Arrow");
// 지연 시켜서 생성하기.
Arrows.Add(arrow);
UGameplayStatics::FinishSpawningActor(arrow, transform);
}
ACArrow* UCDoAction_Bow::GetAttachedArrow()
{
// 붙어있는거 가져오기
for (ACArrow* projectile : Arrows)
{
if (!!projectile->GetAttachParentActor())
return projectile;
}
return nullptr;
}
void UCDoAction_Bow::OnArrowHit(AActor* InCauser, ACharacter* InOtherCharacter)
{
CheckFalse(HitDatas.Num() > 0);
// 데미지 주기
HitDatas[0].SendDamage(OwnerCharacter, InCauser, InOtherCharacter);
}
void UCDoAction_Bow::OnArrowEndPlay(ACArrow* InDestroyer)
{
// 다쐈으면 제거.
Arrows.Remove(InDestroyer);
}
1. 화살을 찾아서 Player 전방방향으로 발사.
2. 연결된 이벤트를 콜해서 Hit 시켜준다.
3. 제거 후 다시 생성 반복.
1. 이제 화살을 BP로 추가해준다.
1. StaticMesh를 추가해주고, Mesh를 설정해준다.
2. 적당한 위치로 설정 후 충돌체와 맞춰준다. 충돌체 기준으로 화살이 움직여서 맞춰줘야 한다.
3. Projectile Speed도 1000으로 설정해줘야 정상적으로 나가게된다.
4. Static Mesh 콜리전에서 NoCollision으로 설정.
1. DoAction_Bow를 상속받아서 클래스를 생성해준다.
1. DoActionClass에 만들어준 Bow로 선택해준다.
1. Hit 몽타주도 설정해준다.
https://www.youtube.com/watch?v=do3-wJJFfE8
'언리얼' 카테고리의 다른 글
UE4 Parkour Climb (0) | 2023.07.19 |
---|---|
UE4 Parkour (0) | 2023.07.18 |
UE4 Bow Aim Bend (0) | 2023.07.10 |
UE4 Bow Aim (2) (0) | 2023.07.07 |
UE4 Bow Aim (0) | 2023.07.06 |
Comments