주먹 난타 스킬에 잔상효과를 추가해보자. 

 

 

목차

     

     


     

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

     

     

     

     

    잔상 Material 만들기

     


     

    BaseColor 값과  Emissive 값의 차이

     

    Base Color

    • sRGB
    • 0~1의 값.

     

    Emissive

    • RGB.
    • 1이상의 값 허용.
    • 값이 커질수록 발광효과가 증폭된다.
    • Emssive Color는 외곽선이라고 생각하면 된다.

     


     

    M_GhostTrail 생성

     

    머터리얼 - M_GhostTrail 생성

    Blend Mode: Translucent

    • Translucent로 변경하여 반투명으로 만들어준다.
    • Translucent로 변경하면 오퍼시티(Opacity) 항목이 나온다.

     

    Usage: Used with SkeletalMesh 체크

    • 바깥을 클릭한 후 좌측 카테고리를 확인한다.
    • Usage에 Used with SkeletalMesh를 체크해준다.
    • " SkeletalMesh에 붙을 Material로 사용하겠다. "는 의미

     

     

    Fresnel이란?

    더보기

    Fresnel은 광학에서 사용되는 용어로, 빛이 특정한 경계면을 통과할 때 발생하는 현상을 설명하는 데 사용된다. 이 용어는 프랑스의 물리학자 Augustin-Jean Fresnel(프레넬)의 이름에서 유래했다.

    Fresnel은 빛이 두 개의 다른 매질로 구성된 경계면을 통과할 때 반사와 굴절이 동시에 발생한다는 사실을 제시했다. 이를 Fresnel의 반사와 Fresnel의 굴절이라고 합니다.

    Fresnel의 반사는 빛이 경계면에서 일어나는 반사를 의미한다. 반사는 입사각과 투과각이 같은 경우에 일어나며, 경계면에서 반사되는 빛의 양은 입사각과 매질의 굴절률에 따라 달라진다.

    Fresnel의 굴절은 빛이 경계면을 통과하여 다른 매질로 들어갈 때 발생하는 굴절 현상을 설명한다. 굴절은 빛의 속도와 굴절률이 다른 두 매질 사이에서 일어나며, 스넬의 법칙에 따라 입사각과 투과각이 관계된다. 굴절률이 다른 두 매질 사이에서는 빛의 진행 방향이 바뀌게 된다.

    Fresnel의 반사와 굴절은 광학적 장치나 경계면에서 발생하는 빛의 현상을 이해하는 데 중요한 개념이다. 이러한 현상은 광학 렌즈, 미러, 굴절 망원경, 프리즘 등 다양한 광학 장치와 물체의 표면 상호작용 등 다양한 분야에서 응용된다.

     

     

     

     

    M_GhostTrail_Inst 생성

     


     

     

     

     

    잔상효과 구현하기

     


     

    CHelpers.h  코드 추가

     

    CHelpers.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Particles/ParticleSystem.h"
    #include "NiagaraSystem.h"
    #include "NiagaraFunctionLibrary.h"
    #include "GameFramework/Character.h"
    #include "Components/CapsuleComponent.h"
    #include "Weapons/AddOns/CGhostTrail.h"
    
    #define CheckTrue(x) { if(x == true) return; }
    #define CheckTrueResult(x, y) { if(x == true) return y; }
    
    #define CheckFalse(x) { if(x == false) return;}
    #define CheckFalseResult(x, y) { if(x == false) return y;}
    
    #define CheckNull(x) { if(x == nullptr) return;}
    #define CheckNullResult(x, y) { if(x == nullptr) return y;}
    
    
    #define CreateTextRender()\
    {\
    	CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Tex", Root);\
    	Text->SetRelativeLocation(FVector(0, 0, 100));\
    	Text->SetRelativeRotation(FRotator(0, 180, 0));\
    	Text->SetRelativeScale3D(FVector(2));\
    	Text->TextRenderColor = FColor::Red;\
    	Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;\
    	Text->Text = FText::FromString(GetName().Replace(L"Default__", L""));\
    }
    
    
    class U2212_06_API CHelpers
    {
    public:
    	template<typename T>
    	static void CreateComponent(AActor* InActor, T** OutComponent, FName InName, USceneComponent* InParent = nullptr, FName InSocketName = NAME_None)
    	{
    		*OutComponent = InActor->CreateDefaultSubobject<T>(InName);
    
    		if (!!InParent)
    		{
    			(*OutComponent)->SetupAttachment(InParent, InSocketName); //이렇게 사용하면 Socket Name에 _를 사용하면 안 된다.
    
    			return;
    		}
    
    		InActor->SetRootComponent(*OutComponent);
    	}
    
    	//CreateActorComponent 추가
    	template<typename T>
    	static void CreateActorComponent(AActor* InActor, T** OutComponent, FName InName)
    	{
    		*OutComponent = InActor->CreateDefaultSubobject<T>(InName);
    	}
    
    	template<typename T>
    	static void GetAsset(T** OutObject, FString InPath)
    	{
    		ConstructorHelpers::FObjectFinder<T> asset(*InPath);
    		*OutObject = asset.Object;
    	}
    
    	template<typename T>
    	static void GetAssetDynamic(T** OutObject, FString InPath)
    	{
    		*OutObject = Cast<T>(StaticLoadObject(T::StaticClass(), nullptr, *InPath));
    	}
    
    	template<typename T>
    	static void GetClass(TSubclassOf<T>* OutClass, FString InPath)
    	{
    		ConstructorHelpers::FClassFinder<T> asset(*InPath);
    		*OutClass = asset.Class;
    	}
    
    	template<typename T>
    	static T* FindActor(UWorld* InWorld)
    	{
    		for (AActor* actor : InWorld->GetCurrentLevel()->Actors)
    		{
    			if (!!actor && actor->IsA<T>())
    				return Cast<T>(actor);
    		}
    
    		return nullptr;
    	}
    
    	template<typename T>
    	static void FindActors(UWorld* InWorld, TArray<T*>& OutActors)
    	{
    		for (AActor* actor : InWorld->GetCurrentLevel()->Actors)
    		{
    			if (!!actor && actor->IsA<T>())
    				OutActors.Add(Cast<T>(actor));
    		}
    	}
    
    	template<typename T>
    	static T* GetComponent(AActor* InActor)
    	{
    		return Cast<T>(InActor->GetComponentByClass(T::StaticClass()));
    	}
    
    	template<typename T>
    	static T* GetComponent(AActor* InActor, const FString& InName)
    	{
    		TArray<T*> components;
    		InActor->GetComponents<T>(components);
    
    		for (T* component : components)
    		{
    			if (component->GetName() == InName)
    				return component;
    		}
    
    		return nullptr;
    	}
    
    	static void AttachTo(AActor* InActor, USceneComponent* InParent, FName InSocketName)
    	{
    		InActor->AttachToComponent(InParent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), InSocketName);
    	}
    
    	static void PlayEffect(UWorld* InWorld, UFXSystemAsset* InAsset, const FTransform& InTransform, USkeletalMeshComponent* InMesh = nullptr, FName InSocketName = NAME_None)
    	{
    		UParticleSystem* particle = Cast<UParticleSystem>(InAsset);
    		UNiagaraSystem* niagara = Cast<UNiagaraSystem>(InAsset);
    
    		FVector location = InTransform.GetLocation();
    		FRotator rotation = FRotator(InTransform.GetRotation());
    		FVector scale = InTransform.GetScale3D();
    
    		if(!!InMesh) //InMesh에 붙어있다면
    		{
    			if(!!particle) //particle이라면
    			{
    				UGameplayStatics::SpawnEmitterAttached(particle, InMesh, InSocketName, location, rotation, scale);
    				return;
    			}
    
    			if(!!niagara) //niagara라면
    			{
    				UNiagaraFunctionLibrary::SpawnSystemAttached(niagara, InMesh, InSocketName, location, rotation, scale, EAttachLocation::KeepRelativeOffset, true, ENCPoolMethod::None);//Pooling풀링 사용.
    				return;
    			}
    		}
    
    		if(!!particle) //어디에 붙어있지 않고 particle이면
    		{
    			UGameplayStatics::SpawnEmitterAtLocation(InWorld, particle, InTransform);//해당 위치에서 실행
    
    			return;
    		}
    
    		if (!!niagara) //어디에 붙어있지 않고 niagara면
    		{
    			UNiagaraFunctionLibrary::SpawnSystemAtLocation(InWorld, niagara, location, rotation, scale);//해당 위치에서 실행
    
    			return;
    		}
    	}
    
    	static ACGhostTrail* Play_GhostTrail(TSubclassOf<ACGhostTrail>& InClass, class ACharacter* InOwner)
    	{
    		CheckNullResult(InClass, nullptr);
    		CheckNullResult(InOwner, nullptr);
    
    
    		FActorSpawnParameters params;
    		params.Owner = InOwner;
    		params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
    
    		FVector location = InOwner->GetActorLocation();
    		location.Z -= InOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
    
    		FTransform transform;
    		transform.SetTranslation(location);
    
    		return InOwner->GetWorld()->SpawnActor<ACGhostTrail>(InClass, transform, params);
    	}
    };

    헤더 추가

    • #include "GameFramework/Character.h"
    • #include "Components/CapsuleComponent.h"
    • #include "Weapons/AddOns/CGhostTrail.h"

     

     

    추가된 코드

    static ACGhostTrail* Play_GhostTrail(TSubclassOf<ACGhostTrail>& InClass, class ACharacter* InOwner)
    {
        //InClass나 InOwner 둘 중 하나라도 없으면 플레이 할 수 없다.
        CheckNullResult(InClass, nullptr);//InClass가 없다면 nullptr
        CheckNullResult(InOwner, nullptr);//InOwner가 없다면 nullptr
    
    
        FActorSpawnParameters params;
        params.Owner = InOwner;
        params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;//충돌되었어도 반드시 Spawn되게 해준다.
    
        FVector location = InOwner->GetActorLocation();//Spawn위치 설정을 위한 location.
        location.Z -= InOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();//발 아래로 보내기 위해 캡슐 높이의 절반만큼 내려준다.
    
        FTransform transform;
        transform.SetTranslation(location);//location 위치로 이동시킨다.
    
        return InOwner->GetWorld()->SpawnActor<ACGhostTrail>(InClass, transform, params);
    }

     

     

    PoseableMesh 사용하기

     

    FPS 1인칭 시점을 구현할 때 손을 총에 붙이기 위해 PoseableMesh를 사용하였다.

    PoseableMesh는 Bone을 수정한다.

     

    자기 자신이 Mesh를 참조할 때 Mesh Animation 있으면 Animation을 캡처해서 수정, Animation이 없으면 Bone을 수정한다.

     

    오늘 구현하는 잔상효과에는 애니메이션을 캡처해서 구현한다.

    추후에 활 휘는것을 구현할 때도 사용할 것이다.


     

     

    CGhostTrail 생성

     

    새 C++ 클래스 - Actor - CGhostTrail 생성

     

    CGhostTrail.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "CGhostTrail.generated.h"
    
    UCLASS()
    class U2212_06_API ACGhostTrail : public AActor
    {
    	GENERATED_BODY()
    
    private:
    	UPROPERTY(EditDefaultsOnly, Category = "Capture")
    		float StartDelay = 0;//키를 누른 후 몇 초 지난 후에 플레이할 지 정할 변수
    
    	UPROPERTY(EditDefaultsOnly, Category = "Capture")
    		float Interval = 0.25f;//애니메이션이 캡처되는 간격
    
    	UPROPERTY(EditDefaultsOnly, Category = "Capture")
    		FLinearColor Color = FLinearColor(1, 1, 1, 1);
    
    	UPROPERTY(EditDefaultsOnly, Category = "Capture")
    		float Exponent = 1;//일정시간에 연속적으로 들어오는 값
    
    	UPROPERTY(EditDefaultsOnly, Category = "Capture")
    		FVector Scale = FVector::OneVector;//GhostTrail 크기
    
    	UPROPERTY(EditDefaultsOnly, Category = "Capture")
    		FVector ScaleAmount = FVector::ZeroVector;//GhostTrail 위치
    private:
    	UPROPERTY(VisibleDefaultsOnly)
    		class UPoseableMeshComponent* Mesh;
    
    public:	
    	ACGhostTrail();
    
    protected:
    	virtual void BeginPlay() override;
    	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
    
    private:
    	class ACharacter* Owner;
    	class UMaterialInstanceDynamic* Material;
    
    	FTimerHandle TimerHandle;//타이머 설정을 위한 변수.
    };

     

     

     

    CGhostTrail.cpp

    더보기
    #include "Weapons/AddOns/CGhostTrail.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/PoseableMeshComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Materials/MaterialInstanceConstant.h"
    #include "Materials/MaterialInstanceDynamic.h"
    
    ACGhostTrail::ACGhostTrail()
    {
    	CHelpers::CreateComponent<UPoseableMeshComponent>(this, &Mesh, "Mesh");//PoseableMesh 생성
    }
    
    void ACGhostTrail::BeginPlay()
    {
    	Super::BeginPlay();
    
    	UMaterialInstanceConstant* material;
    	CHelpers::GetAssetDynamic<UMaterialInstanceConstant>(&material, "MaterialInstanceConstant'/Game/Materials/M_GhostTrail_Inst.M_GhostTrail_Inst'");
    
    	Material = UMaterialInstanceDynamic::Create(material, this);
    	Material->SetVectorParameterValue("Color", Color);
    	Material->SetScalarParameterValue("Exponent", Exponent);
    
    	Owner = Cast<ACharacter>(GetOwner());
    
    
    	Mesh->SetVisibility(false);
    	Mesh->SetSkeletalMesh(Owner->GetMesh()->SkeletalMesh);//캡처할 매쉬
    	//SkeletalComponent를 사용하는 이유는? SkeletalComponent안에 Animation이 들어가기 때문이다.
    	Mesh->CopyPoseFromSkeletalComponent(Owner->GetMesh());//BeginPlay 때 미리 한번 캡처 해줘야 한다.
    	Mesh->SetRelativeScale3D(Scale);
    
    	for (int32 i = 0; i < Owner->GetMesh()->SkeletalMesh->Materials.Num(); i++)
    		Mesh->SetMaterial(i, Material);//Material 할당.
    
    
    	FTimerDelegate timerDelegate;
    	timerDelegate.BindLambda([=]()
    	{
    		if (Mesh->IsVisible() == false)//GhostTrail의 Mesh가 숨겨져있었다면
    			Mesh->ToggleVisibility();//켜준다.
    
    		//위치보정
    		float height = Owner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
    		SetActorLocation(Owner->GetActorLocation() - FVector(ScaleAmount.X, ScaleAmount.Y, height - ScaleAmount.Z));
    		SetActorRotation(Owner->GetActorRotation() + FRotator(0, -90, 0));
    
    		Mesh->CopyPoseFromSkeletalComponent(Owner->GetMesh());
    	});
    
    	GetWorld()->GetTimerManager().SetTimer(TimerHandle, timerDelegate, Interval, true, StartDelay);
    }
    
    void ACGhostTrail::EndPlay(const EEndPlayReason::Type EndPlayReason)
    {
    	Super::EndPlay(EndPlayReason);
    
    	GetWorld()->GetTimerManager().ClearTimer(TimerHandle);//TimerHandle 해제.
    }

     

     

     


     

     

    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 End_SubAction_Implementation() override;
    
    private:
    	TArray<class ACharacter *> Hitted;
    	int32 HitIndex;
    
    private:
    	class ACGhostTrail* GhostTrail;
    };

    변수 추가

    • UPROPERTY(EditAnywhere, Category = "Action")
      TSubclassOf<class ACGhostTrailGhostTrailClass;

    변수 추가

    • class ACGhostTrailGhostTrail;

     

     

     

    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::End_SubAction_Implementation()
    {
    	Super::End_SubAction_Implementation();
    
    	//원래 상태로 돌려준다.
    	State->SetIdleMode();
    	State->OffSubActionMode();
    
    	Movement->Move();
    	Movement->DisableFixedCamera();
    
    	GhostTrail->Destroy();//GhostTrail를 지워준다.
    }

    키를 눌렀을 때 GhostTrail를 플레이한다.

    • void UCSubAction_Fist::Pressed() {
      GhostTrail = CHelpers::Play_GhostTrail(GhostTrailClass, Owner); }

     

    스킬이 끝날 때 GhostTrail를 지워준다.

    • void UCSubAction_Fist::End_SubAction_Implementation() {
      GhostTrail->Destroy(); }

     

     


     

    BP_CGhostTrail 생성

     

    CGhostTrail 기반 블루프린트 클래스 생성 - BP_CGhostTrail 생성

    Capture 카테고리 값 설정

    • Start Delay: 0.5
      • 키를 누른 후 실행될 때까지 걸리는 시간(초)
    • Interval: 0.1
      • 캡처가 되는 간격
    • Color
      • 잔상효과 색
    • Scale
      • 설정한 Mesh 대비 Scale

     


     

     

    BP_CSubAction_Fist에  Ghost Trail Class 할당

     

    Ghost Trail Classes:  BP_CGhostTrail 할당

     

     


     

     

    실행화면