일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 프메
- 언리얼뮤지컬
- flask
- VUE
- R
- 정글사관학교
- 언리얼프로그래머
- 언리얼
- 스터디
- Unseen
- 디자드
- Ajax
- Express
- EnhancedInput
- 게임개발
- 으
- Enhanced Input System
- 알고풀자
- 프린세스메이커
- node
- 마인크래프트뮤지컬
- 데이터베이스
- 카렌
- 파이썬서버
- 스마일게이트
- 미니프로젝트
- 레베카
- JWT
- Bootstrap4
- Jinja2
- Today
- Total
Today, I will
[Unreal] Maximo fbx 애니메이팅과 무기 socket 적용을 위한 animNotifyState 본문
언리얼에서 플레이어가 중요한 게임을 만들고자 한다면,
player의 애니메이션을 잘 가다듬는 것이 손이 참 많이 가지만 중요한 작업이라고 할 수 있다.
자신이 가지고 있는 skeleton을 이용해 maximo에서 다른 애니메이션들도 골라 import해서 언리얼에서 직접 작업하는 과정을 담은 포스팅을 남겨두도록 한다.
Maximo download
Maximo에서 마음에 드는 애니메이션을 골라서 without skin,
in place 옵션을 체크하고 fbx 파일을 잘 내려받아준다. 바로 언리얼로 가져올 수 있다.
언리얼로 가져올 때는 준비한 스켈레톤을 선택하고 import mesh는 체크 해제하고 'import'
(import all은 기타 타 모델링 소프트웨어의 셰이더 부산물이 같이 들어옴)
Animation setting in Unreal
최종 목표 애니메이션 블루프린트 세팅은 아래와 같다.
큰 구성은 Event Graph
그리고 AnimGraph가 있다.
따라서 idle(러닝), jump_start, jump_loop, jump_end 용 애니메이션을 미리 가다듬는 것이 중요하다.
miximo에서 가져온 애니메이션에서 시퀀스 바에 오른쪽 마우스를 클릭해서 시퀀스 바 기준으로 앞부분 혹은 뒷부분을 자를 수 있다. 이러한 작업을 거쳐서 miximo에서 가져온 jump 애니메이션에서 jump_start, jump_loop, jump_end를 뽑아 만들어준다.
또한 플레이어 애니메이션 블루프린트를 아래와 같이 세팅해주는 것이 몹시 중요하다.
(1) speed라는변수를 만들어서 Pawn Owner의 속도 크기를 뽑아 담아주었다.
- 속도(Velocity)에서 벡터의 길이 == 속력
애니메이션 블루프린트의 소유 Pawn을 Character로 Casting 한 이유는, Jump는 Character 클래스에서 제공하는 기능
isFaling은 언리얼에서 제공하는 타겟 캐릭터의 무브먼트 컴포넌트이다. 변수명을 isFalling으로 작성한 이유이기도 하다. Character 필수 구성요소인 Character Movement Comp 에서 ‘IsFalling’ Boolean을 직접 만든 IsFalling 변수에 연결
이렇게 블루프린트와 캐릭터를 연동한 변수 isFalling와 Speed를 이용해서 애니메이팅을 완성할 것이다.
idle
Blend_to_walk
standing idle과 running을 블렌드하여 제작해준다. (animation -> blend space)
idle
블렌드 Space Animation의 입력값을 변수로 만들어서, 캐릭터(Pawn)로부터 받도록 한다.
idle -> jump start
jump start
jump start -> jump loop
- Get Relavant Anim Time Fraction이 있고 Get Relavant Anim Time Remaining이 있다.
jump loop 발동할 조건을 Jumpstart가 끝났을 때 시작하고 싶다면 Get Relavant Anim Time Fraction을 이용해서 Jumpstart의 Animation 재생률이 0 이하일 때 노드를 연결해주면 된다.(언리얼 엔진에서 나머지 시간을 총 시간의 백분율 소수로 제공한다)
jump loop
jump loop -> jump end
jump end
몽타주
몽타주(montage)는 따로 촬영된 화면을 떼어 붙이면서 새로운 장면이나 내용을 만드는 기법이다.
Blend Time은 애니메이션 블렌딩(혼합)하는 데 걸리는 시간이다.
- 사용할 애니메이션의 길이에 맞게 조절을 해야한다.
ex) 너무 짧은 애니메이션은 0.25를 더 크게
anim graph의 state machine의 사람 모양에서 마우스를 드래그해서 뽑고 new.. 로 검색하면 new save cached pose..노드를 만들 수 있고 이것의 이름을 Locomotion cache로 바꿔주었다. 이름 그대로 애니메이션 결과를 저장하는 캐시이다.
Layered blend per bone 같은 경우는 두 가지 애니메이션이 겹쳐나와야 할 때 본(bone)을 기준으로 층(Layer)를 나누어 사용하게 해줄 수 있는 노드이다.
*Blend Poses0
블렌드 할 애니메이션 = 새롭게 재생될 애니메이션
*Blend Weights0
Base Pose의 애니메이션과, Blend Poses0에 들어올 애니메이션을 블렌딩할 때의 가중치
Default slot(애니메이션 몽타주만 저장가능한 변수)을 만들어 배치하여 애니메이션 몽타주가 없을 때 생길 수 있는 어색함을 미리 보완해주도록 한다.
플레이어 헤더
UPROPERTY(EditAnywhere, Category = "Anim")
UAnimMontage* attackMontage;
플레이어 cpp
void AMyPlayer::InputFire(const FInputActionValue& Value)
{
//Fire();
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance) {
AnimInstance->Montage_Play(attackMontage);
}
}
위와 같이 코드와 플레이어의 attackMontage에 적절한 몽타주까지 넣어주면 Fire키를 누를 때(사용자 정의) 몽타주가 발동된다.
weapon in animate hand
animnotifystate
애니메이션 시퀀스에서 이벤트를 구성하고 받아 외부 동작을 수행하는 알림 시스템
이제 fire용 animnotifystate를 상속한 클래스를 만들어준다.
f12를 누르면 원본 확인 가능
virtual void NotifyBegin(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference);
virtual void NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference);
virtual void NotifyEnd(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, const FAnimNotifyEventReference& EventReference);
그대로 오버라이드해준다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "MyAnimNotifyState.generated.h"
/**
*
*/
UCLASS()
class THIRDPERSONTEMPLATE_API UMyAnimNotifyState : public UAnimNotifyState
{
GENERATED_BODY()
public:
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
};
1이 begin 2가 end 그 사이의 파란선들이 tick 구간이 된다고 보면된다.
MyAnimNotifyState의 시작 프레임에서만 총알(fire)가 나가게 하고 싶다면 아래와 같이 코드를 작성해주면 된다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyAnimNotifyState.h"
#include "PBullet.h"
#include "MyPlayer.h"
//Notify가 불려지는 시작 첫(1) 프레임
void UMyAnimNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference)
{
GEngine->AddOnScreenDebugMessage(-1,5.0f, FColor::Green, TEXT("NotifyBegin"));
AMyPlayer* tpsPlayer = Cast<AMyPlayer>(MeshComp->GetOwner());
tpsPlayer->Fire();
}
//Notify가 불려지는 동안의 매 프레임
void UMyAnimNotifyState::NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Magenta, TEXT("NotifyTick"));
}
//Notify가 끝나는 프레임
void UMyAnimNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT("NotifyEnd"));
}
주의
애니메이션 블루프린트에서의 Speed는 0이어야 하고,
플레이어의 속도를 조절하고 싶다면
플레이어의 스피드를 변경시켜주어야 한다!
전체 코드
참고 플레이어의 점프 설정
0.152 정도로 Hold Time을 하면 점프를 한번 정도만 할 수 있고, 0으로 설정하면 끝도없이 위로 올라갈 수 있다.
player.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "MyPlayer.generated.h"
class UInputMappingContext;
class UInputAction;
UCLASS()
class THIRDPERSONTEMPLATE_API AMyPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMyPlayer();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(EditAnywhere, Category = "Anim")
UAnimMontage* attackMontage;
public:
UPROPERTY(VisibleAnywhere, Category="Camera")
class USpringArmComponent* springArmComp;
UPROPERTY(VisibleAnywhere, Category = "Camera")
class UCameraComponent* cameraComp;
UPROPERTY(EditAnywhere, Category = "Input")
UInputMappingContext* PlayerMappingContext;
UPROPERTY(VisibleAnywhere, Category = "Weapon")
class UStaticMeshComponent* staffMesh;
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* MoveIA;
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* LookUpIA;
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* TurnIA;
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* JumpIA;
UPROPERTY(EditAnywhere, Category = "Input")
UInputAction* FireIA;
public:
void Move(const FInputActionValue& Value);
void LookUp(const FInputActionValue& Value);
void Turn(const FInputActionValue& Value);
void InputJump(const FInputActionValue& Value);
void InputFire(const FInputActionValue& Value);
void Fire();
float fireTimerTime; // 타이머의 시간
bool fireReady; // 타이머 도달 여부
public:
UPROPERTY(EditAnywhere, Category = "Move")
float moveSpeed;
UPROPERTY(EditAnywhere)
TSubclassOf<class APBullet> bulletFactory;
UPROPERTY(EditAnyWhere)
float fireCoolTime = 2.0f;
private:
FVector moveDirection;
protected:
void FireCoolTimer(float cooltime, float deltaTime);
};
player.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyPlayer.h"
#include "Components/ArrowComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputSubsystems.h" //https://docs.unrealengine.com/5.0/en-US/enhanced-input-in-unreal-engine/
#include "EnhancedInputComponent.h" //https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/Input/EnhancedInput/
#include "PBullet.h"
// Sets default values
AMyPlayer::AMyPlayer()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//ConstructorHelpers 라는 유틸리티로 에셋 가져오기(찾기)
// InitMesh 굳이 이렇게 하기는 했으나 콘텐츠 브라우저에서 선택하는게 사실 더 좋음
ConstructorHelpers::FObjectFinder<USkeletalMesh> InitMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/MyResouce/unitychan.unitychan'"));
if (InitMesh.Succeeded()) { // 제대로 오브젝트를 가져왔다면~
// GetMesh는 actor(근본)의 상속자인 character에서 제공!
GetMesh()->SetSkeletalMesh(InitMesh.Object);
GetMesh()->SetRelativeLocationAndRotation(
FVector(0, 0, -88)
, FRotator(0, -90, 0));
}
springArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
springArmComp->SetupAttachment(RootComponent);
springArmComp->SetRelativeLocationAndRotation(FVector(0, 0, 50), FRotator(-20,0,0));
springArmComp->TargetArmLength = 530;
springArmComp->bUsePawnControlRotation = true;
//카메라도 생성 초기화
cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
cameraComp->SetupAttachment(springArmComp);
cameraComp->bUsePawnControlRotation = false;
//staff mesh
staffMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("staff meshComp"));// 스태프 외관 컴포넌트 등록
// 두번째 인자 캐릭터 메쉬에 부착, FName이 문자열 속도가 빠름
staffMesh->SetupAttachment(GetMesh(), FName("Character1_RightHandSocket"));
//staffMesh->SetRelativeLocationAndRotation
//pawn이 들고 있음
bUseControllerRotationYaw = true;
moveSpeed = 100.0f;
fireTimerTime = 0;
fireCoolTime = 1.85f;
fireReady = true; // 발사 준비 On
}
// Called when the game starts or when spawned
void AMyPlayer::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem
= ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer())) {
Subsystem->AddMappingContext(PlayerMappingContext, 0);
}
}
}
// Called every frame
void AMyPlayer::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (fireReady == false) {
FireCoolTimer(fireCoolTime, DeltaTime);
}
// 이동방향을 컨트롤 방향 기준으로 변환
moveDirection = FTransform(GetControlRotation()).TransformVector(moveDirection);
/*
// 플레이어 이동 - 등속운동
FVector P0 = GetActorLocation();//현재 위치
FVector vt = moveDirection * moveSpeed * DeltaTime; //이동거리
FVector P = P0 + vt;
SetActorLocation(P);
*/
AddMovementInput(moveDirection);
// 방향 초기화
// 방향이 누적되지 않게
moveDirection = FVector::ZeroVector;
}
// Called to bind functionality to input
void AMyPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &AMyPlayer::Move);
EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &AMyPlayer::LookUp);
EnhancedInputComponent->BindAction(TurnIA, ETriggerEvent::Triggered, this, &AMyPlayer::Turn);
EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputJump);
EnhancedInputComponent->BindAction(FireIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputFire);
}
}
void AMyPlayer::Move(const FInputActionValue& Value)
{
UE_LOG(LogTemp, Log, TEXT("Log Message %f"), moveSpeed);
const FVector _currentValue = Value.Get<FVector>();
if (Controller) {
moveDirection.Y = _currentValue.X;
moveDirection.X = _currentValue.Y;
}
//FVector newLocation = GetActorLocation() + _currentValue;
//SetActorLocation(newLocation);
}
void AMyPlayer::LookUp(const FInputActionValue& Value)
{
// mouse y - 한 축의 값(float)
const float _currentValue = Value.Get<float>();
APawn::AddControllerPitchInput(_currentValue);
}
void AMyPlayer::Turn(const FInputActionValue& Value)
{
// mouse x - 한 축의 값(float)
const float _currentValue = Value.Get<float>();
APawn::AddControllerYawInput(_currentValue);
}
void AMyPlayer::InputJump(const FInputActionValue& Value)
{
Jump();
}
void AMyPlayer::InputFire(const FInputActionValue& Value)
{
if (fireReady == true) {
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance) {
AnimInstance->Montage_Play(attackMontage);
}
fireReady = false;
}
}
void AMyPlayer::FireCoolTimer(float cooltime, float deltaTime)
{
if (fireTimerTime < cooltime) {
fireTimerTime += deltaTime;
}
else
{
fireTimerTime = 0;
fireReady = true;
}
}
void AMyPlayer::Fire()
{
FTransform firePosition = staffMesh->GetSocketTransform(TEXT("FirePosition"));
GetWorld()->SpawnActor<APBullet>(bulletFactory, firePosition);
}
bullet.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PBullet.generated.h"
UCLASS()
class THIRDPERSONTEMPLATE_API APBullet : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
APBullet();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
UPROPERTY(EditAnyWhere, Category = "Movement")
class UProjectileMovementComponent* movementComp;
UPROPERTY(EditAnyWhere, Category = "Movement")
class USphereComponent* collisionComp;
UPROPERTY(EditAnyWhere, Category = "Movement")
class UStaticMeshComponent* meshComp;
};
bullet.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "PBullet.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
//reference: https://velog.io/@minjujuu/Unreal-TPS-%EA%B2%8C%EC%9E%84-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%8B%A4%EC%8A%B5-2
// Sets default values
APBullet::APBullet()
{
// 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;
collisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere Collider"));// 1. 충돌체 등록
collisionComp->SetCollisionProfileName(TEXT("BlockAll")); //같은 이름 확실히 블프에도 있음
// 2. 충돌 프로파일 설정 : 충돌 프로파일 BlockAll: 모든 물체와 부딪혀 튕길 수 있도록 함
collisionComp->SetSphereRadius(13.0f);// 3. 충돌체 크기 설정
RootComponent = collisionComp; // 4. 루트로 등록
meshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("static meshComp"));// 5. 외관 컴포넌트 등록
meshComp->SetupAttachment(collisionComp);// 6. 부모 컴포넌트 지정
meshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//발사체 컴포넌트 생성
movementComp = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("MovementComp"));
// 발사체 컴포넌트를 UPDATE시킬 컴포넌트 지정
// movement 컴포넌트가 갱신시킬 컴포넌트 지정, 스태틱메시컴포넌트 부모가 collisionComp이므로 스태틱메시도 같이 업데이트
// update = tick, update -> 매 프레임 연산하겠다.
movementComp->SetUpdatedComponent(collisionComp);
// 초기속도
movementComp->InitialSpeed = 200.f;
// // 최대속도 등가속도(직선운동)는 속도가 일정한거, 포물선은 속도가 안 일정함!
movementComp->MaxSpeed = 5000.f;
// 반동 여부
movementComp->bShouldBounce = true;
// 반동 크기(값) : 0.3f탱탱볼 정도
movementComp->Bounciness = 0.3f;
// 7. 충돌 비활성화 충돌체 등록해주었으니 StaticMesh에 있는 충돌체있다면 꺼줌
/*
meshComp->SetRelativeScale3D(FVector(0.25f));// 8. 외관 크기 설정
//movementComp를 통해 총알의 속도나 반동과 관련된 설정들을 해줄 수 있다
static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereMeshAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
if (SphereMeshAsset.Succeeded())
{
meshComp->SetStaticMesh(SphereMeshAsset.Object);
meshComp->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
meshComp->SetRelativeLocation(FVector(0.0f, 0.0f, -5.0f));
}
*/
}
// Called when the game starts or when spawned
void APBullet::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void APBullet::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
UMyAnimNotifyState.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "MyPlayer.h"
#include "MyAnimNotifyState.generated.h"
/**
*
*/
UCLASS()
class THIRDPERSONTEMPLATE_API UMyAnimNotifyState : public UAnimNotifyState
{
GENERATED_BODY()
public:
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
public:
AMyPlayer* tpsPlayer;
};
UMyAnimNotifyState.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyAnimNotifyState.h"
#include "PBullet.h"
#include "MyPlayer.h"
//Notify가 불려지는 시작 첫(1) 프레임
void UMyAnimNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference)
{
GEngine->AddOnScreenDebugMessage(-1,5.0f, FColor::Green, TEXT("NotifyBegin"));
// 접근을 위해서, 플레이어 캐릭터를 알아야 한다
// MeshComp : 해당 애니메이션을 플레이하는 스켈레탈 메쉬
tpsPlayer = Cast<AMyPlayer>(MeshComp->GetOwner());
if (tpsPlayer) {
tpsPlayer->Fire();
}
}
//Notify가 불려지는 동안의 매 프레임
void UMyAnimNotifyState::NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Magenta, TEXT("NotifyTick"));
}
//Notify가 끝나는 프레임
void UMyAnimNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT("NotifyEnd"));
}
사실 state의 최대 강점은 tick에서 계속 주시를 하고 begin에서는 이미 발생한 행동, end에서는 주시한 바에 따른 결과 호출을 많이 한다.
Niagara Skeletal Mesh
최종 나이아가라를 아래와 같이 만들어서 플레이어에게 붙여주자.
마음껏 색깔을 왼쪽 오른쪽 클릭과 더블클릭을 이용하여 바꾸어준다.
캡슐 컴포넌트(콜라이더) 아래로 방금 만든 스켈레탈 메쉬 나이아가라가 들어갈 수 있도록 서랍에서 드래그하거나, ADD 해준다.
완성
visible을 의도에 맞게 껐다 켰다 해줄 수도 있다.
Tip
메모리 에러
EXCEPTION_ACCESS_VIOLATION(접근이 불가한, 찾아올 수 없는 thing)
EXCEPTION_Valid (없는 thing에다 무언가를 하고자 하였는데 null이다.)
'Unreal' 카테고리의 다른 글
[Unreal] 언리얼 직렬화, Serialize (1) | 2023.11.23 |
---|---|
[Unreal] singleton UGameInstance와 interface의 활용 예시 (0) | 2023.11.23 |
[Unreal] 언리얼 VFX 에디터 나이아가라(Niagara) (0) | 2023.11.07 |
[Unreal] 언리얼 멀티플레이어 세팅(1) (0) | 2023.11.01 |
[Unreal] enum을 이용한 enemy 이름짓기 (0) | 2023.10.27 |