기본 콘텐츠로 건너뛰기

250408 C++ 애니메이션

 

Locomotion을 New Save Cached pose를 통해 저장하고,

Use cached pose를 이용해서 사용할 수 있다.




Rifle조준시의 Locomotion을 따로 만든다.


Layered Blend per bone을 통해 상체만 움직이도록 만든다.

이를 위해서 상체의 스켈레톤에서 Bone이름인 spine_02을

Layer Setup의 Index를 추가하고 Bone Name에 넣는다.

Mesh Space Rotation Blend에 체크한다.


이동 Locomotion과 Rifle Locomotion을 섞는다.


캐릭터의 상체가 따라가도록 만들 생각이기 때문에, Rotator를 하나 만들어 사용합니다.
PlayerAnimInstance.h

PlayerAnimInstance.cpp

NormalizedDeltaRotator를 통해 플레이어의 회전값으로부터 뷰포트의 회전값을 정규화해서 그 값 만큼의 변화를 AimRotator에 넣어줍니다.

Transform Bones를 통해서 이를 적용합니다.
Skeletal Control에 상체에 해당하는 spine_02를 할당해주고,
회전할 것이기 때문에, Rotation Mode를 Add to Existing으로 바꾸어줍니다.

AimRotator를 통해 회전할 값을 넣어주고 연결하면 끝입니다.


방법1. 블루프린트 Montage에 직접넣기

블루프린트 Montage에서 Play Sound를 넣어준다.





방법2. Notify를 넣고, Animation Blueprint에서 소리 넣기

동일하게 Montage에 오른쪽 클릭을 하고 NewNotify를 하나 넣고 이름을 지어줍니다.



Animation Blueprint의 Event Graph에서 해당 이름의 Notify를 불러온다.

여기에 PlaySound노드와 연결해주면 된다.



방법3.

C++ 클래스에서 AnimNotifyState를 생성한다.



SoundAnimNotify.h

NotifyBegin => Notify가 호출 될 때 시작

NotifyTick => NotifyBegin이 호출 된 후 다음 NotifyEnd가 불릴 때까지 반복해서 호출

NotifyEnd => Notify가 끝날 때 호출


SoundAnimNotify.cpp

생성한 NotifyState는 동일하게 오른쪽 클릭으로 넣으면 된다.


PlayerCharacter.cpp

void USoundAnimNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)

{

//플레이어 캐릭터를 가져와서 플레이어의 WeaponFireSound()를 실행한다.

APlayerCharacter* Player = Cast<APlayerCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(),0));

if (Player)

{

Player->WeaponFireSound();

}

}

방법4. 블루프린트랑 연동해서 쓰기

블루프린트에서 소리만 출력하기 위해서 OnFire_BP()라는 함수를 UFUNCTION으로 선언.


cpp파일에서 OnFire_BP()를 호출한다.


캐릭터의 블루프린트로 가서 OnFire_Bp를 불러오고 PlaySound2D를 연결해준다.


레이트레이싱
Line Trace By Channel
Start : 3인칭이므로 발사 시작점이 되는 카메라로부터 발사가 시작된다.
End : 도달해야 하는 위치도 카메라로부터 조준점인 바라보는 방향.
Trace Channel :
Draw Debug Type : 디버그 확인용




///

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



#include "PlayerCharacter.h"

#include "Components/SkeletalMeshComponent.h"

#include "GameFramework/SpringArmComponent.h"

#include "Camera/CameraComponent.h"

#include "EnhancedInputComponent.h"

#include "EnhancedInputSubSystems.h"

#include "InputMappingContext.h"

#include "Components/StaticMeshComponent.h"

#include "PlayerAnimInstance.h"

#include "Kismet/GameplayStatics.h"


// Sets default values

APlayerCharacter::APlayerCharacter()

{

  // 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::FObjectFinder<USkeletalMesh>PlayerMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/Survival_Character/Meshes/SK_Survival_Character.SK_Survival_Character'"));

if (PlayerMesh.Succeeded())

{

GetMesh()->SetSkeletalMesh(PlayerMesh.Object); //스켈레탈 메쉬 적용.

GetMesh()->SetWorldLocationAndRotation(FVector(0, 0, -90), FRotator(0, -90, 0)); //위치, 회전값 초기화

}


//스프링 암

SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm")); // 하위에 USpringArmComponent 컴포넌트를 "SpringArm"이름으로 만든다.

SpringArm->SetupAttachment(RootComponent); // 가장 상위 Root를 부모로함.

SpringArm->SetWorldLocation(FVector(0, 0, 55.0f)); // Z값 55.0f로

SpringArm->TargetArmLength = 300; // TargetArmLength 300

SpringArm->SocketOffset = FVector(0, 40, 0); // SocketOffset (0,40,0)

SpringArm->bUsePawnControlRotation = true; // Look의 상하이동 가능하게


//카메라

Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); // 카메라 컴포넌트 생성

Camera->SetupAttachment(SpringArm); // 부모를 SpringArm으로 함.


//메쉬의 애니메이션 설정에 애니메이션 인스턴스 삽입

//경로에 Class이기 때문에 _C를 추가해주어야 함

static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstance(TEXT("/Game/Blueprints/ABP_Player.ABP_Player_C"));

if (AnimInstance.Class)

{

GetMesh()->SetAnimInstanceClass(AnimInstance.Class);

}


//무기 추가

SK_Weapon_Pistol = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponPistol"));

SK_Weapon_Pistol->SetupAttachment(GetMesh());

static ConstructorHelpers::FObjectFinder<USkeletalMeshComponent> WeaponMeshPistol(TEXT("/Script/Engine.SkeletalMesh'/Game/ATPS/Assets/Weapons/Pistol/Mesh/SK_Pistol.SK_Pistol'")); //피스톨을 찾아서 넣어줌.


//SM_Weapon에 Pistol 추가

if (WeaponMeshPistol.Succeeded())

{

SK_Weapon_Pistol->SetSkeletalMesh(WeaponMeshPistol.Object);

}


// hand_rSocket에 SM_Weapon을 추가한다.

SK_Weapon_Pistol->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("pistol_socket"));


//무기 추가

SM_Weapon_Rifle = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponRifle"));

SM_Weapon_Rifle->SetupAttachment(GetMesh());

static ConstructorHelpers::FObjectFinder<UStaticMesh> WeaponMeshRifle(TEXT("/Script/Engine.StaticMesh'/Game/Asset/Weapons/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SM_AR4.SM_AR4'")); //AR4를 찾아서 넣어줌.


//SM_Weapon에 Ar4 추가

if (WeaponMeshRifle.Succeeded())

{

SM_Weapon_Rifle->SetStaticMesh(WeaponMeshRifle.Object);

}


// hand_rSocket에 SM_Weapon을 추가한다.

SM_Weapon_Rifle->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("rifle_socket"));


// 처음에는 피스톨을 들도록 설정할 것이라 Rifle의 설정을 Visibility를 false로 함.

SM_Weapon_Rifle->SetVisibility(false);

//Input

static ConstructorHelpers::FObjectFinder<UInputMappingContext> InputContext(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Input/IMC_PlayerDefault.IMC_PlayerDefault'"));

if (InputContext.Object != nullptr)

{

DefaultContext = InputContext.Object;

}

static ConstructorHelpers::FObjectFinder<UInputAction> InputMove(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Move.IA_Move'"));

if (InputMove.Object != nullptr)

{

MoveAction = InputMove.Object;

}

static ConstructorHelpers::FObjectFinder<UInputAction> InputLook(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Look.IA_Look'"));

if (InputLook.Object != nullptr)

{

LookAction = InputLook.Object;

}

static ConstructorHelpers::FObjectFinder<UInputAction> InputAim(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Aim.IA_Aim'"));

if (InputAim.Object != nullptr)

{

AimAction = InputAim.Object;

}

static ConstructorHelpers::FObjectFinder<UInputAction> InputFire(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Fire.IA_Fire'"));

if (InputFire.Object != nullptr)

{

FireAction = InputFire.Object;

}

static ConstructorHelpers::FObjectFinder<UInputAction> InputSwap(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Swap.IA_Swap'"));

if (InputSwap.Object != nullptr)

{

SwapAction = InputSwap.Object;

}

}


// Called when the game starts or when spawned

void APlayerCharacter::BeginPlay()

{

Super::BeginPlay();

if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))

{

if (UEnhancedInputLocalPlayerSubsystem* SubSystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))

{

SubSystem->AddMappingContext(DefaultContext, 0);

}


}

}


// Called every frame

void APlayerCharacter::Tick(float DeltaTime)

{

Super::Tick(DeltaTime);

}


// Called to bind functionality to input

void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)

{

Super::SetupPlayerInputComponent(PlayerInputComponent);

UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);

EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlayerCharacter::Move);

EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlayerCharacter::Look);

EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &APlayerCharacter::OnAim);

EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &APlayerCharacter::OffAim);

EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &APlayerCharacter::Fire);

EnhancedInputComponent->BindAction(SwapAction, ETriggerEvent::Started, this, &APlayerCharacter::Swap);


}


void APlayerCharacter::Move(const FInputActionValue& value)

{

const FVector2D MovementVector = value.Get<FVector2D>();

const FRotator Rotation = Controller->GetControlRotation();

const FRotator YawRotation(0, Rotation.Yaw, 0);


const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);

const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

AddMovementInput(ForwardDirection, MovementVector.Y);

AddMovementInput(RightDirection, MovementVector.X);

}


void APlayerCharacter::Look(const FInputActionValue& value)

{

FVector2D LookAxisVector = value.Get<FVector2D>();

AddControllerYawInput(LookAxisVector.X * GetWorld()->DeltaTimeSeconds * mouseSpeed);

AddControllerPitchInput(LookAxisVector.Y * GetWorld()->DeltaTimeSeconds * -mouseSpeed);

}


//줌인

void APlayerCharacter::OnAim(const FInputActionValue& value)

{

UPlayerAnimInstance* AnimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());

AnimInstance->isAim = true; //isAim이 public이라서 바꿀 수 있음.


}


//줌아웃

void APlayerCharacter::OffAim(const FInputActionValue& value)

{

UPlayerAnimInstance* AnimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());

AnimInstance->isAim = false;

}


//총알 발사

void APlayerCharacter::Fire(const FInputActionValue& value)

{

//블루프린트에서 실행

OnFire_BP();


UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

switch (WeaponNumber)

{

case 1:

if (PistolFireMontage)

{

AnimInstance->Montage_Play(PistolFireMontage, 0.233333f);

}

break;

case 2:

if (RifleFireMontage)

{

AnimInstance->Montage_Play(RifleFireMontage, 0.233333f);

}

}

}


//무기 스왑

void APlayerCharacter::Swap(const FInputActionValue& value)

{

WeaponNumber = value.Get<int32>();


//피스톨 또는 라이플의 메쉬 하나만 보여야함. SetVisibility를 이용해서

//애니메이션이 바꾸어져야 함.(애니메이션 블루프린트에서 하기)

if (SM_Weapon_Pistol and SM_Weapon_Rifle)

{

switch (WeaponNumber)

{

case 1:

SM_Weapon_Pistol->SetVisibility(true);

SM_Weapon_Rifle->SetVisibility(false);

break;

case 2:

SM_Weapon_Pistol->SetVisibility(false);

SM_Weapon_Rifle->SetVisibility(true);

break;

}

}

}


void APlayerCharacter::WeaponFireSound()

{

switch (WeaponNumber)

{

case 1:

if (PistolSound)

{

UGameplayStatics::PlaySound2D(this, PistolSound);

}

break;

case 2:

if (RifleSound)

{

UGameplayStatics::PlaySound2D(this, RifleSound);

}

break;

}

}


/*

void APlayerCharacter::FireOff(const FInputActionValue& value)

{

UPlayerAnimInstance* AnimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());

AnimInstance->isFire = true;


}

*/


참고🐍🐍🐍🐍

Layered animation레이어 애니메이션

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-layered-animations-in-unreal-engine?application_version=5.3


Transform Bone트랜스폼 본

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/transform-bone?application_version=4.27


Notify

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/animation-notifies-in-unreal-engine


GameplayStatics

https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/Kismet/UGameplayStatics?application_version=5.1


LineTrace

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-a-single-line-trace-raycast-by-channel-in-unreal-engine?application_version=5.5

이 블로그의 인기 게시물

250523 PCG 2

PCG Building Volume Sampler를 사용할 경우 Voxel Size를 맞추어주자. 2m길이를 Transform Points로 Offset을 이동시켜 Difference를 통해 메쉬를 생성한다. 사용시에 Pivot의 위치가 중앙이 아니면 제대로 표현되지 않을 것이다. PCG Landscape Landscape 생성후 PCG Volume 생성. Get Landscape Data - Surface Sampler 안보인다면 Generate Surface Sampler 일반 그리드 패턴으로 서피스 데이터에 포인트를 샘플링합니다. 이 노드에는 다음과 같은 옵션이 있습니다. 포인트 규모(Point Extents) : 서피스의 기본 그리드 셀 크기를 정의합니다. 여유(Looseness) : 변형 가능한 셀 크기를 정의합니다. 실제로 셀 크기는 포인트 규모 * (1 + 여유)입니다. 제곱미터당 포인트(Points Per Square Meter) : 유지되는 셀의 비율을 계산합니다. 이 프로퍼티는 그리드가 클 때 과잉을 제한합니다. Looseness : 기본 1. 0일경우 Extent의 길이 만큼, 1일경우 2배가 됨.  Attribute Filter를 생성하고, Material에 해당하는 Layer를 찾아서 이름을 넣어준다. Type을 Float로 하고 Float Value를 설정하면, 해당 머티리얼이 색칠된 정도 0~1에서 Operator에 의해 >(초과) 0.6초과 일 경우만 생성되게 할 수 있다. 값을 1로 하고 Equal을 이용해서 1인 경우만 생성되게 하는 것도 가능하다. plugin Water body lake를 넣으면, 이또한 Spline이라는 것을 알 수 있다. 따라서 Get Spline Data-Spline Sampler를 통해 사용할 수 있다. GetActorData와 Difference를 통해서 Lake내부를 제거해주기 PCG Mesh Mesh Sampler를 쓰기위해 Get Actor Property를 이용하면, 해당 액터가 ...

250604 저장불러오기

SaveGame 블루프린트 생성 Game Instance 생성, Init 오버라이드 AC_Inventory Save Load

250522 Physics2, PCG

Radial Force Actor Force 값을 많이 주어야함. Balloon Mass값과 PhysicsThrust를 이용하거나, Set Physics Linear Velocity를 이용하여 Z축 값을 줄 수 있다. Skeletal Physics Physics Material Friction : 마찰력. 수치가 클수록. 덜 미끄러워짐 Restritution : 복원력. 탄성계수 수치가 높을 수록 bouncy Fracture voronoi : Cluster의 숫자 Show Bone Color 해제해주기 Damage Damage Threshold의 값을 수정하는 것으로 강도 조절 가능. Chaos Cache Actor에서 Chaos Cache Manager를 생성 가능. Record 기능이 존재한다. 일단 생성하면 Chaos Cache Collection을 저장하여 사용할 수 있게된다. Cachos Cache Manager를 Simulate를 누르면 저장되고, 실행을 종료하면 그동안의 변화상황이 저장된다. 저장된 Collection을 가져와서 Sequencer의 StartTime으로 가져와 재생할 수 있다. PCG plugin Spline생성후 포인트 하나 지워 줌 우클릭으로 Spline Generation Panel, Square 생성. 처음 지점 포인트 겹쳐있을 수 있으므로 찾아서 삭제해주기 Get Spline Data => Spline Sampler D키 또는 Debug에 체크를 해주고, Dimension 설정을 해준다. On Interior의 경우, Unbounded에 체크를 하면 내부에도 생기게 된다. Static Mesh Spawner Intersection : 교차점 이 부분은 tag가 Spline과 Overlap되어 닿으면 바꾸어 준다. Difference를 이용해 그외 부분은 채워 넣는다. 길 By Class로 설정해주기 On Spline - Distance Extent Radius 설정해주기 Difference를 통해 제외하기 PCG는 A...