티스토리 뷰

반응형

스플라인은 이런식의 곡선을 만들어 스플라인메쉬컴포넌트를 이용하여 자연스러운 곡선을 표현할수있다.

보통의 블루프린트는 이런식의 기능이 있는데 c++에는 절대 없다. 그래서 일단 Projectile을 이용해서 투척궤도를 그리는 스플라인을 그려보도록 하겠습니다.


AThirdPersonCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
//스플라인메쉬 헤더//
#include "Components/SplineMeshComponent.h"
#include "ThirdPersonCharacter.generated.h"


class PROJECT_API AThirdPersonCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AThirdPersonCharacter();

	

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;


public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

public:
	//SplineMesh에 붙이고 끝부분에 붙일 데칼을 생성해줌//
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline")
	UStaticMesh* DefalutMesh;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline")
	class UMaterialInterface* DefaultMaterial;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline")
	class UDecalComponent* CircleDecal;
    ////////////////////////////////////////////////////

	//스플라인 컴포넌트//
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Player", meta = (AllowPrivateAccess = "true"))
	class USplineComponent* Spline_Path;
	//스플라인메쉬 컴포넌트//
    	TArray<USplineMeshComponent*> Spline_Meshs;
        
public:
	//틱함수에 호출하여 스플라인컴포넌트의 위치를 계속 추적함//
	void UpdateSplinePath();

먼저 스플라인사용하기 위해서는 각 위치의 포인트를 찍을 SplineComponent와 Spline에 입힐 MeshComponent를 선언해준다. (MeshComponent는 각 생성된 메쉬를 지우고 만들고 하기 위해서이다.)

 

AThirdPersonCharacter.cpp

#include "Components/SplineComponent.h"
#include "Components/SplineMeshComponent.h"
#include "Kismet/GameplayStatics.h

AThirdPersonCharacter::AThirdPersonCharacter()
{
	//스플라인을 사용하기위해 생성해줌//
	Spline_Path = CreateDefaultSubobject<USplineComponent>(TEXT("Spline_Path"));
	Spline_Path->SetupAttachment(ThrowLocation);
    ///////////////////////////////////
}

void AThirdPersonCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
    
    UpdateSpline();
}

void AThirdPersonCharacter::UpdateSplinePath()
{
	/**********************************************************************************************************
	* 전에 있었던 스플라인 위치와 스플라인 메쉬를 없애 할당을 하나로 해줌
	***********************************************************************************************************/
	//전에 저장되있는데 스프라인 포인트를 다 지움//
    Spline_Path->ClearSplinePoints(true);
	if (Spline_Meshs.Num() > 0)
	{
		for (int32 i = 0; i < Spline_Meshs.Num(); i++)
		{
			if (Spline_Meshs[i])
			{
            	//저장된 배열의 메시컴포넌트를 삭제하여 계속 지속시킴//
				//Spline_Meshs[i]->DetachFromParent();
				Spline_Meshs[i]->DestroyComponent();
			}
		}
        //배열을 삭제함//
		Spline_Meshs.Empty();
	}
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
	//필요값들//
    FHitResult OutHIt;
	TArray<FVector> OutPathPositions;
	FVector LastPosition;
	///////////
    
    //라인트레이스 시작점//
	FVector StartPos = ThrowLocation->GetComponentLocation();
	//라인트레이스 조준점//
    FVector LaunchVelocity = UKismetMathLibrary::GetForwardVector(ThrowLocation->GetRelativeRotation()) * 1000.f;
	
    //라인 트레이스를 쏴 그 길의 각 포인트지점을 따라 Spline에 저장해줌//
	bool isHit = UGameplayStatics::Blueprint_PredictProjectilePath_ByTraceChannel(GetWorld(), OutHIt, OutPathPositions, LastPosition, StartPos, LaunchVelocity, true, 10.f, ECollisionChannel::ECC_WorldStatic, false, IgnoreActors, EDrawDebugTrace::None, 15.f);
	for (int i = 0; i < OutPathPositions.Num(); i++)
	{
    	//포인트지점을 따라 저장해줌//
		Spline_Path->AddSplinePointAtIndex(OutPathPositions[i], i, ESplineCoordinateSpace::World);
	}
    
    //이거는 제가 만든거라 원하는 State를 이용하여 보이게하거나 안보이게 할것이다.//
	if (bZoomIn && GrabGrenade)
	{

		//////////////////////////////////////////////////////////////////////////////////////////////////////////////
		for (int SplineCount = 0; SplineCount < (Spline_Path->GetNumberOfSplinePoints()) - 1; SplineCount++)
		{
			USplineMeshComponent* SplineMeshComponent = NewObject<USplineMeshComponent>(this, USplineMeshComponent::StaticClass());
			//만약 실린터메쉬를 사용할떄 메쉬를 X이 정면이라 가정하면 Z축으로 뒤집어서 정면으로 바꿈(만약 박스만 이용할경우 사용 안해도됨.)// 
            SplineMeshComponent->SetForwardAxis(ESplineMeshAxis::Z);
			SplineMeshComponent->SetStaticMesh(DefalutMesh);
			//정적 움직임//
			SplineMeshComponent->SetMobility(EComponentMobility::Movable);
			SplineMeshComponent->CreationMethod = EComponentCreationMethod::UserConstructionScript;
			//월드에 등록해줌//
			SplineMeshComponent->RegisterComponentWithWorld(GetWorld());
			//스플라인 컴포넌트에 컴포넌트에 따라 크기 위치를 변경함//
			SplineMeshComponent->AttachToComponent(Spline_Path, FAttachmentTransformRules::KeepRelativeTransform);
			SplineMeshComponent->SetStartScale(FVector2D(UKismetSystemLibrary::MakeLiteralFloat(0.1f), UKismetSystemLibrary::MakeLiteralFloat(0.1f)));
			SplineMeshComponent->SetEndScale(FVector2D(UKismetSystemLibrary::MakeLiteralFloat(0.1f), UKismetSystemLibrary::MakeLiteralFloat(0.1f)));

			//시작지점//
			const FVector StartPoint = Spline_Path->GetLocationAtSplinePoint(SplineCount, ESplineCoordinateSpace::Local);
			const FVector StartTangent = Spline_Path->GetTangentAtSplinePoint(SplineCount, ESplineCoordinateSpace::Local);
			const FVector EndPoint = Spline_Path->GetLocationAtSplinePoint(SplineCount + 1, ESplineCoordinateSpace::Local);
			const FVector EndTangent = Spline_Path->GetTangentAtSplinePoint(SplineCount + 1, ESplineCoordinateSpace::Local);
			SplineMeshComponent->SetStartAndEnd(StartPoint, StartTangent, EndPoint, EndTangent, true);
			
            //메쉬에 충돌할것인지 아닌지확인함(일단은 안함)//
			SplineMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
			
            //메쉬는 꼭아래에다 해줘야한다.//
			if (DefaultMaterial)
			{
				SplineMeshComponent->SetMaterial(0, DefaultMaterial);
			}
            ////////////////////////////////
            
            //메쉬의 위치를 저장해줌//
			Spline_Meshs.Add(SplineMeshComponent);
		}
		CircleDecal->SetVisibility(true);
		CircleDecal->SetWorldLocation(LastPosition);
	}
	else
	{
    	//위에있는 State가 false일경우에는 계속 안보이게 설정해줌//
		Spline_Path->ClearSplinePoints(true);
		if (Spline_Meshs.Num() > 0)
		{
			for (int32 i = 0; i < Spline_Meshs.Num(); i++)
			{
				if (Spline_Meshs[i])
				{
					//Spline_Meshs[i]->DetachFromParent();
					Spline_Meshs[i]->DestroyComponent();
				}
			}
			Spline_Meshs.Empty();
		}
		CircleDecal->SetVisibility(false);
	}
}
////////////////////

먼저 스플라인 그리기전에 먼저 있는지를 확인해 초기화를 시켜줌

먼저 블루프린트 같은경우에는 메쉬컴포넌트를 추가할수있는 블루프린트전용이 있다. 하지만 c++에는 없다는게 단점이다. 다른곳을 찾아보면 많이는 없고 어는정도 일반actor에서 하는것을 가져온것이기에 완벽하진 않수도있다.

이제 스플라인을 그리기위해서는 메쉬컴포넌트를 선언해줘야한다. 

/*시작지점주석같은경우에는 스플라인의 각 포인트에 메쉬가 있지만 각스플라인의

위에와 같이 각지점의 포인트가 있으면 그포인트들의 순번이 왔다고 따지만 맨왼쪽이 이제Start로 설정하고 Start, Start tangent값을 가져오는것이다. 그다음 포인트는 Start+1이라는 순번으로 따져 Start+1과 Start+1 tangent를 가져와 메쉬를 늘려준다. 그러면 하나의 스플라인메쉬가 생성되는것이다. 그다음 순번이면 Start+1였던 포인트가 다시 Start를 가져와 tangent값을 가져와 계속 자연스럽게 이어주는것이다. 

상세하게 표현하면 이런느낌이다.

이렇게 메쉬를 그리고 다시 지우고 그리고 다시 지우고 해서 그려주는것이다. (만약 맨처음에 초기화를 안시켜주면 계속 컴포넌트가 쌓여 프로그램이 멈춘다.) (추가 : 머티리얼같은경우에는 마지막에 설정해줘야 제대로 작용한다. //먼저 적용할경우 계속 머티리얼의 X축으로 같다 Z으로 와리가리하기 떄문에 깔끔하게 마지막에 설정해준다.)

이거는 만약 플레이어 스테이트가 false일경우 그냥 삭제시켜 안보이게 해준다. (이거는 맨처음 시작하고 삭제하고 남아있는게 없기에 연산량은 잡아먹지는 않는다.)

 

마지막으로 스테틱메쉬와 머티리얼, 데칼을 설정할경우에는 블루프린트를 사용할경우에는 블루프린트에서 파일을 넣어도 되지만 저는 그냥 c++타이핑으로 다 넣어서 그거는 생략하겠습니다.

 

이렇게까지 완성하면 다음과같은 모양이 나옵니다.

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/10   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함