Unreal Engine 5/UE5 멀티대전게임 일기

UE5 멀티대전게임 일기(10) 스킬 리플리케이션 구조

언리얼배우기 프로젝트 2025. 4. 9. 21:29

애니메이션 재생 방식

 

1. 클라이언트에서 PlayerController를 통해 EnhancedInput을 받으면 


2. 서버 RPC로 요청 전송

PlayerController.cpp


....

void ASW_PlayerController::ComboAttack(const FInputActionValue& InputValue)
{
    if (ASW_CharacterBase* PlayerCharacter = Cast<ASW_CharacterBase>(GetPawn()))
    {
        if (!PlayerCharacter->bIsLocked)
        {
            PlayerCharacter->Server_PlaySkill("ComboAttack");
        }
    }
}


void ASW_PlayerController::JumpAttack(const FInputActionValue& InputValue)
{
    if (ASW_CharacterBase* PlayerCharacter = Cast<ASW_CharacterBase>(GetPawn()))
    {   
        // 점프중일때만 실행되도록
        if (!PlayerCharacter->bIsLocked && PlayerCharacter->GetCharacterMovement()->IsFalling()) 
        {
            PlayerCharacter->Server_PlaySkill("JumpAttack");
        }
    }
}


void ASW_PlayerController::NormalSkill(const FInputActionValue& InputValue)
{
    if (ASW_CharacterBase* PlayerCharacter = Cast<ASW_CharacterBase>(GetPawn()))
    {
        if (!PlayerCharacter->bIsLocked)
        {
            PlayerCharacter->Server_PlaySkill("NormalSkill");
        }
    }
}

void ASW_PlayerController::SpecialSkill(const FInputActionValue& InputValue)
{
    if (ASW_CharacterBase* PlayerCharacter = Cast<ASW_CharacterBase>(GetPawn()))
    {
        if (!PlayerCharacter->bIsLocked)
        {
            PlayerCharacter->Server_PlaySkill("SpecialSkill");
        }
    }
}

void ASW_PlayerController::DashSkill(const FInputActionValue& InputValue)
{
    if (ASW_CharacterBase* PlayerCharacter = Cast<ASW_CharacterBase>(GetPawn()))
    {
        if (!PlayerCharacter->bIsLocked)
        {
            PlayerCharacter->Server_PlaySkill("DashSkill");
        }
    }
}

 

Server_PlaySkill(SkillName)을 통해 서버 RPC로 요청을 한다.


3. 서버에서 애니메이션 재생 + Multicast 전송

CharacterBase.cpp

 

Multicast_Server_PlaySkill(FName SkillName)을 통해 서버에서 애니메이션이 재생되도록함

 

각 스킬에 맞춰서CharacterBase를 상속받은 캐릭터에 스킬 애니메이션이 재생 됨

 

ComboAttack은 왜 따로 Multicast_ComboAttack()함수를 사용하는가

 

콤보 어택같은 경우는 다른 스킬들과 구조가 다른데 콤보 어택은 내부적인 상태 변수(CurrentComboIndex, bPendingNextCombo, bCanNextCombo)를 사용해서 연속 공격을 처리하는 방식이다.

CharacterBase.cpp

 

그렇기 때문에 클라이언트한테 "함수 자체"를 실행시켜야한다.

 

그래서 따로 Multicast_ComboAttack()함수를 사용하게 된다.

CharacterBase.cpp

아래와 똑같이 서버가 아니면 스킬 애니메이션이 재생된다.


4. 클라이언트에서 애니메이션이 재생되도록 멀티캐스트

CharacterBase.cpp

멀티캐스트로 서버가 아니면 스킬애니메이션이 재생된다.

 


데미지 처리 방식

 

1. ApplyDamage노티파를 통해 애니메이션이 ApplyDmaage노티파이를 만나면

SkillAnimNotify.cpp

 

Server_ApplySkillDamage(FName SkillName)함수를 호출해서 각 캐릭터에 스킬이름마다 정해진 콜리전과 데미지를 줌


2. 서버에서 대상을 탐지 후 데미지를 계산

CharacterBase.cpp

 

SkillsAppliedThisFrame.Contains(SkillName)은 프레임마다 같은 스킬이 두 번 이상 호출되지 않도록 필터링을 해주는 것으로 

ApplyDamage노티파이가 서버 + Multicast 애니메이션에서 두 번 발생할 수 있어서 막기위해 필요

 

SkillsAppliedThisFrame.Add(SkillName)은 중복 방지용으로 한 번 처리된 스킬 이름을 저장한다. 그리고 이 변수를 Tick()함수에서 매 프레임마다 초기화함

CharacterBase.h
CharacterBase.cpp

 

ApplySkillDamage(FName SkillName, const TArray<AActor*>& Targets)함수는 SkillDateMap에서 해당 스킬의 데미지 배율, 범위, 타입 정보를 가져오고, 실제 데미지는 TakeDamage()로 처리한다. (데미지는 무조건 서버에서만 적용된다.)

CharacterBase.cpp

 

3. 멀티캐스트로 모든 클라이언트에 브로트캐스트

CharacterBase.cpp

데미지를 적용하는 함수는 아니고, 서버에서 데미지를 처리한 후, 클라이언트에서는 애니메이션만 보여주게 된다. 


궁금증 해결

 

Server_PlaySkill_Implementation(FName SkillName)

Server_ApplySkillDamage_Implementation(FName SkillName)

이 두개도 똑같은 역할이고

 

Multicast_PlaySkill_Implementation(FName SkillName)

Multicast_ApplySkillDamage_Implementation(FName SkillName)

이 두개도 똑같은 역할로

 

같은건데 그냥 한개로 통일시키켜서 사용하면 되는거아닌가

 

결론

❌ 애니메이션 타이밍과 안 맞음 (예: 칼 휘두르기 전에 데미지 들어감)

❌ 클라에선 타격 효과/모션은 보이는데, 맞은 쪽은 데미지 적용 안 되는 경우 발생

❌ AnimNotify가 아예 의미 없어짐

 

예시: "Brall이 평타 콤보를 날리는 상황"

Server_PlaySkill("NormalSkill") → 스킬 시작: 애니메이션 재생
몽타주 도중 타이밍 맞춰서 AnimNotify("ApplyDamage") 발생
→ 이때 Server_ApplySkillDamage("NormalSkill") 호출
→ 진짜 데미지를 타겟에게 한 번만 적용

 


리플리케이션 동작 요약

항목위치 (함수/파일)설명

항목 
위치 (위치/함수)
원리
입력 처리 ASW_PlayerController::NormalSkill() 클라 → 서버로 입력 전달
서버 스킬 실행 ASW_CharacterBase::Server_PlaySkill() 서버에서 애니메이션 실행
클라 애니 재생 Multicast_PlaySkill() 클라에서 애니 재생
데미지 노티파이 USkillAnimNotify::Notify() 서버에서만 데미지 처리 시작
데미지 중복 방지 SkillsAppliedThisFrame + Tick() 매 프레임마다 1회로 제한
피격 데미지 처리 ApplySkillDamage() + TakeDamage() 서버에서만 체력 감소 처리
피격 이펙트 재생 Multicast_ApplySkillDamage() 클라이언트 시각적 처리 전용