지난번에 구현한 주먹 난타 스킬에 충돌이 적용되게 코드를 추가해보자. 마지막 공격에 적이 주먹에 맞아 나가떨어지는 모습을 보이도록 구현해보자. 주먹 스킬 외에 검 스킬도 추가하자. 오늘은 전진하면서 검으로 찌르는 일섬 스킬을 구현해볼 것이다.    

 

목차

     

     


     

     

     
    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 
    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
        CCamerModifier.h .cpp
    CGhostTrail.h .cpp
    CDoAction_Combo.h .cpp
    CSubAction_Fist.h .cpp
    CSubAction_Sword.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
     

     

     

     

     

     

    주먹 난타 스킬 충돌처리(SubAction_Fist - Hit)

     

    CAttachment에 충돌처리를 위한 DELEGATE들을 넣었다.

    • DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAttachmentBeginCollision);
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAttachmentEndCollision);

    • DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttachmentBeginOverlap, class ACharacter*, InAttacker, AActor*, InAttackCauser, class ACharacter*, InOther);
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAttachmentEndOverlap, class ACharacter*, InAttacker, class ACharacter*, InOther);

     

     

    SubAction은 자기 스스로가 HIt Data를 가지고 자기가 충돌했을때 Hit Data를 사용한다.

     

     

     

    Collision이 끝났을 때

    • FAttachmentEndCollision이 끝났을 때 중복된 객체를 제거해주고 Hit 카운트를 올려준다.

     


     

    CSubAction_Fist

     

     

    CSubAction_Fist.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Weapons/CSubAction.h"
    #include "Weapons/CWeaponStructures.h"
    #include "CSubAction_Fist.generated.h"
    
    UCLASS(Blueprintable)//블루프린트화해서 설정할 수 있도록 Blueprintable 명시
    class U2212_06_API UCSubAction_Fist : public UCSubAction
    {
    	GENERATED_BODY()
    
    public:
    	UPROPERTY(EditAnywhere, Category = "Action")
    		FDoActionData ActionData;
    
    	UPROPERTY(EditAnywhere, Category = "Action")
    		TArray<FHitData> HitDatas;
    
    	UPROPERTY(EditAnywhere, Category = "Action")
    		TSubclassOf<class ACGhostTrail> GhostTrailClass;
    
    public:
    	void Pressed() override;
    
    	void Begin_SubAction_Implementation() override;
    	void End_SubAction_Implementation() override;
    
    private:
    	UFUNCTION()
    		void OnAttachmentEndCollision();
    
    	UFUNCTION()
    		void OnAttachmentBeginOverlap(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther);
    
    private:
    	TArray<class ACharacter *> Hitted;
    	int32 HitIndex;//Hit Montage를 카운팅하기 위한 변수
    
    private:
    	class ACGhostTrail* GhostTrail;
    };

    SubAction에서 상속받은 함수 오버라이드

    • void Begin_SubAction_Implementation() override;

     

    함수 추가

    • UFUNCTION()
      void OnAttachmentEndCollision();
    • UFUNCTION()
      void OnAttachmentBeginOverlap(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther);

     

     

    CSubAction_Fist.cpp

    더보기
    #include "Weapons/SubActions/CSubAction_Fist.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/CStateComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Weapons/CAttachment.h"
    #include "Weapons/CDoAction.h"
    #include "Weapons/AddOns/CGhostTrail.h"
    
    void UCSubAction_Fist::Pressed()
    {
    	CheckFalse(State->IsIdleMode());
    	CheckTrue(State->IsSubActionMode());
    
    	Super::Pressed();
    
    	State->SetActionMode();
    	State->OnSubActionMode();
    
    	GhostTrail = CHelpers::Play_GhostTrail(GhostTrailClass, Owner);//GhostTrail 플레이.
    
    	ActionData.DoAction(Owner);
    }
    
    void UCSubAction_Fist::Begin_SubAction_Implementation()
    {
    	Super::Begin_SubAction_Implementation();
    
    	//DoAction의 충돌 이벤트 연결 제거(=DELEGATE 연결 제거)
    	Attachment->OnAttachmentEndCollision.Remove(DoAction, "OnAttachmentEndCollision");
    	Attachment->OnAttachmentBeginOverlap.Remove(DoAction, "OnAttachmentBeginOverlap");
    
    	//SubAction의 충돌 이벤트 연결(=DELEGATE 연결)
    	Attachment->OnAttachmentEndCollision.AddDynamic(this, &UCSubAction_Fist::OnAttachmentEndCollision);
    	Attachment->OnAttachmentBeginOverlap.AddDynamic(this, &UCSubAction_Fist::OnAttachmentBeginOverlap);
    }
    
    void UCSubAction_Fist::End_SubAction_Implementation()
    {
    	Super::End_SubAction_Implementation();
    
    	//SubAction의 충돌 이벤트 연결 제거(=DELEGATE 연결 제거)
    	Attachment->OnAttachmentEndCollision.Remove(this, "OnAttachmentEndCollision");
    	Attachment->OnAttachmentBeginOverlap.Remove(this, "OnAttachmentBeginOverlap");
    
    	//DoAction의 충돌 이벤트 다시 연결(=DELEGATE 연결)
    	Attachment->OnAttachmentEndCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndCollision);
    	Attachment->OnAttachmentBeginOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginOverlap);
    
    	//원래 상태로 돌려준다.
    	State->SetIdleMode();
    	State->OffSubActionMode();
    
    	Movement->Move();
    	Movement->DisableFixedCamera();
    
    	GhostTrail->Destroy();//GhostTrail를 지워준다.
    
    	HitIndex = 0;//HitIndex 초기화
    }
    
    void UCSubAction_Fist::OnAttachmentBeginOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOther)
    {
    	CheckNull(InOther);
    
    	for (ACharacter* character : Hitted)
    		CheckTrue(character == InOther);
    
    	Hitted.AddUnique(InOther);
    
    	CheckTrue(HitIndex >= HitDatas.Num());
    	HitDatas[HitIndex].SendDamage(Owner, InAttackCauser, InOther);
    }
    
    
    void UCSubAction_Fist::OnAttachmentEndCollision()
    {
    	Hitted.Empty();//사용이 끝난 Hit Data는 비워준다.
    
    	HitIndex++;//다음 Hit Montage가 나오도록 인덱스를 카운팅하여 다음 Hit Data로 넘어간다.
    }

    함수 정의

    • void UCSubAction_Fist::Begin_SubAction_Implementation()

     

    함수 정의

    • void UCSubAction_Fist::OnAttachmentBeginOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOther)
    • void UCSubAction_Fist::OnAttachmentEndCollision()
      • Hitted.Empty();
        • 사용이 끝난 Hit Data는 비워준다.
      • HitIndex++;
        • 다음 Hit Data로 넘어간다.

     

     

    ※ 참고:

    CSubAction_Fist

    CSubAction에 있는void Begin_SubAction_Implementation() End_SubAction_Implementation()를  오버라이드하여 사용하였다.

     


     

     

    BP_CSubAction_Fist에  Hit Data 넣어주기

     

    BP_CSubAction_Fist

    Hit Data 넣어주기

    • 주먹 난타 스킬의 주먹과 발차기가 5번이기 때문에 HitData를 5개 넣어준다.

     

    Ghost Trail Class

    • BP_CGhostTrail를 넣어준다.

     

     

     

    Fist_Skill_Hit_5_Montage

     

    Fist_Skill_Hit_5_Montage

    스킬 마지막 Hit Montage로 사용할 몽타주를 생성하고 프레임 마지막 부분에 End State을 추가한다.

    State Type은 Hitted로 설정해준다.

     


     

     

    Fist_Skill_Montage

     

    Fist_Skill_Montage

    노티파이 트랙 추가

    • 새로 만든 노티파이 트랙에 Collision을 추가해준다.

     

     

     

    실행화면

     

     

     


     

     

     

     

     

    전진 일섬 스킬 구현하기

     

     


     

    WeaponComponent

     

    WeaponComponent.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Components/ActorComponent.h"
    #include "CWeaponComponent.generated.h"
    
    UENUM(BlueprintType)
    enum class EWeaponType : uint8
    {
    	Fist, Sword, Hammer, Warp, Around, Bow, Max,
    };
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);
    
    UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
    class U2212_06_API UCWeaponComponent : public UActorComponent
    {
    	GENERATED_BODY()
    
    private://DataAsset을 받아온다.
    	UPROPERTY(EditAnywhere, Category = "DataAsset")
    		class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max];
    
    public: //무기 Type이 맞는지 확인해주는 함수들
    	FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }
    	FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; }
    	FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; }
    	FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; }
    	FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; }
    	FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; }
    	FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; }
    
    public:
    	UCWeaponComponent();
    
    protected:
    	virtual void BeginPlay() override;
    
    public:
    	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    
    private:
    	bool IsIdleMode();//StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다.
    
    public:
    	class ACAttachment* GetAttachment();
    	class UCEquipment* GetEquipment();
    	class UCDoAction* GetDoAction();
    	class UCSubAction* GetSubAction();
    
    public: //무기 세팅
    	void SetUnarmedMode();
    	void SetFistMode();
    	void SetSwordMode();
    	void SetHammerMode();
    	void SetWarpMode();
    	void SetAroundMode();
    	void SetBowMode();
    
    	void DoAction();
    	void SubAction_Pressed();
    	void SubAction_Released();
    
    private:
    	void SetMode(EWeaponType InType);
    	void ChangeType(EWeaponType InType);
    
    public: //무기가 바뀌었을때 통보해줄 delegate
    	FWeaponTypeChanged OnWeaponTypeChange;
    
    private:
    	class ACharacter* OwnerCharacter;
    
    	EWeaponType Type = EWeaponType::Max;
    };

    Tick을 실행시켜주는 코드

    • UCWeaponComponent::UCWeaponComponent() {
          PrimaryComponentTick.bCanEverTick = true; }

     

    함수 추가

    • virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

     

     

    WeaponComponent.cpp

    더보기
    #include "Components/CWeaponComponent.h"
    #include "Global.h"
    #include "CStateComponent.h"
    #include "GameFramework/Character.h"
    #include "Weapons/CWeaponAsset.h"
    #include "Weapons/CAttachment.h"
    #include "Weapons/CEquipment.h"
    #include "Weapons/CDoAction.h"
    #include "Weapons/CSubAction.h"
    
    UCWeaponComponent::UCWeaponComponent()
    {
    	//Tick을 실행시켜주는 코드
    	PrimaryComponentTick.bCanEverTick = true;
    }
    
    void UCWeaponComponent::BeginPlay()
    {
    	Super::BeginPlay();
    
    	OwnerCharacter = Cast<ACharacter>(GetOwner());
    	for (int32 i=0; i < (int32)EWeaponType::Max; i++)
    	{
    		if (!!DataAssets[i]) //DataAssets[i]이 있다면(=무기가 할당되어 있다면)
    			DataAssets[i]->BeginPlay(OwnerCharacter);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다.
    	}
    }
    
    void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
    {
    	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    	if (!!GetSubAction())//SubAction이 있다면
    		GetSubAction()->Tick(DeltaTime);//SubAction의 Tick을 콜 해준다.
    }
    
    bool UCWeaponComponent::IsIdleMode()
    {
    	return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
    }
    
    ACAttachment* UCWeaponComponent::GetAttachment()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetAttachment();
    }
    
    UCEquipment* UCWeaponComponent::GetEquipment()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetEquipment();
    }
    
    UCDoAction* UCWeaponComponent::GetDoAction()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetDoAction();
    }
    
    UCSubAction* UCWeaponComponent::GetSubAction()
    {
    	CheckTrueResult(IsUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetSubAction();
    }
    
    void UCWeaponComponent::SetUnarmedMode()
    {
    	GetEquipment()->Unequip();
    
    	ChangeType(EWeaponType::Max);
    }
    
    void UCWeaponComponent::SetFistMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Fist);
    }
    
    void UCWeaponComponent::SetSwordMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Sword);
    }
    
    void UCWeaponComponent::SetHammerMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Hammer);
    }
    
    void UCWeaponComponent::SetWarpMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Warp);
    }
    
    void UCWeaponComponent::SetAroundMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Around);
    }
    
    void UCWeaponComponent::SetBowMode()
    {
    	CheckFalse(IsIdleMode());
    
    	SetMode(EWeaponType::Bow);
    }
    
    void UCWeaponComponent::SetMode(EWeaponType InType)
    {
    	if (Type == InType)
    	{
    		SetUnarmedMode();
    
    		return;
    	}
    	else if (IsUnarmedMode() == false)
    	{
    		GetEquipment()->Unequip();
    	}
    
    	if (!!DataAssets[(int32)InType])
    	{
    		DataAssets[(int32)InType]->GetEquipment()->Equip();
    
    		ChangeType(InType);
    	}
    }
    
    void UCWeaponComponent::ChangeType(EWeaponType InType)
    {
    	EWeaponType prevType = Type;
    	Type = InType;
    
    	if (OnWeaponTypeChange.IsBound())
    		OnWeaponTypeChange.Broadcast(prevType, InType);
    }
    
    void UCWeaponComponent::DoAction()
    {
    	if (!!GetDoAction())
    		GetDoAction()->DoAction();
    }
    
    void UCWeaponComponent::SubAction_Pressed()
    {
    	if (!!GetSubAction())
    		GetSubAction()->Pressed();
    }
    
    void UCWeaponComponent::SubAction_Released()
    {
    	if (!!GetSubAction())
    		GetSubAction()->Released();
    }

    Tick 실행

    • UCWeaponComponent::UCWeaponComponent() {
          PrimaryComponentTick.bCanEverTick = true;
      }

     

    함수 정의

    • void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)

     

     


     

    CSubAction_Sword 생성

     

    새 C++ 클래스  - CSubAction - CSubAction_Sword 생성

     

     

    CSubAction_Sword .h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Weapons/CSubAction.h"
    #include "Weapons/CWeaponStructures.h"
    #include "Kismet/KismetSystemLibrary.h"
    #include "CSubAction_Sword.generated.h"
    
    UCLASS(Blueprintable)
    class U2212_06_API UCSubAction_Sword : public UCSubAction
    {
    	GENERATED_BODY()
    
    private:
        UPROPERTY(EditDefaultsOnly, Category = "Trace")
            float Distance = 1000.0;//이동거리
    
        UPROPERTY(EditDefaultsOnly, Category = "Trace")
            float Speed = 200;//속도
    
        UPROPERTY(EditDefaultsOnly, Category = "Trace")
            TEnumAsByte<EDrawDebugTrace::Type> DrawDebug;//디버그용
    
    private:
        UPROPERTY(EditDefaultsOnly, Category = "Action")
            FDoActionData ActionData;
    
        UPROPERTY(EditDefaultsOnly, Category = "Action")
            FHitData HitData;
    
    private:
        UPROPERTY(EditAnywhere, Category = "Add-On")
            TSubclassOf<class ACGhostTrail> GhostTrailClass;
    
    public:
        void Pressed() override;
        void Begin_SubAction_Implementation() override;
        void End_SubAction_Implementation() override;
        void Tick_Implementation(float InDeltaTime) override;
    
    private:
        bool bMoving;
    
        FVector Start;
        FVector End;
    
    private:
        class ACGhostTrail* GhostTrail;
    };

     

     

     

    CSubAction_Sword .cpp

    더보기
    #include "Weapons/SubActions/CSubAction_Sword.h"
    #include "Global.h"
    #include "Weapons/CAttachment.h"
    #include "Weapons/CDoAction.h"
    #include "GameFramework/Character.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/CStateComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Weapons/AddOns/CGhostTrail.h"
    
    void UCSubAction_Sword::Pressed()
    {
    	CheckFalse(State->IsIdleMode());
    	CheckTrue(State->IsSubActionMode());
    
    	Super::Pressed();
    
    
    	State->SetActionMode();
    	State->OnSubActionMode();
    
    	GhostTrail = CHelpers::Play_GhostTrail(GhostTrailClass, Owner);
    
    	ActionData.DoAction(Owner);
    }
    
    void UCSubAction_Sword::Begin_SubAction_Implementation()
    {
    	Super::Begin_SubAction_Implementation();
    
    	bMoving = true;//이동할 수 있게 설정해준다.
    
    	Start = Owner->GetActorLocation();//시작 위치 = Owner의 위치
    	End = Start + Owner->GetActorForwardVector() * Distance;//끝 위치 = 시작 위치 + Owner의 앞 방향으로 (헤더에서 기본값 설정한)Distance 거리
    
    	//Draw Debug
    	if (DrawDebug == EDrawDebugTrace::ForDuration)
    		DrawDebugDirectionalArrow(Owner->GetWorld(), Start, End, 25, FColor::Green, true, 5, 0, 3);
    }
    
    void UCSubAction_Sword::End_SubAction_Implementation()
    {
    	Super::End_SubAction_Implementation();
    
    	bMoving = false;//동작이 끝났으니 이동하지 못하게 false로 만들어준다.
    
    	State->SetIdleMode();
    	State->OffSubActionMode();
    
    	Movement->Move();
    	Movement->DisableFixedCamera();
    
    	if (!!GhostTrail)
    		GhostTrail->Destroy();//GhostTrail를 없앤다.
    }
    
    void UCSubAction_Sword::Tick_Implementation(float InDeltaTime)
    {
    	Super::Tick_Implementation(InDeltaTime);
    	CheckFalse(bMoving);
    
    	FVector location = Owner->GetActorLocation();
    	float radius = Owner->GetCapsuleComponent()->GetScaledCapsuleRadius();
    
    	//location이 radius 오차값 내에서 End와 같다면
    	//Owner(여기서는 Player)의 위치가 radius 오차값 내의 End 위치에 도달했다면
    	if(location.Equals(End, radius))
    	{
    		bMoving = false;//일섬 공격이 끝났으니 이동하지 못하게 false로 만들어준다.
    		Start = End = Owner->GetActorLocation();//시작과 끝 위치를 현재 Owner의 위치로 설정해준다. 초기화.
    
    		return;
    	}
    
    	FVector direction = (End - Start).GetSafeNormal2D();
    	Owner->AddActorWorldOffset(direction * Speed, true);
    }

     

     

     

     

     

     

    BP_CSubAction_Sword 생성

    CSubAction_Sword 기반 블루프린트 클래스 생성 - BP_CSubAction_Sword 생성

     

     

     

    코드로 설정한 Trace 데이터가 들어온다.

     

    Action Data를 설정해준다.

     

    Hit Data를 설정해준다.

     

    Ghost Trail Class를 BP_CGhostTrail_Sword로 설정한다.

     

     

     

     

     

     


     

     

    Sword_Skill_Montage 생성

     

    노트파이 트랙 추가

    • SubAction 추가
    • Collision 추가

     

     

    BP_CGhostTrail_Sword 생성

     

     

    BP_CGhostTrail 복제 - BP_CGhostTrail_Sword 생성

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


     

     

    실행화면