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을 섞는다.
PlayerAnimInstance.h
PlayerAnimInstance.cpp
Transform Bones를 통해서 이를 적용합니다.
방법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는 동일하게 오른쪽 클릭으로 넣으면 된다.
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인칭이므로 발사 시작점이 되는 카메라로부터 발사가 시작된다.
///
// 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