디버깅 드로잉

디버깅 드로잉 기능을 사용하기 위해

 

[.h]
    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
        float AttackRange;
    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
        float AttackRadius;

[.cpp]
#include "ABCharacter.h"
#include "ABAnimInstance.h"
#include "DrawDebugHelpers.h" //추가

AABCharacter::AABCharacter()
{}
...

void AABCharacter::AttackCheck()
{
    FHitResult HitResult;
    FCollisionQueryParams Params(NAME_None, false, this);
    bool bResult = GetWorld()->SweepSingleByChannel(
        HitResult,
        GetActorLocation(),
        GetActorLocation() + GetActorForwardVector() * AttackRange,
        FQuat::Identity,
        ECollisionChannel::ECC_GameTraceChannel2,
        FCollisionShape::MakeSphere(AttackRadius),
        Params);

#if ENABLE_DRAW_DEBUG

    FVector TraceVec = GetActorForwardVector() * AttackRange;
    FVector Center = GetActorLocation() + TraceVec * 0.5f;
    float HalfHeight = AttackRange * 0.5f + AttackRadius;
    FQuat CapsuleROt = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
    FColor DrawColor = bResult ? FColor::Green : FColor::Red;
    float DebugLifeTime = 5.0f;

    DrawDebugCapsule(GetWorld(),
        Center,
        HalfHeight,
        AttackRadius,
        CapsuleRot,
        DrawColor,
        false,
   DebugLifeTime );

#endif
    if (bResult)
    {
        if (HitResult.Actor.IsValid())
        {
            printf("%s",*HitResult.Actor->GetName());
        }
    }
}

 

대미지 프레임워크

  • 언리얼 엔진의 액터 클래스 AActor는 TakeDamage라는 함수가 구현돼 있고, 4개의 인자가 있다
  • DamageAmount : 전달할 대미지의 세기
  • DamageEvent : 대미지 종류
  • EventInstigator : 공격 명령을 내린 가해자
  • DamageCauser : 대미지 전달을 위해 사용한 도구
void AABCharacter::AttackCheck()
{
...
    if (bResult)
    {
        if (HitResult.Actor.IsValid())
        {
            //printf("%s",*HitResult.Actor->GetName());
            FDamageEvent DamageEvnet;
            HitResult.Actor->TakeDamage(50.0f, DamageEvnet,GetController(),this);
        }
    }
}
//h
class KGAME_API AABCharacter : public ACharacter
{
    virtual float TakeDamage(float DamageAmout, struct FDamageEvent const& DamageEvent,
        class AController* EventInstigator, AActor* DamageCauser) override;
}


//cpp
float AABCharacter::TakeDamage(float DamageAmout, struct FDamageEvent const& DamageEvent,
    class AController* EventInstigator, AActor* DamageCauser)
{
    float FinalDamage = Super::TakeDamage(DamageAmout, DamageEvent, EventInstigator, DamageCauser);
    if (FinalDamage > 0.0f)
    {
        ABAnim->SetDeadAnim();
        SetActorEnableCollision(false);
    }

    return FinalDamage;
}

 

사망시 애니메이션 추가

class KGAME_API UABAnimInstance : public UAnimInstance
{
..
    void SetDeadAnim() { IsDead = true; }

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Pawn, Meta = (AllowPrivateAccess = true))
        bool IsDead;
};


//cpp
float AABCharacter::TakeDamage(float DamageAmout, struct FDamageEvent const& DamageEvent,
    class AController* EventInstigator, AActor* DamageCauser)
{
    float FinalDamage = Super::TakeDamage(DamageAmout, DamageEvent, EventInstigator, DamageCauser);
    if (FinalDamage > 0.0f)
    {
        ABAnim->SetDeadAnim();
        SetActorEnableCollision(false);
    }

    return FinalDamage;
}

}

콜리전 채널(기본)

  • WorldStatic : 움직이지 않는 정적인 배경 액터에 사용하는 콜리전 채널. 스태믹 엑터에 있는 스태틱 컴포넌트에 사용한다.
  • WorldDynamic : 움직이는 액터에 사용하는 콜리전 채널. 블루프린트에 속한 스태틱메시 컴포넌트에 사용한다.
  • Pawn : 플레이어가 조종하는 물체에 주로 사용. 캐릭터의 충돌을 담당하는 캡슐 컴포넌트에 설정된다.
  • Visibility : 배경불체가 시각적으로 보이는지 탐지하는 데 사용한다. 탐지에서 폰은 제외된다. 마우스로 물체를 선택하는 피킹 기능을 구현할 떄 사용한다.
  • Camera : 카메라 설정을 위해 카메라와 목표뮬 간에 장애물이 있는지 탐지하는 데 사용한다.
  • PhysicsBody : 물리 시뮬레이션으로 움직이는 컴포넌트에 설정한다.

콜리전 (ollsion Enablue)

  • Quary : 두 물체의 충돌 영역이 서로 겹치는지 테스트하는 설정. 충돌 영역의 겹침을 감지하는 것은 언리얼 엔진에서 오랩이라 한다. 충돌 영역이 겹치면 관련 컴포넌트에서 BeginOverlap 이벤트가 발생한다. 지정한 영역에 물체가 충돌하는지 탐지하는 레이캐스트나 스윕기능도 Query에 속한다.
  • Physics : 물리적인 시뮬레이션을 사용할 떄 설정한다.
  • Quary and Physics : 위의 두 기능을 모두 사용하는 설정이다.

콜리전 채널

  • 오브젝트 채널 : 콜리전 영역에 지정하는 콜리전 채널 (WorldStatic,WorldDynamic,Pawn,PhysicsBody,Vehicle,Destryctuble)
  • 트레시으 채널 : 어떤 행동에 설정하는 콜리전 채널 (Visibility,Camera)

새로 추가한 컬리전 프리셋 설정 ABCharacter (오브젝트채널)

 

AABCharacter::AABCharacter()
{
...
    GetCapsuleComponent()->SetCollisionProfileName(TEXT("ABCharacter"));
}

 

트레이스 채널 추가 및 활용

 

추가할 트레이스 파라미터

  • HitResult : 물리적 충돌이 탐지된 경우 관련된 정보를 담을 구조체
  • Start : 탐색을 시작할 위치
  • End : 탐색을 끝낼 위치
  • Rot : 탐색에 사용할 도형의 회전
  • TraceChannel : 물리 충돌감지에 사용할 트레이스 채널 정보
  • CollisionShape : 탐샛ㄱ에 사용할 기본 도형 정보. 구체, 캡슐, 박스를 사용한다
  • Params : 탐색방법에 대한 설정 값을 모아둔 구조체
  • ResponseParams : 탐색 반을을 설정하이 위한 구조체

트레이스 채널 배정

  • 게임프로젝트에서 새로생성하는 오브젝트 채널과 트레이스 채널은 ECC_GameTraceCHannel 1번부터 18번 까지 중에서 하나를 배정 받는다. 어떤 값을 배정 받앗는 지 확인 할라면 그림 참고

 

코드

  • 콜리전 채널을 지정하면, FCollsioShape::MakeSphere 함수를 사용해 탐지에 사용할 도형을 제작한다. 탐색할 도형으로는 50cm 반지름을 가지는 구체를 사용하고 회전 값은 기본값을 지정한다.
  • 그 다음은 탐색영역을 지정한다. 탐색을 시작할 위치는 액터가 있는 곳으로, 끝낼 위치는 액터 시선방향으로 200cm 떨어진 곳으로 할것이다
  • 그 다음은 탐색방법을 설정한다. 공격 명령을 내리는 자신은 이탐색에 감지 되지 않도록 포인터 this를 무시할 액터 목록에 넣어줘야 한다. 마지막 인자인 탐색반응 설정은 구조체의 기본값을 사용한다
void AABCharacter::PostInitializeComponents()
{
...
    ABAnim->OnAttackHitCheck.AddUObject(this, &AABCharacter::AttackCheck);
}


void AABCharacter::AttackCheck()
{
    FHitResult HitResult;
    FCollisionQueryParams Params(NAME_None, false, this);
    bool bResult = GetWorld()->SweepSingleByChannel(
        HitResult,
        GetActorLocation(),
        GetActorLocation() + GetActorForwardVector()*200.0f,
        FQuat::Identity,
        ECollisionChannel::ECC_GameTraceChannel2,
        FCollisionShape::MakeSphere(50.0f),
        Params);
}
  • 노티파이가 호출되면, 언리얼 엔진은 이를 보고 자동으로 애님인스턴스 클래스
  • 'AnimNotify_노티파이명' 이라는 멤버 함수를 찾아서 호출한다.
  • ex) AnimNotify_AttackHitCheck();
//ABAnimInstance.h
UCLASS()
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
...
private:
	UFUNCTION()
	void AnimNotify_AttackHitCheck();

	UFUNCTION()
	void AnimNotify_NextAttackCheck();
}

//ABAnimInstance.cpp
void UABAnimInstance::AnimNotify_AttackHitCheck()
{
	//OnAttackHitCheck.Broadcast();
}

void UABAnimInstance::AnimNotify_NextAttackCheck()
{
	//OnNextAttackCheck.Broadcast();
}

 


* 애니메이션 노티파이는 노티파이 공간에서 지정한 수만큼 발생한다

 


* 이번에는 각 공격 동작을 섹션으로 분리한 후 일정 시간 내에 공격명령을 내리면 다음 공격 동작으로 이동하는 콤보 공격을 구현해 본다. Attactk2, Attactk3, Attactk4의 이름으로 새로운 섹션을 추가하고 섹션마다 공격 애니메이션을 하나씩 할당한다.

* 그리고 우클릭 메뉴로 섹션을 추가하고, 섹션을 구분하는 녹샌선을 애니메이션 사이에 적당히 드래그 하면 애니메이션 종료시점으로 섹션이 이동한다.

섹션 추가 

 

녹색 x 버튼을 눌러 각 섹션들을 독립적으로 분리


* 이제 노티파이 설정으로 가서 각 섹션별로 공격판정을 할 타이밍을 재설정한다.
* + 버튼을 눌러서 노티파이 작 업 영역을 한 줄 늘리고, 여기에는 다음 공격으로 점프할 타이밍을 파악하는 새로운 노티파이를 설정한다. 새로운 애니메이션 노티파이의 이름은 NextAttackCheck로 한다.
*  NextAttackCheck가 발동하는 타이밍은 공격 모션이 끝나고 다시 IDLE 모션으로 되돌아가기 전으로 설정한다.

* 마지막 공격인 4번쨰에는 연계할 콤보가 없으므로 NextAttackCheck는 설정하지 않는다.

노티파이 한줄 추가 등록


* 애니메이션 노티파이를 설정한 후 에는 해당 프레임에 즉각적으로 반응하는 방식인 Branching Point 값으로 틱타입을 변경하는 것 좋다 
* 기본값인 Queued로 설정하게 되면 비동기방식으로 신호를 받게 돼서 적절한 타이밍에 신호를 받는 것을 놓치게 될 수 있다.
* Queued 값은 주로 타이밍에 민감하지 않은 사운드나 이펙트를 발생시킬떄 사용하는 것이 적합니다.


 

* ABCharacter의 선언으로 가서 캐릭터가 사용할 수 있는
* 콤보 카운터의 최대치 (MaxCombo).
* 현재 실행 중인 콤보 카운터 (CurrentCombo). 
* 다음 콤보로 이동 가능 여부 (CanNextCombo),
* 콤보 입력 여부를 각각 변수 (IsComboInputOn ) 선언한다.

* 그리고 공격이 시작할 떄 관련 속성을 지정하도록 AttackStartComboState 함수와 공격이 종료할 떄, 사용할 AttackEndComboState 함수를 선언하고 구현한다.

//ABCharacter.h
{
private:
	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;
  }  
  
  //ABCharacter.cpp
  AABCharacter::AABCharacter()
{
..
	MaxCombo = 4;
	AttackEndComboState();
 }
 
 void AABCharacter::AttackStartComboState()
{
	CanNextCombo = true;
	IsComboInputOn = false;
	ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 0, MaxCombo - 1));
	CurrentCombo = FMath::Clamp<int32>(CurrentCombo + 1, 1, MaxCombo);
}

void AABCharacter::AttackEndComboState()
{
	IsComboInputOn = false;
	CanNextCombo = false;
	CurrentCombo = 0;
}
 
 
 

* 이제 ABAnimInstance 클래스로 이동해 콤보 카운트를 전달받으면 해당 몽타주 섹션을 재생하도록 기능을 구현한다.
그리고 앞서 선언한 NextAttackCheck 애니메이션 노티파이가 발생할 때마다 ABCharacter에 이를 전달할 델리게이트를 선언한고 애니메이션 노티파이 함수에서 이를 호출한다,

* 이렇게 델리게이트 기능을 사용하면, 애님 인스턴스는 자신의 델리게이트를 사용하는 객체가 어떤 것인지 몰라도 델리게이트에 연결된 함수만 호출하면 되므로, 다른 클래스와 연결되지 않는 의존성 없는 설계를 할 수 있다는 장점이 생긴다.

* 반환값과 인자 갑시 없는 함수 유형으로 델리게이트를 선언하되, 여러개의 함수가 등록되도록 멀티캐스트로 선언해본다. 멀티캐스틑 델리게이트에 등록된 모든 함수를 호출하는 멀티캐스트 델리게이트 명령은 Broadcast 이다.

//ABAnimInstance.h
DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);
DECLARE_MULTICAST_DELEGATE(FOnAttackHitCheckDelegate);


class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{

public:
	UABAnimInstance();
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

	void PlayAttackMontage();
	void JumpToAttackMontageSection(int32 NewSection);

public:
	FOnNextAttackCheckDelegate OnNextAttackCheck;
	FOnAttackHitCheckDelegate OnAttackHitCheck;
    
private:
	UFUNCTION()
	void AnimNotify_AttackHitCheck();

	UFUNCTION()
	void AnimNotify_NextAttackCheck();

	FName GetAttackMontageSectionName(int32 Section);
};

//ABAnimInstance.cpp

void UABAnimInstance::JumpToAttackMontageSection(int32 NewSection)
{
	ABCHECK(!IsDead);
	ABCHECK(Montage_IsPlaying(AttackMontage));
	Montage_JumpToSection(GetAttackMontageSectionName(NewSection), AttackMontage);
}

void UABAnimInstance::AnimNotify_AttackHitCheck()
{
	OnAttackHitCheck.Broadcast();
}

void UABAnimInstance::AnimNotify_NextAttackCheck()
{
	OnNextAttackCheck.Broadcast();
}

FName UABAnimInstance::GetAttackMontageSectionName(int32 Section)
{
	ABCHECK(FMath::IsWithinInclusive<int32>(Section, 1, 4), NAME_None);
	return FName(*FString::Printf(TEXT("Attack%d"), Section));
}

* 플레이어가 공격 명령을 내리면 ABCharacter는 콤보가 가능한지 아닌지 파악하고 각상황에 따라 대응한다. 공격을 시작하고 NextAttackCheck 타이밍 전까지 공격 명령이 들어오면 NextAttackCheck 타이밍에서 다음 콤보 공격을 시작한다. 이를 파악하기 위해 CanNextcombo 속성을 사용한다.

* ABAnimInstance의 OnNextAttackCheck 델리게이트와 등록할 로직을 ABCharacter에서 선언하고 구현한다.

//AABCharacter.cpp

void AABCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
	ABCHECK(nullptr != ABAnim);

	ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);

	ABAnim->OnNextAttackCheck.AddLambda([this]() -> void{

		ABLOG(Warning, TEXT("OnNextAttackCheck"));
		CanNextCombo = false;

		if (IsComboInputOn)
		{
			AttackStartComboState();
			ABAnim->JumpToAttackMontageSection(CurrentCombo);
		}
	});
}

void AABCharacter::Attack()
{
	if (IsAttacking)
	{
		ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 1, MaxCombo));
		if (CanNextCombo)
		{
			IsComboInputOn = true;
		}
	}
	else
	{
		ABCHECK(CurrentCombo == 0);
		AttackStartComboState();
		ABAnim->PlayAttackMontage();
		ABAnim->JumpToAttackMontageSection(CurrentCombo);
		IsAttacking = true;
	}
}

void AABCharacter::OnAttackMontageEnded(UAnimMontage * Montage, bool bInterrupted)
{
	ABCHECK(IsAttacking);
	ABCHECK(CurrentCombo > 0);
	IsAttacking = false;
	AttackEndComboState();

	OnAttackEnd.Broadcast();
}

 

 

* 프로그래밍 용어인 델리게이트는 넓은 의미로 본다면 특정 객체가
해야 할 로직을 다른 객체가 대신 처리할 수 있도록 만드는 보편적인 설계의 개념을 의미한다. 

* 언리얼 엔진의 델리게이트는 A 객체가 B객체에 작업 명령을 내릴 때,
B객체에 자신을 등록하고 B의 작업이 끝나면, 이 떄 A에게 알려주는 설계방식을 의미한다.

* A가 B가 요구하는 형식으로 멤버 함수를 만들면 이를 B에 등록 할 수 있고,
B가 특정 상황이 될떄, B는 미리 등록해둔 A의 멤버 함수를 호출해주는 방식으로 동작한다.

* C++ 언어 이후 제작된 C# 과 같은 언어는 이러한 델리게이트 시스템을 기본으로 제공한다. 오래된 언어인  C++ 에선 지원하지 않기에, 언리얼 엔진에서 델리게이트를 사용하려면 별도로 구축한 델리게이트 프레임워크를 사용해야 한다.

* 애님 인스턴스에는 애니메이션 몽타주 재생이 끝나면 발동하는 OnMontageEnded라는 델리게이트를 제공한다. 어떤 언리얼 오브젝트라도 UAnimMontage * 인자와 bool 인자를 가진 멤버 함수를 가지고 있다면, 이를 OnMontageEnded 델리게이트에 등록해 몽타주 재생이 끝나는 타이밍을 파악 할수 있다.

* ABCharacter 액터에 위의 함수 형식을 선언하고, 윗줄에 UFUNCTION이라는 매크로를 추가로 선언한다.
  OnMontageEnded 델리게이트는 블루프린트의 함수와도 연동할 수 있도록 설계돼 있으므로, C++에서 연동하려는 함수는 블루프린트와 호환되는 함수형으로 선언해야 하기 떄문이다.
  해당 인자를 가지는 함수를 ABCharacter 클래스에 선언하고, PostInitializeComponents에서 해당 함수를 애님 인스턴스의 OnMontageEnded 델리게이트에 바인딩 한다. 그리고 현재 공격 중인지 아닌지를 파악 할 수 있도록 bool  변수를 추가로 선언한다.

 

※ 언리얼 에서 델리게이트는 C++ 객체에만 사용할 수 있는 델리게이트와 C++, 블루프린트 객체가 모두 사용할 수 있는 델리게이트로 나뉜다. 블루프린트 오브젝트는 멤버 함수에 대한 정보를 저장하고 로딩하는 직렬화 매커니즘이 들어있기 때문에 일반 C++ 언어가 관리하는 방법으로 멤버 함수를 관리 할수 없다. 

 그래서 블루프린트와 관련된 C++함수는 모두 UFUNCTION 매크로를 사용해야 한다.
이렇게 블루프린트 객체와도 연동하는 델리게이트를 언리얼 엔진에서는 다이내믹 델리게이트라고 한다.

다이나믹 딜리게이트 함수포인터가 아닌, 함수의 이름을 기반으로 등록해 호출하는 방
C++ 함수 뿐만 아니라 블루프린트 함수도 연결할 수 있게 하기 위해서입니다.

//ABCharacter.h
{
public:	
	virtual void PostInitializeComponents() override;
private:
	UFUNCTION()
	void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);
private:
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	bool IsAttacking;
}    

* ABCHECK라는 매크로를 추가해 런타임에서 문제가 발생할 떄 붉은색으로 에러로그를 뿌리고 바로 함수를 반환하도록 기능을 추가한다.

//ArenaBattle.h
#define ABCHECK(Expr, ...) {if(!(Expr)) {ABLOG(Error, TEXT("ASSERTION : %s"), TEXT("'"#Expr"'")); return __VA_ARGS__;}}

 

* 매크로를 활용해 애님 인스턴스의 OnMontageEnded 델리게이트와 선언한 OnAttackMontageEnded를 연결해, 델리게이트가 발동할 때까지 애니메이션 시스템에 몽타주 재생 명령을 내리지 못하게 폰 로직에서 막아준다.

//ABCharacter.cpp

ABCharacter::ABCharacter()
{
  IsAttacking = false;
}


void AABCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
	ABCHECK(nullptr != ABAnim);

	ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}

void AABCharacter::Attack()
{
	if (IsAttacking) return;
    
	auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
    if(nullptr == AnimInstance) return;
    
    AnimInstane->PlayAttackMontage();
	ABAnim->PlayAttackMontage();
    
    IsAttacking = true;
}

void AABCharacter::OnAttackMontageEnded(UAnimMontage * Montage, bool bInterrupted)
{
	ABCHECK(IsAttacking);
	IsAttacking = false;
}

 


※ OnMontageEnded 델리게이트는 블루프린트와 호환되는 성질 외에도 여러 개의 함수를 받을 수 있어서 행동이 끝나면 등록된 모든 함수들에게 모두 알려주는 기능도 제공한다. 이러한 델리게이트르르 멀티캐스트 델리게이트 라고한다
블루프린트 사용가능 + 여러개 함수가능

 애님 인스턴스 헤더에 선언된 OnMontageEnded가 사용하는 델리게이트를 저으의한 코드는 다음과 같다. 언리얼 엔진에서 델리게이트의 선언은 언리얼이 제공하는 매크로를 통해 정의되며, 이렇게 정의된 델리게이트의 형식을 시그니처라고 한다.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnMontageEndedMCDelegate, UAnimMontage*,
Montage,bool,bInterrupted);

※ 이렇게 두 가지 기능이 있는  OnMontageEnded 델리게이트는 다이내믹 멀티캐스트 델리게이트라고 할 수 있다.
참고로 다이내믹 멀티캐스트 델리게이트에서 사용하는 AddDynamic 함수는 코딩할 떄 비주얼 C++ 인텔리센스에서 검색되지 않는다. 인텔리센스를 무시하고 타이핑해도 컴파일에는 문제가 없다

C++ 함수 뿐만 아니라 블루프린트 함수도 연결 + 
 블루프린트에서 사용할 수 있게


 

* 앞으로 캐릭터 클래스에서 애님 인스턴스를 자주 사용할 예정이므로, 이를 멤버 변수로 선언해 런타임에서 이를 활용하도록 구조를 변경한다. 
* UABAnimInstance 클래스의 멤버 변수를 선언할 떄 이를 전방 선언으로 설계하는 것이 바람직하다. 전방 선언은 헤더파일에서 같은 모듈에 있는 다른 헤더 파일을 참조하지 않아도 되므로 상호참조를 방지하는 한편, 코드 구조를 관리하기도 좀 더 용이 해진다.

* 이제 폰 로직에서 입력이 들어오면 애님 인스턴스의 PlayAttack을 호출하도록 로직을 추가한다.

//ABCharacter.h
{
private:
	UPROPERTY()
	class UABAnimInstance* ABAnim;
} 

//ABCharacter.cpp
void AABCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
	ABCHECK(nullptr != ABAnim);

	ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}

void AABCharacter::Attack()
{
	if (IsAttacking) return;

	ABAnim->PlayAttackMontage();
	IsAttacking = true;
}

 

* 델리게이트에 의해 공격의 시작과 종료가 감지되므로, AnimInstance에서 사용한 Montage_IsPlaying 함수는 사용하지 않아도 무방하다. 

//ABAnimInstance.cpp
void UABAnimInstance::PlayAttackMontage()
{
	Montage_Play(AttackMontage, 1.0f);
}

* 캐릭터에 공격 기능을 넣기 위해 공격 입력을 설정하고 공격 입력을 누를 때마다 캐릭터가 연속된 모션으로 공격하도록 애니메이션 기능을 구현한다

* 언리얼 엔진은 스테이트 머신의 확장 없이 특정 상황에서 원하는 애니메이션을 방동시키는 애니메ㅣ션 몽타주라는 기능 제공한다. 이를 활용한 콤보 공격기능을 구현해본다.

* 애니메이션창을 열고 애님 몽타주 생성 메뉴를 눌러 몽타주 애셋을 생성한다.

 


 

* 몽타주는 섹션을 단위로 애니메이션을 관리한다.
* 본래 몽타주는 촬영된 화면이나 인쇄된 종이를 뗴어 붙여서 새로운 장면이나 이미지를 만드는 미술 기법을 의미한다. 애니메이션 몽타주도 이와 유사하게 여러 애니메이션 클립들의 일부를 뗴어내고 붙여서 새로운 애니메이션을 생성하는 기법이다.
* 이때 섹션 단위로 애니메이션들을 자르고 붙이는 작업을 진행한다.

몽타주 애셋을 생성하면 기본으로 Default라는 이름의 섹션이 주어진다. 

기본 생성시 화면


 

* 이제 섹션을 클릭하고, 디테일 창에서 Default에서 Attack1으로 변경한다. 

* 준비된 공격 애니메이션을 1부터 4까지 드래그 하면, 클립이 지그재그로 배치된다.

드래그를 하면 지그재그로 배치가 된다.

 


* 이제 입력에서 Attack 맵핑을 추가하고, 이를 바인딩 한다

//ABCharacter.h
{
public:
	void Attack();
}

//ABCharacter.cpp
void AABCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
   PlayerInputComponent->BindAction(TEXT("Attack"), EInputEvent::IE_Pressed, this, &AABCharacter::Attack);
}

 

* 이번에는 애님 인스턴스에 멤버 함수화 변수를 생성하고 함수를 실행하면 몽타주 애니메이션을 재생하도록 기능을 구현한다.
* Montage_IsPlaying 함수를 사용해 현재 몽타주가 재생하는지 파악하고, 재생 중이 아니면 Montage_Play 함수를 사용해 재생하도록 로직을 구현한다.

 

//ABAnimInstance.h
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
public:	
    void PlayAttackMontage();
private:
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	UAnimMontage* AttackMontage;
}

//ABAnimInstance.cpp
#include "ABAnimInstance.h"

UABAnimInstance::UABAnimInstance()
{
...
	static ConstructorHelpers::FObjectFinder<UAnimMontage> ATTACK_MONTAGE(TEXT("/Game/Book/Animations/SK_Mannequin_Skeleton_Montage.SK_Mannequin_Skeleton_Montage"));

	if (ATTACK_MONTAGE.Succeeded())
	{
		AttackMontage = ATTACK_MONTAGE.Object;
	}
}

void UABAnimInstance::PlayAttackMontage()
{
	if(!Montage_isPlaying(AttackMontage))
    {
	 Montage_Play(AttackMontage, 1.0f);
    }
}

 

* 컴파일 하고 애니메이션 블루프린트를 열어보면, 해당 속성에 몽타주 애셋이 자동 할당된 것을 볼 수 있다.


 

* 몽타주에게 재생 ㅁ병령을 내려도 애니메이션 블루프린트에서 이를 재생하려면 몽타주 재생 노드를 애님 그래프에 추가해야 하낟. 이 몽타주 재생 노드를 적당한 애니메이션 재생 흐름 사이에 끼워 넣으면 해당 타이밍에 발동할 수 있다. 

* 우리는 모든 상황에서 몽타주를 재생할 예정이므로 애님 그래프의 최종 애니메이션 포즈와 스테이트 머신 사이에 몽타주 재생 노드를 추가해 본다.

 

* 노드 완성 후 캐릭터에게 몽타주를 사용해 공격 애니메이션을 재생하라는 명령을 내린다.

//AABCharacter.cpp

#include "ABGameInstance.h"
void AABCharacter::Attack()
{
  auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
  if(nullptr == AnimInstance) return;
  
  AnimInstance->PlayAttackMontage()
}

 

* 이제 실행을 하고 마우스 공격 클릭을 하면 공격 애니메이션이 재생된다.

+ Recent posts