Unreal Engine 4의 Hand IK(Inverse Kinematics)는 캐릭터의 가상 손이 사실적인 방식으로 환경 및 개체와 상호 작용할 수 있도록 하는 기능이다. 대상 개체의 위치와 방향을 기반으로 캐릭터 손의 위치와 방향을 계산하여 작동한다. 언리얼 엔진 4에서 Hand IK를 사용하려면 스켈레탈 메시가 있는 캐릭터와 손 애니메이션이 포함된 애니메이션 세트가 있어야 한다. Hand IK 시스템은 애니메이션 데이터를 사용하여 캐릭터 손의 위치와 방향을 실시간으로 계산한다.

 

목차

     

     


     

     

    Weapon

     

    Unreal Engine 4의 Hand IK(Inverse Kinematics)는 캐릭터의 가상 손이 사실적인 방식으로 환경 및 개체와 상호 작용할 수 있도록 하는 기능이다. 대상 개체의 위치와 방향을 기반으로 캐릭터 손의 위치와 방향을 계산하여 작동한다.

     

    언리얼 엔진 4에서 Hand IK를 사용하려면 스켈레탈 메시가 있는 캐릭터와 손 애니메이션이 포함된 애니메이션 세트가 있어야 한다. Hand IK 시스템은 애니메이션 데이터를 사용하여 캐릭터 손의 위치와 방향을 실시간으로 계산한다.

    Hand IK를 설정하려면 애니메이션 블루프린트에 Hand IK 노드를 생성해야 합니다. 이 노드는 대상 오브젝트의 위치와 회전을 입력으로 받아 캐릭터 손의 위치와 회전을 출력합니다. 그런 다음 이 출력을 사용하여 게임에서 캐릭터 손의 위치와 회전을 구동할 수 있다.

    Hand IK는 물체 집기 및 조작, 등반, 몸짓과 같은 다양한 상호 작용에 사용할 수 있습니다. 또한 포인팅, 손 흔들기 및 잡기와 같은 애니메이션 중에 사실적인 손 움직임을 시뮬레이션하는 데 사용할 수 있다.

    전반적으로 Unreal Engine 4의 Hand IK는 플레이어가 가상 손을 사용하여 게임 세계에 완전히 참여할 수 있도록 하는 몰입형 인터랙티브 가상 환경을 만들기 위한 강력한 도구이다.

     

     
    Characters
      CAnimInstance.h .cpp
    CPlayer.h .cpp
    Utilities
      CHelpers.h
    CLog.h .cpp
    Weapons
      CWeapon.h .cpp
    CWeapon_AR4.h .cpp
    CWeaponComponent.h .cpp
    CGameMode.h .cpp
    Global.h
    CAnimNotifyState_Equip.h .cpp 

     

     


     

     

    Animation, AimOffset, BlendSpace, Montage 작업

     


     

     

    Rifle_Idle_AO에서 Rifle_Idle_Center, Up, Down 생성

     

    AO, Center, Up, Down

     

    모든 동작이 존재하는 Rifle_Idle_AO를 복사해서 파일 3개를 만든다.

    Center, Up, Down에 해당하는 프레임 하나를 남기고 다 지운다. 이렇게 만든 Rifle_Idle_Center, Up, Down은 AO_Idle를 만들 때 쓰인다.

     

     

     

    Additive Anim Type

    • Local Space: 수정 불가.
    • Mesh Space: 언리얼엔진 옵션으로 수정.
    • Component Space: 프로그램 영역에서 수정.

    Base Pose Type

    • Skeleton Referenc Pose: A포즈
    • Selected animation scaled: 애니메이션 커졌다 작아졌다 할 때 주로 쓰인다.
    • Selected animation frame: 매 프레임 단위로 섞어준다. (주로 사용되는 옵션이다).

    Base Pose Animation

    • 기준이 되는 애니메이션. 여기서는 Rifle_Idle로 설정하였다.

     


     

     

    AO_Rifle_Idle 생성

     

    애니메이션 - 에임 오프셋 1D - AO_Rifle_Idle 생성

    Rifle_Idle_Center, Up, Down 할당.

     


     

     

     

     

    Rifle_Aim_AO에서 Rifle_Aim_Center, Up, Down 생성

     

    위와 같은 방법으로 Rifle_Aim_Center, Up, Down 생성

     

    모든 동작이 존재하는 Rifle_Aim_AO를 복사해서 파일 3개를 만든다.

    Center, Up, Down에 해당하는 프레임 하나를 남기고 다 지운다. 이렇게 만든 Rifle_Idle_Center, Up, Down은 AO_Rifle_Aim을 만들 때 쓰인다.

     

     

    Idle에서 사용한 세팅값과 같이 넣어준다. 

     

     

     

     

     

     

     

     

     

     

     


     

     

    AO_Rifle_Aim 생성

     

    애니메이션 - 에임 오프셋 1D - AO_Rifle_Aim 생성

    Aim_Idle_Center, Up, Down 할당.

     


     

    BS_Rifle_Aim 생성

     

    Blend Space를 만든다.

    BS_Rifle를 복사하여 BS_Rifle_Aim 생성

     

     


     

     

     

    Rifle_Aim_Fixed 생성

     

    Rifle_Aim_Idle 애니메이션을 복사하여 Rifle_Aim_Fixed 생성

    0 프레임을 제외한 나머지 프레임 삭제하여 정지된 프레임으로 만들어준다. 

     

     


     

     

    Rifle_Equip_Montage - 커브 추가

     

    커브 - 커브 추가 - 커브 생성 - LeftHand 생성

    • 값 추가 - (0, 1), (1.421167, 1), (1.5, 0)

     


     

     

    CAnimInstance

     

    CAnimInstance.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Animation/AnimInstance.h"
    #include "Weapons/CWeaponComponent.h"
    #include "CAnimInstance.generated.h"
    
    UCLASS()
    class U2212_04_API UCAnimInstance : public UAnimInstance
    {
    	GENERATED_BODY()
    	
    protected:
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
    		float Speed;
    
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
    		float Pitch;
    
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
    		float Direction;
    
    protected:
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Weapons")
    		EWeaponType WeaponType = EWeaponType::Max; //기본값 Max
    
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Weapons")
    		bool bInAim;
    
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Weapons")
    		bool bUseIK;
    
    	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Weapons")
    		FVector LeftHandLocation;
    
    public:
    	void NativeBeginPlay() override;
    	void NativeUpdateAnimation(float DeltaSeconds) override;
    
    private:
    	UFUNCTION()
    		void OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType);//?
    
    private:
    	class ACPlayer* OwnerCharacter;
    	class UCWeaponComponent* Weapon;
    
    private:
    	FRotator PrevRotation; //이전 값 저장
    };

    변수 생성

    • Animation 카테고리:  bool Pitch, float Direction
    • Weapons 카테고리:  bool bUseIK, FVector LeftHandLocation
    • FRotator PrevRotation; //이전 값 저장

     

     

    CAnimInstance.cpp

    더보기
    #include "CAnimInstance.h"
    #include "Global.h"
    #include "CPlayer.h"
    
    void UCAnimInstance::NativeBeginPlay()
    {
    	Super::NativeBeginPlay();
    
    	OwnerCharacter = Cast<ACPlayer>(TryGetPawnOwner());
    	CheckNull(OwnerCharacter);
    
    	Weapon = CHelpers::GetComponent<UCWeaponComponent>(OwnerCharacter);
    	CheckNull(Weapon);
    
    	Weapon->OnWeaponTypeChanged.AddDynamic(this, &UCAnimInstance::OnWeaponTypeChanged);
    }
    
    void UCAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
    {
    	Super::NativeUpdateAnimation(DeltaSeconds);
    	CheckNull(OwnerCharacter);
    
    	Speed = OwnerCharacter->GetVelocity().Size2D();
    
    	FRotator rotator = OwnerCharacter->GetVelocity().ToOrientationRotator();
    	FRotator rotator2 = OwnerCharacter->GetControlRotation();
    	FRotator delta = UKismetMathLibrary::NormalizedDeltaRotator(rotator, rotator2);
    	PrevRotation = UKismetMathLibrary::RInterpTo(PrevRotation, delta, DeltaSeconds, 25); //선형보간 사용.
    
    	Pitch = UKismetMathLibrary::FInterpTo(Pitch, OwnerCharacter->GetBaseAimRotation().Pitch, DeltaSeconds, 25);
    	Direction = PrevRotation.Yaw;
    
    	bInAim = Weapon->IsInAim();
    	bUseIK = Weapon->IsUnarmedMode() == false;
    	LeftHandLocation = Weapon->GetLeftHandLocation();
    }
    
    void UCAnimInstance::OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType)
    {
    	WeaponType = InNewType;
    }

    NativeUpdateAnimation(float DeltaSeconds)에서 헤더에서 생성한 변수에 값을 넣어준다.

    •   rotator, rotator2를 구하여 delta로 두 값을 Normalize 한다.
      • FRotator delta = UKismetMathLibrary::NormalizedDeltaRotator(rotatorrotator2);
    • Direction
      • Direction = PrevRotation.Yaw  (방향을 이전 Rotation의 Yaw값 사용)

     


     

     

    CPlayer

     

    CPlayer.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "CPlayer.generated.h"
    
    UCLASS()
    class U2212_04_API ACPlayer : public ACharacter
    {
    	GENERATED_BODY()
    
    private:
    	UPROPERTY(EditDefaultsOnly, Category = "Camera")
    		FVector2D PitchRange = FVector2D(-40, +40);
    
    private:
    	UPROPERTY(VisibleAnywhere)
    		class USpringArmComponent* SpringArm;
    
    	UPROPERTY(VisibleAnywhere)
    		class UCameraComponent* Camera;
    
    private:
    	UPROPERTY(VisibleAnywhere)
    		class UCWeaponComponent* Weapon;
    
    public:
    	ACPlayer();
    
    protected:
    	virtual void BeginPlay() override;
    
    public:	
    	virtual void Tick(float DeltaTime) override;
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    
    private:
    	void OnMoveForward(float InAxisValue);
    	void OnMoveRight(float InAxisValue);
    	void OnHorizontalLook(float InAxisValue);
    	void OnVerticalLook(float InAxisValue);
    
    private:
    	void OnRun();
    	void OffRun();
    };

    플레이어의 시야각을 제한하기 위해 PitchRange 변수를 생성하고 Default값을 설정한다.

    •  FVector 2D PitchRange = FVector2D(-40, +40)

     

     

    CPlayer.cpp

    더보기
    #include "CPlayer.h"
    #include "Global.h"
    #include "CAnimInstance.h"
    #include "Weapons/CWeaponComponent.h"
    #include "GameFramework/SpringArmComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/InputComponent.h"
    #include "Materials/MaterialInstanceDynamic.h"
    
    ACPlayer::ACPlayer()
    {
    	PrimaryActorTick.bCanEverTick = true;
    
    	CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetCapsuleComponent());
    	CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm);
    
    	CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
    
    	USkeletalMesh* mesh;
    	CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'");
    	GetMesh()->SetSkeletalMesh(mesh);
    	GetMesh()->SetRelativeLocation(FVector(0, 0, -90));
    	GetMesh()->SetRelativeRotation(FRotator(0, -90, 0));
    
    	
    	TSubclassOf<UCAnimInstance> animInstance;
    	CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/Player/ABP_Player.ABP_Player_C'");
    	GetMesh()->SetAnimClass(animInstance);
    
    
    	GetCharacterMovement()->MaxWalkSpeed = 400;
    
    
    	SpringArm->SetRelativeLocation(FVector(0, 0, 60));
    	SpringArm->TargetArmLength = 200;
    	SpringArm->bUsePawnControlRotation = true;
    	SpringArm->bEnableCameraLag = true;
    
    }
    
    void ACPlayer::BeginPlay()
    {
    	Super::BeginPlay(); //Super가 BP의 BeginPlay를 콜한다.
    
    	GetController<APlayerController>()->PlayerCameraManager->ViewPitchMin = PitchRange.X; //각 제한 걸기
    	GetController<APlayerController>()->PlayerCameraManager->ViewPitchMax = PitchRange.Y; //각 제한 걸기
    }
    
    void ACPlayer::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    }
    
    void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    {
    	Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    	PlayerInputComponent->BindAxis("MoveForward", this, &ACPlayer::OnMoveForward);
    	PlayerInputComponent->BindAxis("MoveRight", this, &ACPlayer::OnMoveRight);
    	PlayerInputComponent->BindAxis("HorizontalLook", this, &ACPlayer::OnHorizontalLook);
    	PlayerInputComponent->BindAxis("VerticalLook", this, &ACPlayer::OnVerticalLook);
    
    	PlayerInputComponent->BindAction("Run", EInputEvent::IE_Pressed, this, &ACPlayer::OnRun);
    	PlayerInputComponent->BindAction("Run", EInputEvent::IE_Released, this, &ACPlayer::OffRun);
    
    	PlayerInputComponent->BindAction("AR4", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAR4Mode);//Object를 Weapon으로 받는다.
    
    	PlayerInputComponent->BindAction("Aim", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Aim);
    	PlayerInputComponent->BindAction("Aim", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Aim);
    
    	PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Fire);
    	PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Fire);
    }
    
    void ACPlayer::OnMoveForward(float InAxisValue)
    {
    	FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0);
    	FVector direction = FQuat(rotator).GetForwardVector();
    
    	AddMovementInput(direction, InAxisValue);
    }
    
    void ACPlayer::OnMoveRight(float InAxisValue)
    {
    	FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0);
    	FVector direction = FQuat(rotator).GetRightVector();
    
    	AddMovementInput(direction, InAxisValue);
    }
    
    void ACPlayer::OnHorizontalLook(float InAxisValue)
    {
    	AddControllerYawInput(InAxisValue);
    }
    
    void ACPlayer::OnVerticalLook(float InAxisValue)
    {
    	AddControllerPitchInput(InAxisValue);
    }
    
    void ACPlayer::OnRun()
    {
    	GetCharacterMovement()->MaxWalkSpeed = 600;
    }
    
    void ACPlayer::OffRun()
    {
    	GetCharacterMovement()->MaxWalkSpeed = 400;
    }

    BeginPlay()

    • GetController<APlayerController>()->PlayerCameraManager->ViewPitchMin = PitchRange.X; //각 제한 걸기
    • GetController<APlayerController>()->PlayerCameraManager->ViewPitchMax = PitchRange.Y;

     

    SetupPlayerInputComponent(UInputComponentPlayerInputComponent)

    • PlayerInputComponent-> BindAction("Fire", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::Begin_Fire);
    • PlayerInputComponent-> BindAction("Fire", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::End_Fire);
    • Fire 입력키값 지정.

     


     

     

     

     

     

    IK - Left Hand

     

     


     

     

    IK (Inverse Kinetic) 3가지 종류

     

    Two Bone IK

     

    CCD(Cyclic Coordinate Descent) IK

     

    FABR(Forward and Backward Reaching) IK

    • 상대간격을 구한 후 사용한다.

     


     

     

     

    ABP_Player

     

     

    Rifle Layer

    Zoom In

    • Root Bone: 어디까지 Bone이 움직일 것인가.
    • Precision: 정밀도
    • Max Iterations: 정밀도를 높이기위해 몇번이나 반복해서 계산할 것인가. 높을수록 정밀도가 높지만 연산량이 많아진다.

     

     

    IK Layer

     

     

     

    AnimGraph

     

     

     

     


     

    CWeapon

     

    CWeapon.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "CWeapon.generated.h"
    
    //직렬화 해야하는 struct는 클래스 사이에 선언해야 한다.
    USTRUCT()
    struct FWeaponAimData
    {
    	GENERATED_BODY()
    
    public:
    	UPROPERTY(EditAnywhere)
    		float TargetArmLength;
    
    	UPROPERTY(EditAnywhere)
    		FVector SocketOffset;
    
    	UPROPERTY(EditAnywhere)
    		float FieldOfView;
    
    public:
    	void SetData(class ACharacter* InOwner);
    	void SetDataByNoneCurve(class ACharacter* InOwner);
    
    };
    
    //abstract를 쓰면 Unreal Editor에서 추상 베이스 클래스로 취급하겠다는 의미. 추상이되면 배치시킬 수 없다.
    UCLASS(abstract) 
    class U2212_04_API ACWeapon : public AActor
    {
    	GENERATED_BODY()
    
    
    protected:
    	UPROPERTY(EditDefaultsOnly, Category = "Equip")
    		FName HolsterSocketName;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Equip")
    		class UAnimMontage* EquipMontage;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Equip")
    		float EquipMontage_PlayRate;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Equip")
    		FName RightHandSocketName;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Equip")
    		FVector LeftHandLocation;
    
    protected:
    	UPROPERTY(EditDefaultsOnly, Category = "Aim")
    		FWeaponAimData BaseData;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Aim")
    		FWeaponAimData AimData;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Aim")
    		class UCurveFloat* AimCurve;
    
    	UPROPERTY(EditDefaultsOnly, Category = "Aim")
    		float AimingSpeed = 200;
    
    protected:
    	UPROPERTY(EditDefaultsOnly, Category = "Hit")
    		float HitDistance = 3000;
    
    private:
    	UPROPERTY(VisibleAnywhere)
    		class USceneComponent* Root;
    
    protected:
    	UPROPERTY(VisibleAnywhere)
    		class USkeletalMeshComponent* Mesh;
    
    private:
    	UPROPERTY(VisibleAnywhere)
    		class UTimelineComponent* Timeline;
    
    public:
    	FORCEINLINE bool IsInAim() { return bInAim; }
    	FORCEINLINE FVector GetLeftHandLocation() { return LeftHandLocation; }
    
    public:	
    	ACWeapon();
    
    protected:
    	virtual void BeginPlay() override;
    
    public:	
    	virtual void Tick(float DeltaTime) override;
    
    public:
    	bool CanEquip();
    	void Equip();
    	void Begin_Equip();
    	void End_Equip();
    
    	bool CanUnequip();
    	void Unequip();
    
    public:
    	bool CanAim();
    	void Begin_Aim();
    	void End_Aim();
    
    private:
    	UFUNCTION()
    		void OnAiming(float Output); //조준
    
    public:
    	bool CanFire();
    	void Begin_Fire();
    	void End_Fire();
    
    private:
    	UFUNCTION()
    		void OnFiring(); //연사
    
    private:
    	bool bEquipping;
    	bool bInAim;
    	bool bFiring;
    	bool bReload;
    	bool bAutoFire = true;
    
    private:
    	class ACPlayer* Owner;
    };

    변수 생성

    • FName RightHandSocketName;
    • FVector LeftHandLocation;
      • CWeapon_AR4에서 LeftHandLocation 값을 넣어준다.

     

     

    Weapon.cpp

    더보기
    #include "Weapons/CWeapon.h"
    #include "Global.h"
    #include "Character/CPlayer.h"
    #include "Components/SkeletalMeshComponent.h"
    #include "Components/TimelineComponent.h"
    #include "GameFramework/SpringArmComponent.h"
    #include "Camera/CameraComponent.h"
    
    void FWeaponAimData::SetData(ACharacter* InOwner)
    {
    	USpringArmComponent* springArm = CHelpers::GetComponent<USpringArmComponent>(InOwner);
    	springArm->TargetArmLength = TargetArmLength;
    	springArm->SocketOffset = SocketOffset;
    }
    
    void FWeaponAimData::SetDataByNoneCurve(ACharacter* InOwner)
    {
    	USpringArmComponent* springArm = CHelpers::GetComponent<USpringArmComponent>(InOwner);
    	springArm->TargetArmLength = TargetArmLength;
    	springArm->SocketOffset = SocketOffset;
    
    	UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(InOwner);
    	camera->FieldOfView = FieldOfView; //curve를 이용하여 FieldOfView를 만들어준다. 
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    
    ACWeapon::ACWeapon()
    {
    	PrimaryActorTick.bCanEverTick = true;
    
    	CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
    	CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Mesh, "Mesh", Root);
    
    	CHelpers::CreateActorComponent<UTimelineComponent>(this, &Timeline, "Timeline");
    	//CurveFloat 
    	CHelpers::GetAsset<UCurveFloat>(&AimCurve, "CurveFloat'/Game/Character/Weapons/Curve_Aim.Curve_Aim'");
    }
    
    void ACWeapon::BeginPlay()
    {
    	Super::BeginPlay();
    
    	Owner = Cast<ACPlayer>(GetOwner());
    
    	if (HolsterSocketName.IsValid())
    		CHelpers::AttachTo(this, Owner->GetMesh(), HolsterSocketName);
    
    	BaseData.SetDataByNoneCurve(Owner);
    
    	if(!!AimCurve)
    	{
    		FOnTimelineFloat timeline;
    		timeline.BindUFunction(this, "OnAiming");
    
    		Timeline->AddInterpFloat(AimCurve, timeline); //이벤트 객체를 다룸.
    		Timeline->SetLooping(false);
    		Timeline->SetPlayRate(AimingSpeed);
    	}
    	
    }
    
    void ACWeapon::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    }
    
    bool ACWeapon::CanEquip()
    {
    	bool b = false;
    	//3개 다 false일 때 b = false
    	//3개 중 하나라도 true면 b = true
    	b |= bEquipping;
    	b |= bReload;
    	b |= bFiring;
    
    	return !b; //b = false일 때 True를 리턴
    }
    
    void ACWeapon::Equip()
    {
    	bEquipping = true;
    
    	if (!!EquipMontage)
    		Owner->PlayAnimMontage(EquipMontage, EquipMontage_PlayRate);
    }
    
    void ACWeapon::Begin_Equip()
    {
    	if (RightHandSocketName.IsValid())
    		CHelpers::AttachTo(this, Owner->GetMesh(), RightHandSocketName);
    }
    
    void ACWeapon::End_Equip()
    {
    	bEquipping = false;
    }
    
    bool ACWeapon::CanUnequip()
    {
    	bool b = false;
    	b |= bEquipping;
    	b |= bReload;
    	b |= bFiring;
    
    	return !b;
    }
    
    void ACWeapon::Unequip()
    {
    	if (HolsterSocketName.IsValid())
    		CHelpers::AttachTo(this, Owner->GetMesh(), HolsterSocketName);
    }
    
    bool ACWeapon::CanAim()
    {
    	bool b = false;
    	b |= bEquipping;
    	b |= bReload;
    	b |= bInAim;
    
    	return !b;
    }
    
    void ACWeapon::Begin_Aim()
    {
    	bInAim = true;
    
    	//우리는 생성자 ACWeapon()에서 AimCurve를 가져왔다.
    	if (!!AimCurve) //AimCurve가 있으면 true, 없으면 nullptr이기 때문에 false
    	{
    		Timeline->PlayFromStart();
    		AimData.SetData(Owner);
    
    		return;
    	}
    
    	AimData.SetDataByNoneCurve(Owner);
    }
    
    void ACWeapon::End_Aim()
    {
    	CheckFalse(bInAim);
    	bInAim = false;
    
    	if (!!AimCurve)
    	{
    		Timeline->ReverseFromEnd();
    		BaseData.SetData(Owner);
    
    		return;
    	}
    
    	BaseData.SetDataByNoneCurve(Owner);
    
    }
    
    void ACWeapon::OnAiming(float Output)
    {
    	UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(Owner);
    	camera->FieldOfView = FMath::Lerp(AimData.FieldOfView, BaseData.FieldOfView, Output);
    }
    
    bool ACWeapon::CanFire()
    {
    	bool b = false;
    	b |= bEquipping;
    	b |= bReload;
    	b |= bFiring; //발사하고 있는지
    
    	return !b;
    }
    
    void ACWeapon::Begin_Fire()
    {
    	bFiring = true;
    
    	OnFiring();
    }
    
    void ACWeapon::End_Fire()
    {
    	CheckFalse(bFiring);
    
    	bFiring = false;
    }
    
    void ACWeapon::OnFiring()
    {
    	UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(Owner);
    	FVector direction = camera->GetForwardVector();
    	FTransform transform = camera->GetComponentToWorld();
    
    	FVector start = transform.GetLocation() + direction; //눈알 안보다 앞쪽으로 설정하기 때문에 쏘는 방향쪽으로 조금 더해준다.
    	FVector end = transform.GetLocation() + direction * HitDistance;
    
    	DrawDebugLine(GetWorld(), start, end, FColor::Red, true, 5);
    }

     


     

    CWeapon_AR4

     

    CWeapon_AR4.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Weapons/CWeapon.h"
    #include "CWeapon_AR4.generated.h"
    
    UCLASS()
    class U2212_04_API ACWeapon_AR4 : public ACWeapon
    {
    	GENERATED_BODY()
    
    public:
    	ACWeapon_AR4();
    };

    변경사항 없음

     

     

    CWeapon_AR4.cpp

    더보기
    #include "Weapons/CWeapon_AR4.h"
    #include "Global.h"
    #include "Components/SkeletalMeshComponent.h"
    #include "Animation/AnimMontage.h"
    
    ACWeapon_AR4::ACWeapon_AR4()
    {
    	USkeletalMesh* mesh;
    	CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SK_AR4.SK_AR4'");
    	Mesh->SetSkeletalMesh(mesh);
    
    	//Equip
    	{
    		HolsterSocketName = "Rifle_AR4_Holster";
    		CHelpers::GetAsset<UAnimMontage>(&EquipMontage, "AnimMontage'/Game/Character/Animations/Rifle_Equip_Montage.Rifle_Equip_Montage'");
    		EquipMontage_PlayRate = 2.0f;
    		RightHandSocketName = "Rifle_AR4_RightHand";
    		LeftHandLocation = FVector(-32.5f, 15.5f, 7.0f);
    	}
    
    	//Aim
    	{
    		BaseData.TargetArmLength = 200;
    		BaseData.SocketOffset = FVector(0, 50, 15);
    		BaseData.FieldOfView = 90;
    
    		AimData.TargetArmLength = 80;
    		AimData.SocketOffset = FVector(0, 55, 10);
    		AimData.FieldOfView = 65;
    	}
    }

    LeftHandLocation에 Default값을 넣어준다.

    • LeftHandLocation = FVector(-32.5f, 15.5f, 7.0f);

     


     

     

     

    참고) 에디터 개인설정 - 키보드 단축키 - 월드 플레이 일시정지

     

     


     

    실행화면

     

    Hand IK가 적용되었다.

     

     


     

     

     

     

     

     

     

    ABP: Animation Blueprint
    AN: Blueprint Class AnimNotify
    ANS: Blueprint Class - AnimNotifyState

    AO: Aim Offset

    BP: Blueprint Class
    BS: Blend Space

    BF: Blueprint Function Library
    CS: Matinee Camera Shake
    E: Enum 열거형
    DT: Data Table
    F: 구조체

    I: Blueprint Interface

    WB: Widget Bluepprint

     

    UE2212_04

      Level - LEVEL
    Character Animation Rifle
    Unarmed
    Rifle_Equip_Montage
    BlendSpaces AO_Rifle_Aim
    AO_Rifle_Idle
    BS_Rifle
    BS_Rifle_Aim

    BS_Unarme
    Materials

    MaterialLayers
    M_UE4Man_Body
    M_UE4Man_ChestLogo
    Mesh SK_Mannequin
    SK_MAnnequin_PhysicsAsset
    Skel_Mannequin

    Montages
       
       
    Textures UE4_LOGO_CARD
    UE4_Mannequin__normals
    UE4_Mannequin_MAT_MASKA
    UE4Man_Logo_N
    Weapons Elven Bow
    Greate Hammer
    Sword
    Environment
    Materials Textures  
    Meshes    
    Obstacle    
    FPS_Weapon_Bundle
    Weapons
    Material  
    Meshes  
    Textures  
    Materials M_Mesh
    M_White_Inst
    M_Red_Inst
    M_Green_Inst
    M_Blue_Inst
    M_UE4Man_Body_Inst
    M_UE4Man_ChesLogo_Inst
       
    Meshes Cone_2
    Cube_2
    Cylinder_2
    Sphere_2
       
    Player ABP_Player
    BP_Player
       
    Textures Crosshair    
    Weapons
    BP_CWeapon_AR4