개인프로젝트

[Unreal_C++_DarkSoul]#7_문제 해결, Magic Projectile

Hardii2 2022. 12. 17. 15:03

 

 

[Unreal_C++_DarkSoul]#7_Magic Projectile

 

Player의 "Wizard" 공격 모드에서 활용하는 마법 발사체 개발 내용입니다.

포트폴리오 진행 사항을 기록하기 위한 포스팅입니다.

 


 

Overview

 

  1. 문제점
  2. 문제 해결
  3. 문제 해결 후 결과 코드

 

문제점

1. AMagic::SpawnMagicBall()

void AMagic::SpawnMagicBall(const FName& InSocketName)
{

	// Transform
	AActor* OwnerActor = GetOwner();
	CheckNull(OwnerActor);

	APlayerCharacter* Player = Cast<APlayerCharacter>(OwnerActor);
	CheckNull(Player);

	FVector SpawnLocation = Player->GetMesh()->GetSocketLocation(InSocketName);
	FRotator SpawnRotation = Player->GetActorRotation();
	FTransform SpawnTransform = FTransform(SpawnRotation, SpawnLocation);

	// Spawn
	switch (MagicProjectileType)
	{
	case EMagicProjectileType::E_MagicBall:
		CheckNull(ProjectileSpawner);
		ProjectileSpawner->SpawnProjectile((int32)EMagicProjectileType::E_MagicBall, InSocketName);
		break;
	case EMagicProjectileType::E_SkullBall:
		CheckNull(MagicProjectileClass[(int32)EMagicProjectileType::E_SkullBall]);
		MagicProjectile = GetWorld()->SpawnActor<AProjectile_MagicSkullBall>(MagicProjectileClass[(int32)EMagicProjectileType::E_SkullBall], SpawnTransform);
		MagicProjectile->SetOwner(this);
		break;
	case EMagicProjectileType::E_Ray:
		CheckNull(MagicProjectileClass[(int32)EMagicProjectileType::E_Ray]);
		SpawnTransform = FTransform(SpawnRotation, SpawnLocation);
		MagicProjectile = GetWorld()->SpawnActor<AProjectile_MagicRay>(MagicProjectileClass[(int32)EMagicProjectileType::E_Ray], SpawnTransform);
		MagicProjectile->SetOwner(this);
		break;
	case EMagicProjectileType::E_Tornado:
		CheckNull(MagicProjectileClass[(int32)EMagicProjectileType::E_Tornado]);
		SpawnTransform = FTransform(SpawnRotation, SpawnLocation);
		MagicProjectile = GetWorld()->SpawnActor<AProjectile_MagicTornado>(MagicProjectileClass[(int32)EMagicProjectileType::E_Tornado], SpawnTransform);
		MagicProjectile->SetOwner(this);
		break;
	}

}

 

Problems

  • 코드 중복 : 기본 공격 포함 3개의 스킬이 모두 다른 Proejctile을 던지는 바람에 미친 코드 중복이 발생합니다.
  • 커플링 : 마법 발사체 클래스와 마법 클래스의 강한 커플링

Solutions

  1. 컴포넌트 패턴 : Player의 "마법" 공격 모드의 마법 발사체만을 관리하는 "컴포넌트" 구현
  2. 다형성 : 마법 발사체들 상위에 공통의 상위 클래스를 하나 선언하여, C++가 제공하는 다형성의 이점 활용
  3. 캡슐화 : 공통의 상위 클래스를 통해 하위 클래스들의 세부 기능들을 숨길 수 있습니다. 

 

문제 해결

1. Projectile_Magic.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"

#include "Projectile_Magic.generated.h"

/******************************************************************************************************************

	[Remark]	:	Magic Weapon < - > Projectile_Magic 간 충돌 과정에서 발생하는 상호작용
	
	시간 :
		22-12-01(목) [03:34]
	목적 :
		Magic Projectile의 충돌을 통해 Magic Weapon이 Damage를 전달
	설명 :
		1. FMagicProjectileBeginOverlap 커스텀 Delegate 선언
		2. Magic Weapon은 BindUFunction을 통해 바인딩을 수행
		3. Magic Projectile의 충돌 컴포넌트의 충돌 발생 시 IsBound() + Execute()를 통해 Magic Weapon의 데미지 전달을 구현

********************************************************************************************************************/

DECLARE_DELEGATE_OneParam(FMagicProjectileBeginOverlap, AActor*);

UCLASS()
class DARKSOUL_API AProjectile_Magic : public AActor
{
	GENERATED_BODY()
	
public:	
	AProjectile_Magic();

protected:
	virtual void BeginPlay() override;

//	******************************************************************************************
	//	Method
//	******************************************************************************************

protected:
	UFUNCTION()
		virtual void OnCollision();
	UFUNCTION()
		virtual void OffCollision();

protected:
	UFUNCTION()
		virtual void StartLaunched();
	UFUNCTION()
		virtual void EndLaunched(class AActor* OtherActor);

protected:
	UFUNCTION()
		virtual void OnCapsuleBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

public:
	FMagicProjectileBeginOverlap OnMagicProjectileBeginOverlap;


// *******************************************************************************************
	// Data Member
// *******************************************************************************************

protected:

	UPROPERTY(EditDefaultsOnly, Category = "Components")
		class USceneComponent* Scene;

	UPROPERTY(EditDefaultsOnly, Category = "Components")
		class UNiagaraComponent* ProjectileEffect;

	UPROPERTY(EditDefaultsOnly, Category = "Components")
		class UNiagaraComponent* MuzzleEffect;

	UPROPERTY(EditDefaultsOnly, Category = "Components")
		class UNiagaraComponent* HitEffect;

protected:

	UPROPERTY(VisibleAnywhere, Category = "Effects")
		FTimerHandle TimerHandler;
	UPROPERTY(VisibleAnywhere, Category = "Effects")
		float WaitTime = 0.15f;

};

 

Details

  1. "마법" 공격 모드에서 던질 마법 발사체의 가장 상위 클래스 선언
  2. Custom Delegate(FMagicProjectileBeginOverlap) 선언, "충돌" 정보를 "Magic" 클래스에게 전달하기 위함.
  3. 그밖에 하위 클래스들에게 공통적으로 필요한 데이터 멤버들

 

Projectile Spawner 컴포넌트

1. C_ProjectileSpawnerComponent.h

UPROPERTY(EditDefaultsOnly, Category = "Proejectile")
	TArray<TSubclassOf<class AProjectile_Magic>> ProjectileClass;

UPROPERTY(VisibleAnywhere, Category = "Projectile")
	class AProjectile_Magic* Projectile;

 

2. C_ProjectileSpawnerComponent::SpawnProjectile()

void UC_ProjectileSpawnerComponent::SpawnProjectile(int32 InNum, const FName& InName)
{

	AActor* OwnerWeapon = GetOwner();
	CheckNull(OwnerWeapon);

	APlayerCharacter* Player = Cast<APlayerCharacter>(OwnerWeapon->GetOwner());
	CheckNull(Player);

	// TODO	:	Spawn Location 설정
	FVector SpawnLocation = Player->GetMesh()->GetSocketLocation(InName);
	FRotator SpawnRotation = Player->GetActorRotation();
	FTransform SpawnTransform = FTransform(SpawnRotation, SpawnLocation);

	// TODO	:	Spawn Actor의 클래스 설정 여부
	CheckNull(ProjectileClass.IsValidIndex(InNum));
	Projectile = GetWorld()->SpawnActor<AProjectile_Magic>(ProjectileClass[InNum], SpawnTransform);
	Projectile->SetOwner(this->GetOwner());

}

 

Details

  1. 마법 발사체 클래스 목록 관리
  2. 정수 인자를 받아서 스폰할 마법 발사체 종류를 결정 및 마침내 스폰합니다.

 

결과 코드

1. Magic.h

// ...

private:
	UPROPERTY(EditDefaultsOnly, Category = "Components")
		class UC_ProjectileSpawnerComponent* ProjectileSpawner;

//...

 

Details

  • Projectile Spawner 컴포넌트를 데이터 멤버로 선언합니다.

 

2. Magic::SpawnMagicBall()

/*********************************************************************************************************
	
[Remark]: Switch 문을 통해 EMagicProjectileType 별 Spawn할 Magic_Projectile 결정하는 코드 수정

목적:
	Component 패턴 적용
설명:
	1. Projectile Spawner 컴포넌트 클래스 생성 및 구현
	2. 현재 Projectile Type만 인자로 넘겨, Spawn할 Magic Projectile을 Projectile Spawner 컴포넌트가 결정합니다.

*********************************************************************************************************/
void AMagic::SpawnMagicBall(const FName& InSocketName)
{
	
	CheckNull(ProjectileSpawner);
	ProjectileSpawner->SpawnProjectile((int32)MagicProjectileType, InSocketName);

}

 

Details

  • 단순히 몇 가지 데이터들을 인자로 넘겨주어, Projectile Spawner 컴포넌트에게 마법 발사체의 스폰을 맡깁니다.