unreal 5기

251218 언리얼엔진 본캠프 91일차 멀티플레이 팀프로젝트6

parkjinnam 2025. 12. 18. 19:56

렉돌을 넣어보자

렉돌이란 여타 다른 게임에서 캐릭터가 사망 시 힘이 풀린 것처럼 쓰러지는 것을 표현하기 위해서 사용하는 기능으로 파티애니멀즈에서도 흐느적흐느적거리는 움직임을 표현하기 위해 활용되고 있다. 현재 캐릭터를 공격했을 때 스턴치가 쌓이고 일정 수치만큼 스턴치가 쌓이게 되면 기절되는 것까지는 구현을 해둔 상태이다. 이제 KnokOut함수를 구현해서 실제로 기절한 캐릭터가 렉돌 되어 철퍼덕 쓰러지는 효과를 얻고자 한다.

 

렉돌을 구현하기 위한 함수와 변수 추가

 

지난 글에서 TakeDamage함수에 스턴치가 일정 구간을 넘어서면 KnockOut 함수를 호출하게끔 해두었다. 이 KnockOut 함수를 다음과 같이 작성해 주자

void ATTPlayerCharacter::KnockOut ()
{
	if (bIsStunned) return;
	UE_LOG ( LogTemp , Warning , TEXT ( "%s is KNOCKED OUT!" ) , *GetName () );

	bIsStunned = true;
	CurrentStun = 0.0f;

	OnRep_IsStunned ();

	FTimerHandle StunTimerHandle;
	GetWorld ()->GetTimerManager ().SetTimer (
		StunTimerHandle ,
		this ,
		&ATTPlayerCharacter::WakeUp ,
		StunDuration ,
		false
	);
}

 

당연히 캐릭터가 기절한 상태에서는 기절하면 안 되기에 바로 return 하여 함수를 종료하고 그렇지 않은 경우에만 작동하게 끔 해준다. 일단 캐릭터의 상태를 기절 상태로 바꾸기 위해 bIsStunned를 true로 변경하여 기절상태임을 표시하고 CurrentStun을 0으로 초기화해준다. 그다음 OnRep_IsStunned(실질적인 렉돌 키는 함수)를 호출해 주고 기절 타이머를 StunDuration만큼 돌려준 다음 WakeUp함수를 호출하게끔 해준다.

 

void ATTPlayerCharacter::OnRep_IsStunned ()
{
	if (bIsStunned)
	{
		GetCapsuleComponent ()->SetCollisionEnabled ( ECollisionEnabled::NoCollision );

		GetMesh ()->SetCollisionProfileName ( TEXT ( "Ragdoll" ) );
		GetMesh ()->SetSimulatePhysics ( true );
	}
	else
	{
		GetMesh ()->SetSimulatePhysics ( false );
		GetMesh ()->SetCollisionProfileName ( TEXT ( "CharacterMesh" ) );
		GetCapsuleComponent ()->SetCollisionEnabled ( ECollisionEnabled::QueryAndPhysics );

		GetMesh ()->AttachToComponent ( GetCapsuleComponent () , FAttachmentTransformRules::SnapToTargetNotIncludingScale );
		GetMesh ()->SetRelativeLocation ( FVector ( 0.0f , 0.0f , -60.0f ) );
		GetMesh ()->SetRelativeRotation ( FRotator ( 0.0f , 0.0f , -90.0f ) );
	}
}

 

OnRep_IsStunned()가 호출되었을 때 bIsStunned가 false냐 true냐에 따라 다음과 같이 작성한다 true라면 기절한 것이므로 캡슐 컴포넌트의 콜리전을 오프 해주고 매쉬의 콜리전 프로필을 Ragdoll로 바꿔준 다음 SimulatePhysics를 true로 바꿔 매쉬에 물리가 적용되도록 해준다. bIsStunned가 false라면 위의 과정을 원복 시켜 캐릭터 매시가 다시 정상적으로 돌아오도록 하고 매시가 렉돌상황에서 흘러내리면서 위치가 바뀌었을 테니 다시 캡슐 컴포넌트에 어태치 시켜준다음 매쉬의 위치를 기존과 동일하게 하기 위해 로케이션과 로테이션을 조정해 준다.

 

그러고 나서 기절된 캐릭터가 움직이면 안 되기 때문에 Move와 Attack 함수에 bIsStunned가 true라면 바로 함수를 종료하도록 코드를 입력해 주면 클라이언트에서의 처리는 끝나게 된다.

 

하지만 우리가 만드는 건 멀티플레이 게임 이 과정이 서버를 통해서 처리가 되도록 해야 할 것이다. 그렇기 때문에 다음처럼 bIsStunned의 UPROPERTY 매크로를 수정해 준다.

해당 변수의 값이 바뀌면 자동으로 함수를 호출하도록 수정해준다.

 

이렇게 작성해 주면 클라와 서버 모두 bIsStunned가 변경되게 되면 OnRep_IsStunned가 호출되게 된다.

 

발견된 문제들

하지만 코딩이 뭐 생각처럼 쉬운가? 여러 문제가 발견되었다. 일단 첫 번째로는 우리는 캐릭터 커스터마이징을 위해서 한 캐릭터에 2개의 매쉬를 사용하고 있다. 하나는 머리이며 하나는 몸통이다. 머리는 다음처럼 적용하여 몸통의 Pose를 따라가게끔 세팅되어 있는 상황이다.

 

여기서 이제 발생하는 문제가 스턴이 적용되어 렉돌이 작동하게 되면 캐릭터의 몸통만 렉돌이 되고 머리는 공중에 떠있는 상황이 나온다. 두 번째로는 기절한 동안 Jump가 적용되어 점프를 하게 되면 점프가 된다. 세 번째로는 사진과 같이 클라이언트끼리의 화면이 다르게 적용되고 있다.

1번 클라와 2번 클라의 화면이 다르다(머리가 없다!)

 

문제를 이제 한번 하나씩 해결해 보도록 하자.

 

 

문제 1 : 머리랑 몸이 분리됐어요!

일단 첫 번째 의심 상황으로는 Head가 캡슐 컴포넌트에 Attach 되어 있다는 점이다. 현재 Head매시는 다음과 같이 Root 컴포넌트인 캡슐에 어태치 되어 있는데 이를 몸통에 해당하는 Mesh에 부착해 보도록 하자.

GetRootComponent()를 GetMesh()로 변경해보자

 

부착 대상이 변경되었기 때문에 Head의 Location과 Rotation을 수정해 주고 에디터를 빌드해 보면

정상적으로 머리도 작동한다!

 

 

문제 2 : 기절인데 이 녀석 점프를 해요!

우리는 앞서 기절한 상태에서 움직이지 못하도록 Move와 Attack 함수에 방지 코드를 넣었는데 Jump는 누락되었다. 구현하는 과정에서 Jump는 언리얼엔진에서 기본적으로 제공되는 점프 함수를 사용하기에 별도로 Character에 Jump를 불러줄 함수를 만들고 방지 코드를 넣어주는 방향으로 해결해 보도록 하자.

다음과 같이 작성해준다.

 

새로 점프 함수를 만들었으니 기존에 SetupPlayerInputComponent로 바인딩한 함수를 수정해줘야 한다.

 

 

문제 3 : 기절했던 녀석이 사라졌어요!

일단 캐릭터가 완전히 사라지는 건 아니다. 기절상태에서 해제되면 캐릭터가 다른 클라이언트에서 사라지게 되는데 인풋을 입력하면 또 보이게 된다. 이 원인이 뭔지 생각해야 하는데 시간 관계상 이는 다음 글로 넘기도록 하겠다.