공부용 이모저모
UE4 - 잔상 효과 만들기(SPAWN/TIME LINE/POSEABLEMESH) 본문
참고 사이트 : https://mingyu0403.tistory.com/247
https://www.youtube.com/watch?v=9yftOwWp48A
[Unreal BP] 잔상 효과 만들기
방법 1. 액터를 하나 만든다. 2. 컴포넌트에 PoseableMesh를 추가하고, 설정 값을 바꾼다. 3. 이벤트가 들어오면 2번에서 만들었던 액터를 생성한다. (잔상이니까 내 위치에 생성.) 4. 생성하고 난 뒤,
mingyu0403.tistory.com
우선 움직일때마다 액터를 생성하는 부분부터 시작한다
일정 시간마다 부르기 위해 타임라인을 만들어본다
우선 타임라인을 이용한 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);
}
중간중간 섬세한 조정은 이렇게 사용하는거도 좋지만,
잔상은 정확한 타이밍에 나와야 하니 타임라인이 끝나는 시점을
기준으로 적용 시키기로 한다
헤더
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.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;
}
원인은 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 |