개요

캐릭터의 손에 소켓을 장착하고, 그곳에 폭탄을 부착 / 분리하는 과정을 소개한다.

1) 소켓

소켓을 사용하는 이유

플레이어는 게임 시작과 함께 폭탄을 리로드한다. 이 때, 폭탄은 플레이어의 손에 부착되어 움직인다. 플레이어가 폭탄을 던지면 타이밍에 맞추어 손에서 분리, 발사된다.

폭탄은 UStaticMeshComponent를 가지는 액터이다. 별도의 트랜스폼을 가져야하기에 컴포넌트가 아닌 액터로 구성했다. 기본 정보 및 동작은 BombBase 클래스에 정의하였으며 이를 상속받은 폭탄 BP를 사용한다.

캐릭터 또한 액터의 한 종류이며, 액터에 액터를 부착하기 위해서 소켓을 사용했다.

캐릭터 소켓 추가

Untitled

Untitled

스켈레탈 메시의 오른손에 BombHolder 소켓을 장착했다. 그리고 폭탄을 제대로 들었는지 확인하기 위하여 프리뷰 에셋을 적용, 위치를 조정했다.

2) 부착

폭탄 액터 스폰

// CharacterBase.h
protected:
	UPROPERTY(EditAnywhere, Category = "Bomb", Meta = (AllowPrivateAccess = "true"))
	TSubclassOf<class AEPBombBase> BP_Bomb;

캐릭터 베이스에는 폭탄 액터에 접근하기 위한 BP_Bomb을 선언했다. 캐릭터 베이스를 상속받은 캐릭터 플레이어에서 특정 폭탄을 대입하고 사용할 것이다.

Untitled

// CharacterPlayer.cpp
BombInstance = GetWorld()->SpawnActor<AEPBombBase>(BP_Bomb, FVector::ZeroVector, FRotator::ZeroRotator);

입력받은 폭탄 BP의 인스턴스를 스폰하여 사용한다.

폭탄을 소켓에 붙이기

// CharacterPlayer.cpp
void AEPCharacterPlayer::OnReloadingBomb()
{
	BombInstance = GetWorld()->SpawnActor<AEPBombBase>(BP_Bomb, FVector::ZeroVector, FRotator::ZeroRotator);
	if (BombInstance)
	{
		...
		FName BombSocket(TEXT("BombHolder"));
		if (GetMesh()->DoesSocketExist(BombSocket))
		{
			BombInstance->AttachToComponent(
				GetMesh(),
				FAttachmentTransformRules::SnapToTargetIncludingScale,
				BombSocket
			);
		}
	}
}

폭탄을 던지고 나서 재장전하는 함수이다. 소켓이 있는지 먼저 검사한 다음, 부착을 진행한다. 소켓 이름을 알고있어야지 FName으로 전달 가능하다.

폭탄의 피직스 시뮬레이트

Untitled

Untitled

폭탄을 손에 붙이고 싶었을 뿐인데 붙지 않고 위의 이미지처럼 땅에서 스폰된다. 그건 폭탄에 피직스 시뮬레이트가 활성화 되어있기 때문이다. 피직스 시뮬레이트 문서를 보면 어딘가 붙어있을 경우에는 분리된다고 명시되어있다.

// BombBase.cpp
	BombMeshComponent->SetCollisionProfileName(TEXT("NoCollision"));
	BombMeshComponent->SetSimulatePhysics(false);

그렇기에 폭탄의 피직스 시뮬레이트를 비활성화 하고, 충돌 감지도 아예 비활성화 했다. 어차피 손에 들고 있을 때는 충돌의 의미가 없기 때문이다.

3) 분리

폭탄을 손에서 분리하기

// CharacterPlayer.cpp
void AEPCharacterPlayer::OnThrowingBomb()
{
	if (BombInstance)
	{
		...
		BombInstance->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
		BombInstance = nullptr;
		...
	}
}

폭탄을 던지는 시점에 호출되는 함수이다. DetachFromActor()함수로 쉽게 분리할 수 있다. 분리된 오브젝트의 위치 옵션으로는 KeepRelativeTransform, KeepWorldTransform이 있다. 손에서 분리된 그 지점이 필요했기에 KeepWorldTransform을 사용했다.

폭탄의 피직스 시뮬레이트 복구하기

Untitled

피직스 시뮬레이트를 복구하지 않고 캐릭터와 폭탄을 분리하면 위와 같이 해당 좌표에 고정된다. 그러니 복구가 피룡하다.

// BombBase.cpp
void AEPBombBase::OnThrowingBomb()
{
	if (BombMeshComponent) 
	{
		BombMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		BombMeshComponent->SetSimulatePhysics(true);
	}

}

폭탄을 던지는 시점에 호출되는 함수이다. 피직스는 물론이고 콜리전 또한 복구해야한다. NoCollision 상태에서는 고정이 풀리지 않는다.

폭탄을 던지는 방법

언리얼 엔진에서는 발사체에 대한 여러 기능들을 쉽게 사용할 수 있도록 Projectile Movement를 제공한다. 하지만 이번 프로젝트와는 적합하지 않다 판단하였기에 사용하지 않았다.

폭탄을 던지는 기능은 캐릭터가 OnThrowingBomb() 함수에서 처리한다. 폭탄은 그저 폭발에 필요한 정보와 기능만을 가진다.

// CharacterPlayer.cpp
void AEPCharacterPlayer::OnThrowingBomb()
{
	if (BombInstance)
	{
		// 바라보는 방향
		FRotator Rotation = GetControlRotation();
		FVector Direction = Rotation.Vector();

		// 던지기(임시)
		BombInstance->GetBombMeshComponent()->AddImpulse(Direction * ThrowingDistanceMultiplier * ThrowingDistance);
		...
		BombInstance->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
		BombInstance = nullptr;
	}
}

플레이어가 바라보고 있는 방향으로 단발성 힘을 가하는 방식이다. AddImpulse는 PrivitiveComponent에 힘을 가하기 때문에 폭탄의 메시에 접근할 필요가 있다. ThrowingDistanceMultiplier는 조준 시간에 비례한 거리 가중치이다.

+) 이후에 변경한 사항

개요

위의 던지기 구조는 멀티플레이 환경에 적합하지 않았다. 리플리케이션의 주체와 물리 설정 등이 얽혀있었으며, 던지는 동작을 수행할 때 마다 관련 설정을 변경해야 했다. 이러한 문제점을 해결하고자 던지기 구조 개편을 진행한다.

기존 방식

  • 던지기-리로드로 구성된 동작이다.
  • 리로드 과정에서는 폭탄을 스폰하고 캐릭터 손 부분의 소켓에 부착한다. 이는 추후 던지기 과정에서 분리된다.
  • 부착을 위해서는 물리 시뮬레이션 및 콜리전 옵션을 해제해야 한다. 추후 던지기 과정에서 재설정된다.
  • 던지기-리로드 과정은 연속된 하나의 폭탄을 사용한다. 그렇기에 위와 같은 설정 변경이 필요하다. 이로 인해 얻는 장점은 중간에 폭탄을 교체하지 않기에 끊김 없이 자연스럽다는 것이다.
  • 던지기 과정에서는 분리된 폭탄에 힘을 가해 멀리 날려보낸다.

변경 방식

  • 동일하게 던지기-리로드로 구성된 동작이다. 하지만 2개의 폭탄을 사용한다.
  • 게임의 시작과 함께 폭탄(BombInHand)를 스폰하여 물리 옵션 설정 이후 소켓에 부착한다. 던지는 타이밍에 이를 숨기고, 리로드 타이밍에 복원한다.
  • 던지는 동작에서는 타이밍에 맞추어 새로운 폭탄(BombToThrow)을 스폰한다. 부착하지 않기에 물리 옵션을 변경할 필요가 없다. 힘을 가해 멀리 날려 보내는 것은 동일하다.
  • 스폰과 던지기를 캐릭터에서 담당하는 것은 동일하지만, 확실하게 분리된 액터이다.

참고 자료