게임 개발 (언리얼 엔진)

UE4 생존게임 제작 - 9 (플레이어 원거리 공격 및 공격 모션 상하체 블렌딩 제작)

언린이 2020. 11. 7. 19:45

1. 발사체 액터 제작

 

먼저 발사체의 액터로 지정할 클래스를 만들었습니다.

 

 

protected:
	// 스태틱 메시
	UPROPERTY(Category = Mesh, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UStaticMeshComponent* Mesh;

	// 충돌을 감지할 박스 컴포넌트
	UPROPERTY(Category = Collision, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UBoxComponent* ArrowBox;

	// 파티클
	UPROPERTY(Category = Particle, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UParticleSystemComponent* Particle;

	// 프로젝타일 컴포넌트
	UPROPERTY(Category = Projectile, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UProjectileMovementComponent* Movement;

 

클래스 안에 스태틱 메시 컴포넌트, 박스 컴포넌트, 파티클 컴포넌트, 프로젝타일 무브먼트 컴포넌트를 선언하였습니다.

이렇게 선언한 다음 이 클래스를 상속받는 블루프린트 클래스를 만들어서 메시와 파티클을 적용할 것입니다.

 

 

AProjectileArrow::AProjectileArrow()
{
	PrimaryActorTick.bCanEverTick = true;
	
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Particle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Particle"));
	Movement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Movement"));
	ArrowBox = CreateDefaultSubobject<UBoxComponent>(TEXT("ArrowBox"));
	
	SetRootComponent(Mesh);

	// 몬스터 객체와 충돌하는 프로파일 적용
	ArrowBox->SetCollisionProfileName(TEXT("PlayerAttack"));
	ArrowBox->SetBoxExtent(FVector(20.f, 20.f, 20.f));

	ArrowBox->SetupAttachment(Mesh);
	Particle->SetupAttachment(Mesh);

	Mesh->SetRelativeRotation(FRotator(-90.f, 0.f, 0.f));
}

 

생성자에서 선언하였던 컴포넌트들을 생성하고 ArrowBox의 프로파일은 플레이어의 근거리 공격에 사용했던 프로파일과 동일하게 적용하였습니다.

그리고 스태틱 메시가 위쪽을 향하고 있어서 SetRelativeRotation 함수를 이용해 돌려주었습니다.

 

 

 

블루프린트 클래스를 만들어 스태틱 메시와 파티클을 적용하였습니다.

그리고 프로젝타일 무브먼트 컴포넌트의 초기속력과 최대속력을 설정해주었습니다.

 

 

 

적용된 모습입니다. 화살 애셋이 없어서 스태틱 메시로는 작은 단검을 적용하였습니다.

그리고 현재 스태틱 메시를 회전을 시켰기 때문에 메시의 포워드 방향이 아래인 것을 확인할 수 있습니다.

현재 상태에서 포워드 방향으로 발사시키면 아래로 떨어지게 될 것입니다. 그래서 조금 이따 블루프린트를 이용해 발사할 방향을 재설정해 줄 것입니다.

 

 

public:
	// 충돌이 시작할시에 호출되는 델리게이트에 등록하는 함수
	UFUNCTION()
	void ArrowBeginOverlap(UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
		bool bFromSweep, const FHitResult& SweepResult);

	// 충돌이 종료될시에 호출되는 델리게이트에 등록하는 함수
	UFUNCTION()
	void ArrowEndOverlap(UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

 

void AProjectileArrow::BeginPlay()
{
	Super::BeginPlay();

	// 델리게이트에 생성한 함수 등록
	ArrowBox->OnComponentBeginOverlap.AddDynamic(this, &AProjectileArrow::ArrowBeginOverlap);
	ArrowBox->OnComponentEndOverlap.AddDynamic(this, &AProjectileArrow::ArrowEndOverlap);
}

 

충돌시에 작동시킬 함수를 선언한 뒤, ArrowBox와 Overlap이 발생할 때 호출될 델리게이트에 함수를 등록하였습니다.

 

 

// Overlap이 시작할 때 호출되는 함수
void AProjectileArrow::ArrowBeginOverlap(UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
	bool bFromSweep, const FHitResult& SweepResult)
{
	FDamageEvent DamageEvent;

	// 여기서 OtherActor는 ProjectileArrow와 충돌한 객체, 즉 몬스터 객체이다
	float fDamage = OtherActor->TakeDamage(30.f, DamageEvent, GetWorld()->GetFirstPlayerController(), this);

	this->Destroy();
}

// Overlap이 끝날 때 호출되는 함수
void AProjectileArrow::ArrowEndOverlap(UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{

}

 

델리게이트에 등록한 함수에서는 발사체 액터와 Overlap이 된 액터, 즉 몬스터 객체에게 데미지를 주기 위해서 TakeDamage 함수를 호출하였습니다. 그리고 나서 발사체 액터가 계속 월드에 남아있지 않게 Destroy 함수를 호출하여 제거하였습니다.

 

 

 

이제 블루프린트를 이용해 발사체를 발사할 방향을 재설정하도록 하겠습니다.

포워드 백터가 아래쪽이었기 때문에 업 백터를 사용하여 프로젝타일 무브먼트 컴포넌트의 Velocity를 재설정하였습니다.

 

 

 

그리고 현재 코드 상에선 몬스터 객체와 충돌할 때만 Destroy를 해주고 있어서 땅이나 벽에 부딪힌다면 액터가 사라지지 않고 그 자리에 박혀있을 것입니다.

그래서 블루프린트를 이용해 프로젝타일 무브먼트 컴포넌트가 멈췄을 때, 액터를 제거하도록 하였습니다.

 

 

2. 발사체 액터 생성 코드 제작

 

	UPROPERTY(Category = ArrowAttack, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	TSubclassOf<AProjectileArrow> ProjectileArrowClass;

 

	// 블루프린트 클래스를 불러온다
	static ConstructorHelpers::FClassFinder<AProjectileArrow> ProjectileAsset(TEXT("Blueprint'/Game/Player/BPProjectileArrow.BPProjectileArrow_C'"));

	if (ProjectileAsset.Succeeded())
		ProjectileArrowClass = ProjectileAsset.Class;

 

플레이어 클래스가 발사체 액터 클래스를 가지고 있도록 선언한 뒤,

플레이어의 생성자에서 발사체 액터의 블루프린트 클래스를 불러왔습니다.

 

 

// 발사체 액터 생성 함수
void APlayerSurvivor::ArrowAttack()
{
	// 화살이 발사될 위치를 설정한다
	FVector vPos = GetActorLocation() + GetActorUpVector() * 30.f + GetActorForwardVector() * 100.f;

	// 스폰 파라미터를 설정한다
	FActorSpawnParameters params;
	params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

	// 월드에 액터를 배치한다
	AProjectileArrow* ProjectileArrow = GetWorld()->SpawnActor<AProjectileArrow>(ProjectileArrowClass, vPos, GetActorRotation(), params);
}

 

그리고 발사체 액터를 생성하는 함수를 정의하였습니다.

화살이 발사될 위치와 스폰 파라미터를 설정하고 SpawnActor 함수를 사용하여 발사체 액터를 월드에 생성하였습니다.

 

 

void USurvivorAnim::AnimNotify_ArrowProjectile()
{
	APlayerSurvivor* pOwner = Cast<APlayerSurvivor>(TryGetPawnOwner());

	if (IsValid(pOwner))
		pOwner->ArrowAttack();
}

 

이제 활을 쏘는 모션에 노티파이을 생성하고 해당 노티파이에서 플레이어의 ArrowAttack 함수를 호출하도록 하였습니다.

 

 

 

화살이 발사되고 화살을 맞은 몬스터가 죽는 모습을 확인할 수 있습니다.

 

 

3. 애니메이션 상하체 블렌딩 제작

 

원거리 공격이 완성되었습니다. 하지만 플레이어가 이동을 하면서 공격 모션을 실행하면 애니메이션이 굉장히 이상합니다. 왜냐하면 공격 모션을 실행하면 발이 움직이지 않기 때문입니다.

그래서 하체는 뛰는 애니메이션으로, 상체는 공격하는 애니메이션으로 실행해줘야 합니다.

 

 

 

먼저 캐시에 지금까지 만들어 놓은 애니메이션들을 저장해주었습니다.

 

 

 

그 다음 본마다 레이어 블렌딩을 해주는 노드를 생성하였습니다.

해당 노드는 Base Pose에 Blend Poses를 덮어 씌우게 해주는 노드입니다.

 

 

 

상하체 블렌딩을 할 것이기 때문에 Bone Name을 스켈레탈 메시 Spine의 이름으로 지정해주었습니다.

 

 

 

마지막으로 Base Pose에는 플레이어의 기본 움직임을 표현하는 애니메이션을,

Blend Poses에는 캐시에 저장해두었던 공격 모션 애니메이션을 연결해주었습니다.

 

 

 

하체는 뛰는 모션으로, 상체는 공격 모션으로 애니메이션이 적용되는 모습을 확인할 수 있습니다.

 

 

다음에 해야할 일

 

보스 몬스터 제작