화살을 생성하여 발사하여 데미지를 전달한다. 활을 장착(=Equip) 시에 화살을 생성하고 장착 해제(=Unequp) 시에 화살을 소멸시켜준다. 조준 후 화살을 발사하면 Projectile 값이 1000.0으로 적용되어 날라가고 충돌이 켜진다. 발사 후에는 Owner(여기서는 플레이어)의 손에 화살을 생성하여 붙여준다.

 

목차

     

     


     

     

     

     
    Plugins
     
      Weapon
     
        Resource
     
          Icon128.png
    weapon_thumbnail_icon.png
     
        Source
     
          Weapon  
          SWeaponCheckBoxes.h .cpp
    SWeaponDetailsView.h .cpp

    SWeaponDoActionData.h .cpp
    SWeaponEquipmentData.h .cpp
    SWeaponHitData.h .cpp

    SWeaponLeftArea.h .cpp
    Weapon.Build.cs
    WeaponAssetEditor.h .cpp
    WeaponAssetFactory.h .cpp
    WeaponCommand.h .cpp 
    WeaponContextMenu.h .cpp
    WeaponModule.h .cpp
    WeaponStyle.h .cpp 
     
       

     

     
    Source
      U2212_06
        Characters
        CAnimInstance.h .cpp
    CEnemy.h .cpp 
    CPlayer.h .cpp
    ICharacter.h .cpp
        Components
        CMontagesComponent.h .cpp 
    CMovementComponent.h .cpp 
    CStateComponent.h .cpp
    CStatusComponent.h .cpp  
    CWeaponComponent.h .cpp 
        Notifies
        CAnimNotifyState_BeginAction.h .cpp
    CAnimNotifyState_BowString.h .cpp
    CAnimNotify_CameraShake.h .cpp 
    CAnimNotifyState_EndAction.h .cpp
    CAnimNotify_EndState.h .cpp
    CAnimNotifyState.h .cpp
    CAnimNotifyState_CameraAnim.h .cpp
    CAnimNotifyState_Collision.h .cpp 
    CAnimNotifyState_Combo.h .cpp
    CAnimNotifyState_Equip.h .cpp
    CAnimNotifyState_SubAction.h .cpp
        Utilities
        CHelper.h
    CLog.h .cpp
        Weapons
        CArrow.h .cpp 생성
    CAura.h .cpp
    CCamerModifier.h .cpp
    CGhostTrail.h .cpp
    CRotate_Object.h .cpp
    CThornObject.h .cpp
    CAnimInstance_Bow.h .cpp
    CAttachment_Bow.h .cpp
    CDoAction_Around.h .cpp
    CDoAction_Bow.h .cpp
    CDoAction_Combo.h .cpp
    CDoAction_Warp.h .cpp
    CSubAction_Around.h .cpp
    CSubAction_Bow.h .cpp
    CSubAction_Fist.h .cpp
    CSubAction_Hammer.h .cpp
    CSubAction_Sword.h .cpp
    CDoAction_Warp.h .cpp

    CAttachment.h .cpp
    CDoAction.h .cpp
    CEquipment.h .cpp
    CSubAction.h .cpp
    CWeaponAsset.h .cpp
    CWeaponStructures.h .cpp
        Global.h
    CGameMode.h .cpp
    U2212_06.Build.cs
        U2212_06.uproject
     

     

     

     

    조준 - 위아래 조준하기

     


     

    ABP_Character

     

    AO_Aim를 추가한다.


     

    실행화면

     

     

     

     

     

     


     

     

     

     

     

    화살 구현하기

     


     

    CArrow 생성

     

    새 C++ 클래스 - AActor - CArrow 생성

     

     

    CArrow.h

    더보기
    #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;//충돌 후 몇 초 후에 사라지는지
    
    	UPROPERTY(VisibleDefaultsOnly)
    		class UCapsuleComponent* Capsule;//충돌체
    
    	UPROPERTY(VisibleDefaultsOnly)
    		class UProjectileMovementComponent* Projectile;//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;
    	FProjectileEndPlay OnEndPlay;
    
    private:
    	TArray<AActor*> Ignores;
    };

     

     

     

     

    CArrow.cpp

    더보기
    #include "Weapons/AddOns/CArrow.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "GameFramework/ProjectileMovementComponent.h"
    #include "Components/CapsuleComponent.h"
    
    ACArrow::ACArrow()
    {
    	CHelpers::CreateComponent<UCapsuleComponent>(this, &Capsule, "Capsule");
    	CHelpers::CreateActorComponent<UProjectileMovementComponent>(this, &Projectile, "Projectile");
    
    	Projectile->ProjectileGravityScale = 0;
    	Capsule->BodyInstance.bNotifyRigidBodyCollision = true;//BP와는 달리 UnrealC++에서는 bNotifyRigidBodyCollision를 true로 켜줘야 Block 연산이 일어난다.
    	Capsule->SetCollisionProfileName("BlockAll");//Overlap과는 달리 Block의 경우 충돌이 일어나면 멈춘다. 화살은 적에게 박히면 멈춰야하기 때문에 Block으로 설정한다.
    }
    
    void ACArrow::BeginPlay()
    {
    	Super::BeginPlay();
    
    	//화살이 Player에게 붙어있어야 하므로 충돌을 끄고 시작한다.
    	Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    	//추후에 화살 충돌이 일어나야 하므로 Delegate 연결을 해준다.
    	Capsule->OnComponentHit.AddDynamic(this, &ACArrow::OnComponentHit);
    
    	Projectile->Deactivate();//포물선 움직임이 필요없는 상태이므로 꺼준다.
    }
    
    void ACArrow::EndPlay(const EEndPlayReason::Type EndPlayReason)
    {
    	Super::EndPlay(EndPlayReason);
    
    	//소유자 이벤트를 콜해 자기 자신을 지워준다. FProjectileEndPlay OnEndPlay 사용하여 Delegate 이벤트 콜.
    	if (OnEndPlay.IsBound())
    		OnEndPlay.Broadcast(this);
    }
    
    void ACArrow::Shoot(const FVector& InForward)
    {
    	Projectile->Velocity = InForward * Projectile->InitialSpeed;
    	Projectile->Activate();
    
    	Capsule->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);//Collision을 켜준다.
    }
    
    void ACArrow::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
    {
    	SetLifeSpan(LifeSpanAfterCollision);
    
    	for (AActor* actor : Ignores)
    		CheckTrue(actor == OtherActor);
    
    
    	Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    
    	ACharacter* character = Cast<ACharacter>(OtherActor);
    	if (!!character && OnHit.IsBound())
    		OnHit.Broadcast(this, character);
    }

     

     

     

     

    BP_CArrow

    CArrow 기반 블루프린트 생성 - BP_CArrow 생성

    Capsule 사이즈 설정

    • Capsule Half Height: 8.0
    • Capsule Radius: 8.0

    컴포넌트 추가 - StaticMesh

    • 위치: -75.0, 0.0, 0.0 설정.
    • Static Mesh: SM_ElvenArrow 설정.

    StaticMesh

    • 콜리전
      • 콜리전 프리셋: No Collision 

    Projectile

    • Initial Speed: 1000.0으로 설정.

     

     


     

     

    CDoAction_Bow

     

     

    CDoAction_Bow.h

    더보기
    #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();//AnimNotify_BowString의 Notify에서 콜하여 사용한다.
    
    /** 화살 */
    private:
        void CreateArrow();//화살 생성
        class ACArrow* GetAttachedArrow();//활에 붙은 화살을 콜하는 함수.
    
    private:
        UFUNCTION()
            void OnArrowHit(class AActor* InCauser, class ACharacter* InOtherCharacter);
    
        UFUNCTION()
            void OnArrowEndPlay(class ACArrow* InDestroyer);
    /** 화살 */
    
    private:
        class USkeletalMeshComponent* SkeletalMesh;
        class UPoseableMeshComponent* PoseableMesh;
    
    private:
        float* Bending;
    
    private:
        FVector OriginLocation;//처음 위치
        bool bAttachedString = true;//true면 활 시위가 손에 붙는다.
    
    private:
        const bool* bEquipped;
    
    private:
        TArray<class ACArrow*> Arrows;//화살들(=화살 목록)
    };

    변수 추가

    • UPROPERTY(EditDefaultsOnly, Category = "Arrow")
      TSubclassOf<class ACArrow> ArrowClass;
      • 화살 클래스인 ACArrow를 받아오는 변수
    • TArray<class ACArrow*> Arrows;
      • 화살들(=화살 목록)

     

    함수 추가

    • void CreateArrow();
      • 화살를 생성하는 함수
    • UFUNCTION()
      void OnArrowHit(class AActor* InCauser, class ACharacter* InOtherCharacter);
      • 화살 충돌 처리 함수
    • UFUNCTION()
      void OnArrowEndPlay(class ACArrow* InDestroyer);
      • 화살 발사 후 함수

     

     

     

    CDoAction_Bow.cpp

    더보기
    #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 "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);
    
    	//CAnimInstance.h의 Bend변수를 CAttachment_Bow의 GetBend()함수를 거쳐 여기로 가져와서 사용한다.
    	ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment);
    	Bending = bow->GetBend();
    
    	OriginLocation = PoseableMesh->GetBoneLocationByName("bow_string_mid", EBoneSpaces::ComponentSpace);
    
    	bEquipped = InEquipment->GetEquipped();//헤더에 만든 bEquipped변수에 장착 상태를 받아와서 넣어준다.
    }
    
    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;//발사하면 활 시위가 손에서 떨어지도록 false로 만들어준다.
    
    
    	*Bending = 0;//활 구부림 0으로 만들어준다.
    	PoseableMesh->SetBoneLocationByName("bow_string_mid", OriginLocation, EBoneSpaces::ComponentSpace);//활 시위를 처음 위치로 돌아가게 만들어준다.
    
    	CheckNull(ArrowClass);//ArrowClass 있는지 확인.
    
    	ACArrow* arrow = GetAttachedArrow();//Attach되어 있는 화살을 가져온다.
    	arrow->DetachFromActor(FDetachmentTransformRules(EDetachmentRule::KeepWorld,true));//발사하기 위해 화살을 떼어준다. World 상 기준으로 떼어준다. bInCallModify를 true로 설정.
    
    	//이벤트를 연결해준다.
    	arrow->OnHit.AddDynamic(this, &UCDoAction_Bow::OnArrowHit);
    	arrow->OnEndPlay.AddDynamic(this, &UCDoAction_Bow::OnArrowEndPlay);
    
    	FVector forward = FQuat(OwnerCharacter->GetControlRotation()).GetForwardVector();//방향을 설정한다.
    	arrow->Shoot(forward);//설정한 방향으로 화살을 발사한다.
    }
    
    void UCDoAction_Bow::End_DoAction()
    {
    	Super::End_DoAction();
    
    	CreateArrow();//Arrow 생성. 화살을 발사한 후 새로운 화살을 생성한다. 새로 생성된 화살은 손에 붙어있다.
    }
    
    void UCDoAction_Bow::OnBeginEquip()
    {
    	Super::OnBeginEquip();
    
    	OwnerCharacter->GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);//충돌을 꺼준다.
    
    	CreateArrow();//처음에 화살이 하나 붙어있도록 화살을 생성한다.
    }
    
    void UCDoAction_Bow::OnUnequip()
    {
    	Super::OnUnequip();
    
    	*Bending = 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);//BeginAction이면(활 발사) 활 시위가 붙으면 안되므로 false여야 한다.
    	bCheck &= (bAttachedString == true);//활 시위 붙는게 true여야 한다.
    
    	CheckFalse(bCheck);
    
    	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()
    {
    	if (World->bIsTearingDown == true)//World->bIsTearingDown는 World가 종료되었다는 의미
    		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);
    }

    헤더 추가

    • #include "Weapons/AddOns/CArrow.h"

     

    void UCDoAction_Bow::Begin_DoAction()

    • CheckNull(ArrowClass);//ArrowClass 있는지 확인.

      ACArrow* arrow = GetAttachedArrow();
      arrow->DetachFromActor(FDetachmentTransformRules(EDetachmentRule::KeepWorld,true));

      arrow->OnHit.AddDynamic(this, &UCDoAction_Bow::OnArrowHit);
      arrow->OnEndPlay.AddDynamic(this, &UCDoAction_Bow::OnArrowEndPlay);

      FVector forward = FQuat(OwnerCharacter->GetControlRotation()).GetForwardVector();
      arrow->Shoot(forward);

     

    void UCDoAction_Bow::End_DoAction()

    • CreateArrow();
      • 화살을 발사한 후에 새로운 화살을 생성한다.

     

    void UCDoAction_Bow::OnBeginEquip()

    • CreateArrow();
      • Bow 장착 시, 처음에 화살이 하나 붙어있도록 화살을 생성한다.

     

    void UCDoAction_Bow::OnUnequip()

    • for (int32 i = Arrows.Num() - 1; i >= 0; i--)
      {
      if (!!Arrows[i]->GetAttachParentActor())
      Arrows[i]->Destroy();
      }
      • 무기 장착을 해제하면 화살들을 제거해준다.

     

    void UCDoAction_Bow::CreateArrow()

    • if (World->bIsTearingDown == true)
              return;
      • World->bIsTearingDown는 World가 종료되었다는 의미
    • 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;

    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);

     

     

    ※ World가 종료되는 상황은 뭐가 있을까?

    • 게임이 종료되었을 때(=에디터가 꺼졌을 때)
    • 맵 이동 

     

     

     

     

    BP_CDoAction_Bow

    CDoAction_Bow기반 블루프린트 생성 - BP_CDoAction_Bow생성

    Arrow

    • Arrow Class: BP_CArrow 할당

     


     

    DA_Bow -  BP_CDoAction_Bow와 Hit Data 할당

     

     

     


     

     

     

     

    Skel_Mannequin - 소켓 수정

     

    Relative Location: -88,9, 23.0, -11.0

    Relative Rotation: -79.3, -9.9, 166.7

    Relative Scale: 1.1, 1.0, 1.0


     

     

    실행화면

     

     

     

     


     

     

     

     

    '⭐ Unreal Engine > UE RPG Skill' 카테고리의 다른 글

    [UE] 파쿠르: 벽 오르기  (0) 2023.07.19
    [UE] 파쿠르 구현하기  (0) 2023.07.18
    [UE] 활 시위에 손 맞추기  (0) 2023.07.10
    [UE] 활 시위 당기기  (0) 2023.07.07
    [UE] 활 조준  (0) 2023.07.06