Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

공부용 이모저모

UE4 - 잔상 효과 만들기(SPAWN/TIME LINE/POSEABLEMESH) 본문

UE4 - C++

UE4 - 잔상 효과 만들기(SPAWN/TIME LINE/POSEABLEMESH)

불타는버스 2021. 9. 24. 16:17

참고 사이트 : https://mingyu0403.tistory.com/247

https://www.youtube.com/watch?v=9yftOwWp48A 

 

[Unreal BP] 잔상 효과 만들기

방법 1. 액터를 하나 만든다. 2. 컴포넌트에 PoseableMesh를 추가하고, 설정 값을 바꾼다. 3. 이벤트가 들어오면 2번에서 만들었던 액터를 생성한다. (잔상이니까 내 위치에 생성.) 4. 생성하고 난 뒤,

mingyu0403.tistory.com

액터를 상속받는 CPP 클래스 하나 생성

우선 움직일때마다 액터를 생성하는 부분부터 시작한다

일정 시간마다 부르기 위해 타임라인을 만들어본다

우선 타임라인을 이용한 CurveFloat가 어떻게 나오는지 체크한다.

 

ABCharacter.h 추가사항

#include "Components/TimelineComponent.h"

class ARENABATTLE_API AABCharacter : public ACharacter
{
	GENERATED_BODY()

...
protected:
	FTimeline CurveTimeLine;
private:
	UFUNCTION()
		void TimelineProgress(float Value);
public:
	UPROPERTY(EditAnywhere, Category = TimeLine)
		UCurveFloat* CurveFloat;
}

ABCharacter.cpp 추가사항

 

void AABCharacter::TimelineProgress(float Value)
{
	ABLOG(Warning, TEXT("%f"), Value);
}

// Called when the game starts or when spawned
void AABCharacter::BeginPlay()
{
	Super::BeginPlay();
	
    //커브float가 있다면
	if (CurveFloat)
	{
    	//타임라인 생성
		FOnTimelineFloat TimelineProgress;
    	//해당 함수를 이어준다.
		TimelineProgress.BindUFunction(this, FName("TimelineProgress"));
        //CurveFloat값을 매개변수로 해서 받아낸다.
		CurveTimeLine.AddInterpFloat(CurveFloat, TimelineProgress);		
		CurveTimeLine.SetLooping(true);		//루프할것인가?

		//타임라인 시작
		CurveTimeLine.PlayFromStart();
	}
}

// Called every frame
void AABCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
    //타임라인을 틱단위로 실행 시킨다
	CurveTimeLine.TickTimeline(DeltaTime);
}

 

커브 생성
CurveFloat로 새로 생성
만들어놓은 CurveFloat삽입.
CurveFloat에 두 점을 만들고 하나는 0,0. 하나는 1,0.1로 만든다
1초단위로 0부터 0.1까지 수치가 올라가는걸 확인 할 수 있다.

중간중간 섬세한 조정은 이렇게 사용하는거도 좋지만,

잔상은 정확한 타이밍에 나와야 하니 타임라인이 끝나는 시점을

기준으로 적용 시키기로 한다

 

헤더
    
UPROPERTY(EditDefaultsOnly, Category = "Spawning")
	TSubclassOf<AActor> ActorToSpawn;
    
UFUNCTION()
	void FinishTimeLine();
        
        
CPP
  
void AABCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	if (CurveFloat)
	{
		FOnTimelineFloat TimelineProgress;
		TimelineProgress.BindUFunction(this, FName("TimelineProgress"));
		FOnTimelineEvent FinishTimeLine;
		FinishTimeLine.BindUFunction(this, FName("FinishTimeLine"));
		CurveTimeLine.AddInterpFloat(CurveFloat, TimelineProgress);		
		CurveTimeLine.SetTimelineFinishedFunc(FinishTimeLine);
		CurveTimeLine.SetLooping(false);		//루프 하지 말고 끝났을때 리셋시키게

		CurveTimeLine.PlayFromStart();
	}
}
  
void AABCharacter::FinishTimeLine()
{
	FActorSpawnParameters SpawnParams;
	SpawnParams.Owner = this;

	FRotator rotator = GetActorRotation();
	FVector  SpawnLocation = GetActorLocation();

	//월드상에서 액터 스폰을 요청한다.(액터 종류,액터 방향,회전,액터의 주인)
	auto GTrail = Cast<AABGhostTrail>(GetWorld()->SpawnActor<AActor>(ActorToSpawn, SpawnLocation, rotator, SpawnParams));
	if (GTrail)
	{
		GTrail->Init(GetMesh());
	}

	//처음이라면
	if (CurveTimeLine.GetPlaybackPosition() == 0.0f)
		CurveTimeLine.Play();
	//끝났을때
	else if(CurveTimeLine.GetPlaybackPosition() == CurveTimeLine.GetTimelineLength())
		CurveTimeLine.Reverse();
}

 

처음에 만들었던 GhostTrail Actor을 넣어준다.

GhostTrail.h

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Components/TimelineComponent.h"
#include "ArenaBattle.h"
#include "GameFramework/Character.h"
#include "ABCharacter.generated.h"
enum class EControlMode
{
	GTA,
	DIABLO
};

UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AABCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	//카메라모드 구현
	void SetControlMode();
	EControlMode CurrentControlMode = EControlMode::GTA;		//변수
	FVector DirectionToMove = FVector::ZeroVector;

	float ArmLengthTo = 0.0f;
	FRotator ArmRotationTo = FRotator::ZeroRotator;
	float ArmLengthSpeed = 0.0f;
	float ArmRotationSpeed = 0.0f;
	FTimeline CurveTimeLine;
public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	virtual void PostInitializeComponents() override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	UPROPERTY(VisibleAnyWhere, Category = Camera)
		USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnyWhere, Category = Camera)
		UCameraComponent* CameraS;

	UPROPERTY(EditAnywhere, Category = TimeLine)
		UCurveFloat* CurveFloat;
private:
	void UpDown(float NewAxisValue);
	void LeftRight(float NewAxisValue);
	void LookUp(float NewAxisValue);
	void Turn(float NewAxisValue);
	void ViewChange();
	void JumpStart();
	void Attack();

	//언리얼 함수
	UFUNCTION()
		void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);

	UFUNCTION()
		void TimelineProgress(float Value);
	UFUNCTION()
		void FinishTimeLine();

	void AttackStartComboState();		//공격이 시작 될 때 불러주는 함수
	void AttackEndComboState();			//공격이 종료 될 때 불러주는 함수

private:
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		bool IsAttacking;
	
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		bool CanNextCombo;		// 다음 콤보로 진행이 가능한가?

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		bool IsComboInputOn;	// 콤보가 입력되었는가?

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		int32 CurrentCombo;		// 현재 진행중인 콤보의 번호

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		int32 MaxCombo;			// 최대치 콤보 개수

	UPROPERTY(EditDefaultsOnly, Category = "Spawning")
		TSubclassOf<AActor> ActorToSpawn;

	UPROPERTY()
		class UABAnimInstance* ABAnim;
};

 

cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABGhostTrail.h"

// Sets default values
AABGhostTrail::AABGhostTrail()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	bSpawned = false;
}

// Called when the game starts or when spawned
void AABGhostTrail::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AABGhostTrail::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bSpawned == true)
	{
		fCount -= DeltaTime;
		if (fCount < 0)
		{
			UE_LOG(LogTemp, Warning, TEXT("GhostDestroy"));
			Destroy();
		}
	}
}

void AABGhostTrail::Init(USkeletalMeshComponent* Pawn)
{
	UE_LOG(LogTemp, Warning, TEXT("GhostBegin"));
	fCount = 0.5f;
	bSpawned = true;
}

 

액터의 생성 삭제가 반복된다

이제 액터에 메쉬 정보값을 보내주고 굳혀본다.

 

ABGhostTrail.h

class UPoseableMeshComponent;
class UMaterialInstance;
class UMaterialInstanceDynamic;
class USkeletalMeshComponent;


private:
	UPoseableMeshComponent* PoseableMesh;	//메쉬의 포즈정보만 복사해오는 컴포넌트
	UMaterial* GhostMaterial;				//효과를 주기위한 매테리얼(림라이트를 쓰기로했다)
	TArray<UMaterialInstanceDynamic*> Materials;	//매테리얼은 한 메쉬당 한개이므로 여러개 생성
    
	float	fAlphaCount;
	float	fQuarterAlpha;
	bool	bSpawned;

CPP

#include "Components/PoseableMeshComponent.h"
#include "Kismet/KismetMaterialLibrary.h"

AABGhostTrail::AABGhostTrail()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	bSpawned = false;

	PoseableMesh = CreateDefaultSubobject<UPoseableMeshComponent>(TEXT("POSEABLEMESH"));
	RootComponent = PoseableMesh;

	//복사할 대상의 스켈레탈 메시를 불러온다
	ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_PoseMesh(TEXT("SkeletalMesh'/Game/Tekken/Character/newHwoarang.newHwoarang'"));
	if (SK_PoseMesh.Succeeded())
	{
		PoseableMesh->SetSkeletalMesh(SK_PoseMesh.Object);
	}
	//바꿔줄 메테리얼 효과를 불러온다.
	ConstructorHelpers::FObjectFinder<UMaterial> M_GhostTail(TEXT("Material'/Game/Tekken/Character/MaterialFunction/RimLight_Empty.RimLight_Empty'"));
	if (M_GhostTail.Succeeded())
	{
		GhostMaterial = M_GhostTail.Object;
	}
}

// Called every frame
void AABGhostTrail::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bSpawned == true)
	{
		fAlphaCount -= DeltaTime;

		for (int i = 0; i < Materials.Num(); i++)
		{
        	//시간이 지날수록 Opacity의 수치가 낮아져야한다.
            //Opacity는 아래 스크린샷에 생성한 ScalarParameter의 이름이다.
			Materials[i]->SetScalarParameterValue("Opacity", fAlphaCount / fQuarterAlpha);
		}
		// 투명도가 0이 되어버리면 삭제
		if (fAlphaCount < 0)
		{
			UE_LOG(LogTemp, Warning, TEXT("GhostDestroy"));
			Destroy();
		}
	}
}

void AABGhostTrail::Init(USkeletalMeshComponent* Pawn)
{
	UE_LOG(LogTemp, Warning, TEXT("GhostBegin"));
    //생성을 요청한 객체의 SkeletalMesh 포즈를 복제한다.
	PoseableMesh->CopyPoseFromSkeletalComponent(Pawn);
    
    //매테리얼 개수를 얻기위해
	TArray<UMaterialInterface*> Mats = PoseableMesh->GetMaterials();

	for (int i = 0; i < Mats.Num(); i++)
	{
    	//개수만큼 임의로 추가한 매테리얼을 복제한다.
		Materials.Add(UKismetMaterialLibrary::CreateDynamicMaterialInstance(GetWorld(), GhostMaterial));
		//복제한 매테리얼을 메쉬에 적용
        PoseableMesh->SetMaterial(i, Materials[i]);
	}

	//수치가 적을수록 빠르게 삭제됨
 	fQuarterAlpha = fAlphaCount = 0.5f;
	bSpawned = true;
}

효과에 쓰인 매테리얼.Opacity는 여기서 생성한 Scalar값이다.
아무래도 복제 순서때문에 꼬인듯하다.하지만 똑같은 모양으로 복제되므로 수치만 조정하면 된다.

원인은 Mesh의 Transform정보가 따로 존재하기 때문.이런경우의 해결책은

자체 값을 수정하거나 메쉬정보를 더 해주면 된다.

SpawnLocation.Z -= 90;
rotator.Yaw -= 90;

언리얼은 유니티랑 다르게 transform의 값을 일부 수정이 가능한 부분은 좋은것 같다.

 

FRotator rotator = GetActorRotation() + GetMesh()->GetRelativeRotation();
FVector  SpawnLocation = GetActorLocation() + GetMesh()->GetRelativeLocation();

ㅇ\ㅇ.른 \

 

깔끔하게 잔상처리가 적용되었다.

'UE4 - C++' 카테고리의 다른 글

UE4 - 아이템 상자와 무기 제작  (0) 2021.10.28
UE4 - 충돌 설정과 데미지 전달  (0) 2021.09.30
UE4 - 애니메이션 몽타주  (0) 2021.09.09
UE4 - 점프기능 구현  (0) 2021.08.31
UE4 - 일인칭 슈팅 C++ 튜토리얼  (0) 2021.07.23