개인프로젝트를 진행하며  Player 설계시 Weapon 관련 클래스들을 직접 소유,관리 하면 발생하는 문제점이 있었습니다.

  • Player가 Weapon 생성,장착,교체 등 로직까지 관리하는 과다 책임 및 SRP 위반
  • Weapon 클래스 수정시 PlayerCharacter를 직접 수정해야 할 수 있는 OCP 위반
  • 반동 적용과 같은 무기 관련 로직이 Player에서 처리되며 낮은 유지보수성 및 확장성 

이 외에도 다양한 문제점들이 있었지만, Player 클래스 내 Weapon만 추가했던 경우에도 코드가 복잡해지며 가독성이 낮아지는것을 확인하게되어 해당 클래스를 Actor Component를 사용한 리팩토링을 진행하게 되었습니다.

이번 포스팅에서는 Weapon 관련 프레임워크 설계에 대해서 작성하도록 하겠습니다.

 

Actor Component 적용 전

Player.cpp

void ABasePlayableCharacter::AttachWeapon()
{
	
	if (!IsValid(WeaponClass)) return;

	ABaseWeapon* CurrentWeapon = GetWorld()->SpawnActor<ABaseWeapon>(WeaponClass);
	if (CurrentWeapon)
	{
		FAttachmentTransformRules AttachmentRules(EAttachmentRule::KeepRelative, true);
		CurrentWeapon->AttachToComponent(GetMesh(), AttachmentRules, FName("WeaponSocket"));
		CurrentWeapon->SetActorRelativeLocation(CurrentWeapon->GetSocketOffset());
		CurrentWeapon->SetActorRelativeRotation(CurrentWeapon->GetSocketRotation());
	}
}

 

적용 전 Player.cpp 내 총기를 스폰 및 부착을 담당하는 관련 로직입니다.

이러한 무기 관련 로직들을 Character 클래스 내 처리해도 정상적으로 실행이 되는것을 확인 할 수 있었습니다.

그러나 프로젝트 설계에 따른 Sound, Effect, Notify등 다양한 기능들이 포함될 예정이며 이러한 로직들을 Player에게
중앙 집중 방식으로 처리할 경우 객체지향의 원칙 및 특징들을 위반하며, 코드 가독성 또한 저하되는것을 확인했습니다.

 

이러한 문제점을 발견하여 해당 클래스에 부착할 수 있는 Actor Component 클래스를 사용하여 관리하게 되었습니다.

  • Actor Component
    • AActor 클래스에 동적으로 Attach할 수 있는 재사용 가능한 컴포넌트
    • 독립적인 로직을 모듈화하여 여러 액터에서 공유 가능
    • 서버/클라이언트 동기화를 지원하는 RPC 사용 가능
    • 액터가 특정 기능에 의존하지않고, 컴포넌트가 기능을 담당함으로써 결합도를 낮춤
    • 필요한 기능만 컴포넌트로 추가하여 불필요한 로직 최소화 가능
    • Service Locator 패턴과 유사한 역할

 

Actor Component 적용 후

Player.cpp

void ABasePlayableCharacter::SpawnActorComponent()
{
	WeaponComponent = CreateDefaultSubobject<UWeaponComponent>(TEXT("Weapon"));
	if (!WeaponComponent)
	{
		UE_LOG(LogPlayer, Error, TEXT("WeaponComponent CDO is Null"));
	}
}

// UI 관련 구현 후 이벤트 기반의 총기 장착 구현
// 현재 단위테스트용 하드코딩 
void ABasePlayableCharacter::AttachWeapon()
{
	if (WeaponComponent)
	{
		WeaponComponent->EquipWeapon(EWeaponType::Rifle, WeaponClass);
	}
}

void ABasePlayableCharacter::SwitchCurrentWeapon(int32 WeaponType)
{
	UE_LOG(LogPlayer, Warning, TEXT("WeaponType Call : %d"), int32(WeaponType));

	if (WeaponComponent)
	{
		WeaponComponent->SwitchWeapon((EWeaponType)WeaponType);
	}
}

 

Weapon Component.cpp

void UWeaponComponent::EquipWeapon(EWeaponType Slot, TSubclassOf<class ABaseWeapon> WeaponClass)
{
	ACharacter* TargetOwner = Cast<ACharacter>(GetOwner());

	if (!TargetOwner || !WeaponClass)
	{
		UE_LOG(LogWeapon, Error, TEXT("WeaponComponent_EquipWeapon Func Owner || WeaponClass nullptr"));
		return;
	}

	if (EquipWeapons.Contains(Slot) && EquipWeapons[Slot])
	{
		EquipWeapons[Slot]->Destroy();
		EquipWeapons[Slot] = nullptr;
	}

	FActorSpawnParameters SpawnParams;
	SpawnParams.Owner = TargetOwner;
	SpawnParams.Instigator = TargetOwner->GetInstigator();
	ABaseWeapon* NewWeapon = GetWorld()->SpawnActor<ABaseWeapon>(WeaponClass, SpawnParams);

	if (NewWeapon)
	{
		NewWeapon->AttachToComponent(TargetOwner->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true),
			WeaponAttachSocketName);
		NewWeapon->SetActorRelativeLocation(NewWeapon->GetSocketOffset());
		NewWeapon->SetActorRelativeRotation(NewWeapon->GetSocketRotation());
		NewWeapon->SetActorEnableCollision(false);

		EquipWeapons.Add(Slot, NewWeapon);
		SwitchWeapon(Slot);
	}
	// SkeletalMesh 에 특정 소켓으로 부착
	// KeepRelative : 상대적인 위치를 유지하며 부착
	// true : 부착이 실패해도 실행을 계속
}

void UWeaponComponent::SwitchWeapon(EWeaponType Slot)
{
	ACharacter* TargetOwner = Cast<ACharacter>(GetOwner());

	if (!TargetOwner || !EquipWeapons.Contains(Slot))
	{
		UE_LOG(LogWeapon, Error, TEXT("WeaponComponent_SwitchWeapon Func Owner || Target Weapon Slot is nullptr"));
		return;
	}

	if (CurrentWeapon)
	{
		UnEquipWeapon();
	}

	CurrentWeapon = EquipWeapons[Slot];
	if (CurrentWeapon)
	{
		CurrentWeapon->SetActorHiddenInGame(false);
	}
}

 

  • Weapon 관련 로직 Component를 사용한 분리, 독립적인 모듈화 진행
  • 재사용성 증가 및 유지보수성 용이, SRP,OCP 원칙을 지향하는 설계
  • 멀티플레이 환경에서의 RPC를 독립적으로 관리 가능

 

이러한 이유로 Actor Component를 적용한 프레임 워크 설계를 진행하게 되었습니다.

 

+ Recent posts