TL:DR
마우스 클릭 시 공격 애니메이션 재생.
공격 애니메이션을 어떻게 추가할까 고민하다가 애니메이션 스테이트 머신에 적용할려고 했다. 하지만 강좌를 찾는 도중에 이 강좌를 찾았다!
https://youtu.be/cWWuDf2ZUj0?list=PLCeaAi_Ah78SEV2Q-iVuFbe6xOtQYH6sH
몽타주 추가 1 2 3 UPROPERTY (EditDefaultsOnly, BlueprintReadOnly, Category = Animation, meta = (AllowPrivateAccess = "true" ))class UAnimMontage * MeleeAttackMontage;
1 2 3 4 5 6 static ConstructorHelpers::FObjectFinder<UAnimMontage> MeleeAttackMontageObject (TEXT("AnimMontage'/Game/Animation/PrimaryAttack_C_Slow_Montage1.PrimaryAttack_C_Slow_Montage1'" )) ;if (MeleeAttackMontageObject.Succeeded ()) { MeleeAttackMontage = MeleeAttackMontageObject.Object; }
몽타주는 기존에 있는 것을 팔라곤 에셋인 것을 사용하기로 하고 몽타주는 따로 제작하지 않았다. 몽타주를 읽기 위해서 프로젝트 폴더내에 있는 몽타주를 가져와서 읽는데 경로는 해당하는 레퍼런스 복사를 누르면 된다.
20.06 나중에 리소스 삭제할려고 할 때 레퍼런스 참조로 삭제가 힘들게 뜬다. 삭제한다고 하면, 나중에 컴파일 때 엔진이 크래시나는 문제가 있다. 블루프린터로 따로 빼서 리소스를 등록하게 하는 것이 편하다.
몽타주 실행 함수 1 2 3 4 void AMyPlayer::AttackStart () { PlayAnimMontage (MeleeAttackMontage, -1.f , FName ("Default" )); }
설정해놓은 몽타주 애니메이션을 재생시킨다.
키 맵핑 1 2 PlayerInputComponent->BindAction ("Attack" , IE_Pressed, this , &AMyPlayer::AttackStart); PlayerInputComponent->BindAction ("Attack" , IE_Released, this , &AMyPlayer::AttackEnd);
마우스 키를 눌렸을 때 만들어준 AttackStart 함수를 호출하고 마우스 키를 뗄 떼는 AttackEnd함수를 호출한다.
1 2 3 4 void AMyPlayer::AttackEnd () { GEngine->AddOnScreenDebugMessage (-1 , 5.0f , FColor::Blue, __FUNCTION__); }
AttackEnd는 별 것 없다. 그냥 쉽게 로그를 확인하기 위해서이다. 뭔가 끝나는 동작이 이상하다. 애니메이션이 검을 내리고 빠르게 원상 복귀가 되는 것이다.
2019/11/12
1 2 - PlayAnimMontage(MeleeAttackMontage, -1.f, FName("Default")); + PlayAnimMontage(MeleeAttackMontage, +1.f, FName("Default"));
-1.f가 아니라 1로하면 자연스럽게 애니메이션이 재생된다. 난 바보 같이 -1로 한거나….ㅠㅠ
검에 콜라이더 추가. 애니메이션이 재생되었다고해도 콜라이더를 붙여서 다른 오브젝트와 충돌 상호작용을 해야한다.
그래서 박스 콜라이더를 붙여보았다.
1 2 UPROPERTY (VisibleAnywhere, BlueprintReadOnly, Category = Attack)class UBoxComponent * MeleeCollisionBox;
1 2 3 4 MeleeCollisionBox = CreateDefaultSubobject <UBoxComponent>(TEXT ("MeleeCollisionBox" )); MeleeCollisionBox->SetupAttachment (RootComponent); MeleeCollisionBox->SetCollisionProfileName ("NoCollision" ); MeleeCollisionBox->SetHiddenInGame (false );
하지만 원하는 것은 검의 움직임에 따라 같이 콜라이더가 움직이는 것인데 그렇지 않다. ㅠㅠ
https://youtu.be/IWnSlOmVhTM?list=PLCeaAi_Ah78SEV2Q-iVuFbe6xOtQYH6sH
하지만 그 다음 강좌에 콜라이더를 붙이는 강좌가 있다.
1 2 3 4 5 6 7 8 void AMyPlayer::BeginPlay () { Super::BeginPlay (); const FAttachmentTransformRules AttackmentRules (EAttachmentRule::SnapToTarget, EAttachmentRule::SnapToTarget, EAttachmentRule::KeepWorld, false ) ; MeleeCollisionBox->AttachToComponent (GetMesh (), AttackmentRules, "weapon_r_collison" ); GetCapsuleComponent ()->OnComponentBeginOverlap.AddDynamic (this , &AMyPlayer::OnOverlapBegin); }
부착 규칙을 설정해서 콜라이더를 붙여준다. 해당 메시의 스켈레톤의 “weapon_r_collision”의 중심점으로 만든 콜라이더가 붙는다. 콜라이더가 검의 움직임에 맞춰서 동작하는 것을 볼 수 있다!!!!
상호 작용하는 모습은 다음과 같다.
콤보 공격 만들기 기획서상에 콤보 공격이 존재한다!!! 앞에서 한 것은 콤보공격을 위한 준비과정일뿐.
http://joyeeeeeee.blogspot.com/2017/11/unreal491-combo.html
콤보 공격의 아이디어를 여기서부터 출발했다. 애니메이션 몽타주를 배열로 만든 다음 콤보 수를 배열의 인덱스로 사용하여 재생시키기로 했다!!!!
몽타주 배열 정의 1 2 UPROPERTY (EditDefaultsOnly, BlueprintReadOnly, Category = Animation, meta = (AllowPrivateAccess = "true" )) TArray<UAnimMontage * >MeleeAttackMontages;
공격 몽타주를 정의해준다. 언리얼에서는 배열을 TArray
로 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static ConstructorHelpers::FObjectFinder<UAnimMontage> MeleeAttackMontageObject1 (TEXT("AnimMontage'/Game/Animation/PrimaryAttack_A_Slow_Montage1.PrimaryAttack_A_Slow_Montage1'" )) ;if (MeleeAttackMontageObject1.Succeeded ()) { MeleeAttackMontages.Add (MeleeAttackMontageObject1.Object); }static ConstructorHelpers::FObjectFinder<UAnimMontage> MeleeAttackMontageObject2 (TEXT("AnimMontage'/Game/Animation/PrimaryAttack_B_Slow_Montage1.PrimaryAttack_B_Slow_Montage1'" )) ;if (MeleeAttackMontageObject2.Succeeded ()) { MeleeAttackMontages.Add (MeleeAttackMontageObject2.Object); }static ConstructorHelpers::FObjectFinder<UAnimMontage> MeleeAttackMontageObject3 (TEXT("AnimMontage'/Game/Animation/PrimaryAttack_C_Slow_Montage1.PrimaryAttack_C_Slow_Montage1'" )) ;if (MeleeAttackMontageObject3.Succeeded ()) { MeleeAttackMontages.Add (MeleeAttackMontageObject3.Object); }
몽타주들을 배열에 넣어준다. 그러면 인덱스 값에 따라 몽타주들을 관리할 수 있다!
그 다음 콤보에 대한 설정을 해준다.
1 2 3 4 5 bool m_bCombo;int m_iCombo;
두개의 변수를 만든다. 콤보 상태인가 아닌가를 구분해 줄 bool형 변수와 현재 콤보가 몇 인지 알려주는 변수 2개를 추가했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 void AMyPlayer::AttackStart () { if (m_bCombo) { m_iCombo = (m_iCombo + 1 ) % 3 ; } else { m_bCombo = true ; m_iCombo = 0 ; } PlayAnimMontage (MeleeAttackMontages[m_iCombo], 1.f , FName ("Default" )); }
공격을 했을 때 콤보 상태인지 아닌지를 확인해서 콤보 상태일 경우 콤보를 1증가시키고 아닐 경우에는 콤보 상태로 바꾸고, 콤보를 0으로 초기화시킨다. 그 이후 콤보 애니메이션을 재생시킨다.
1 2 3 4 5 void AMyPlayer::AttackEnd () { m_bCombo = false ; m_iCombo = 0 ; }
AttackEnd함수에서 콤보를 취소시킨다. 빠르게 첫 번째 애니메이션만 재생이 된다. ㅠㅠ 그 이유를 알아보니 AttackEnd함수는 마우스를 뗄 떼 콤보랑 콤보 상태가 초기화되기 때문이다. 애니메이션이 종료될 때 AttackEnd함수를 호출해야 한다.
그렇다면 애니메이션이 끝날 때를 판단할 수 있을까?https://darkcatgame.tistory.com/4
이 블로그를 참고하여 인터페이스 함수를 제작하고 그 함수를 호출시키는 Anim Notify 제작 후 연동시켰다.
Interface Class 제작 1 2 3 4 5 UFUNCTION (BlueprintNativeEvent, BlueprintCallable, Category = "Itf_AnimState" )void Itf_ResetCombo () ;
캐릭터 클래스에서 함수 정의를 해준다. 참고로 캐릭터 클래스는 Itf_AnimationNotify를 상속받아야 한다.
1 2 3 4 5 6 UFUNCTION (BlueprintNativeEvent, BlueprintCallable, Category = "Itf_AnimState" )void Itf_ResetCombo () ;virtual void Itf_ResetCombo_Implementation () override ;
1 2 3 4 5 6 7 void AMyPlayer::Itf_ResetCombo_Implementation () { m_bCombo = false ; m_iCombo = 0 ; }
인터페이스로 만든 함수가 호출 시 이 함수가 호출 될 것이다. 호출이 되면 애니메이션이 끝나게 되는 상황이기 때문에 콤보를 초기화 해준다. 그 다음 애니메이션에서 애니메이션이 끝나는 부분에 Interface함수를 호출해야 한다.
그렇게 하기 위해서는 AnimNotify를 만들어 준 후 Anim Notify Blueprint 클래스를 제작해야한다.
AnimNotify Blueprint 클래스를 제작한다.
Interface함수를 호출하는 노드를 제작한다.
원하는 애니메이션 시점에 만들어준 Notify를 추가하면 된다. 이렇게 해서 끝나는 시점까지 누르지 않으면 콤보가 초기화 되는 조작을 하게 되었다.
그런데 말입니다…. 누르는 속도에 따라 엄청나게 빠르게 칼질하는 플레이어의 모습을 볼 수 있다.
이렇게 되는 이유는 마우스 클릭할 때마다 AttackStart()의 애니메이션이 재생되기 때문이다. 자연스러운 동작을 보고 싶으면 애니메이션이 끝날 때 콤보가 있을 경우 다음 애니메이션으로 보여주게 하는 것이다.
그러면 우선 AttackStart부분을 수정해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void AMyPlayer::AttackStart () { if (m_bAttack) { m_bCombo = true ; } else { m_bAttack = true ; m_iCombo = 0 ; GEngine->AddOnScreenDebugMessage (-1 , 5.0f , FColor::Blue, __FUNCTION__); PlayAnimMontage (MeleeAttackMontages[m_iCombo], 1.f , FName ("Default" )); } }
m_bAttack
변수를 주어 그것이 true
일 때 클릭하는 경우 콤보상태로 바꾸고 그렇지 않을 경우에는 m_bAttack
상태를 true
로 바꾼다음 콤보를 0으로 바꿔준다.
그 이유는 m_bAttack
상태가 false
인 경우 현재 공격이 아닌 다른 애니메이션이 재생되었다가 공격을 하는 거라서 첫번째 애니메이션이 재생이 되어야 한다. 그래서 m_iCombo
를 0
으로 초기화한 것이다.
이렇게 m_bAttack
상태를 true
로 바꿔서 한번 더 누를 경우 m_bAttack
이 true
이기 때문에 콤보상태가 true
로 바꿔진다.
그 다음 Itf_ResetCombo_Implementation
에서 콤보와 애니메이션을 설정 작업을 해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void AMyPlayer::Itf_ResetCombo_Implementation () { GEngine->AddOnScreenDebugMessage (-1 , 5.0f , FColor::Red, __FUNCTION__); if (m_bCombo) { m_iCombo = (m_iCombo + 1 ) % 3 ; PlayAnimMontage (MeleeAttackMontages[m_iCombo], 1.f , FName ("Default" )); } else { m_bAttack = false ; m_bCombo = false ; m_iCombo = 0 ; } m_bCombo = false ; }
그 후 애니메이션이 설정한 시간을 도달하게 되면 함수가 호출된다. 우선 콤보상태일 경우 콤보를 하나 더 증가시킨다.(애니메이션이 종료되기 전에 공격 키를 눌려 콤보를 유지시켰기 때문에) 그 후 증가시킨 인덱스의 애니메이션을 재생시킨다.
콤보상태가 아닐 경우 설정해둔 변수들을 초기화 시켜준다.(공격이 계속 진행되는 상황이라도 그 공격 애니메이션이 콤보 종료 상태가 들어가기 전에는 한 번 더 조작키를 눌려줘야지 콤보로 인식하기 때문이다.)
그 후 m_bCombo
는 무조건 초기화를 해줘야한다.
이렇게 아무리 빨리 눌려도 애니메이션이 끝나야지 다음 애니메이션으로 넘어가게 만들었다. 자연스러운 애니메이션 동작이 확인가능하게 되었다. 짝짝짝-
여담 2023.02.29
오랜만에 옛날 자료를 보다가 잘 정리된 자료를 찾았습니다. 졸업과제 하면서 시행착오도 거치고, 많이 공부했다는 것이 보이네요. 옛날 자료이기는 하지만 누군가가 보고 도움이 되기를 바라면 백업을 해두겠습니다.