11.14 - TIL

김혁·2025년 11월 14일

TIL

목록 보기
57/84

오늘의 코드카타

오늘의 공부

팀 프로젝트 진행 내용

1. 파쿠르 동작을 할 수 있는 오브젝트에 부착할 컴포넌트 구현

  • 파쿠르 시도했을 시에 파쿠르를 진행할 Ledge 정보를 반환하는 함수를 구현
void UAO_TraversableComponent::GetLedgeTransforms(FVector& HitLocation, FVector& ActorLocation,
	FTraversalCheckResult& TraversalResult)
{
	// Find the ledge closest to the actor
	USplineComponent* ClosestLedge = FindLedgeClosestToActor(ActorLocation);
	if (!ClosestLedge)
	{
		TraversalResult.bHasFrontLedge = false;
		return;
	}

	// Check if the ledge is wide enough
	if (ClosestLedge->GetSplineLength() < MinLedgeWidth)
	{
		TraversalResult.bHasFrontLedge = false;
		return;
	}

	// Get the closest point on the ledge to the actor
	// Local Space로 계산하는 이유: 스플라인의 거리 계산은 Local Space로 해야 되기 때문에 변환하는 과정이 필요함
	FVector ClosestLocation = ClosestLedge->FindLocationClosestToWorldLocation(HitLocation, ESplineCoordinateSpace::Local);
	float ClosestPoint = ClosestLedge->GetDistanceAlongSplineAtLocation(ClosestLocation, ESplineCoordinateSpace::Local);
	float ClampedPoint = FMath::Clamp(ClosestPoint, MinLedgeWidth / 2.0f, ClosestLedge->GetSplineLength() - MinLedgeWidth / 2.0f);
	FTransform ClosestTransform = ClosestLedge->GetTransformAtDistanceAlongSpline(ClampedPoint, ESplineCoordinateSpace::World);

	TraversalResult.bHasFrontLedge = true;
	TraversalResult.FrontLedgeLocation = ClosestTransform.GetLocation();
	TraversalResult.FrontLedgeNormal = ClosestTransform.GetRotation().GetUpVector();

	// Find the opposite ledge of the closest ledge using map
	USplineComponent* OppositeLedge = *OppositeLedges.Find(ClosestLedge);
	if (OppositeLedge)
	{
		TraversalResult.bHasBackLedge = false;
		return;
	}

	// Get the closest point on the back ledge from the front ledge
	FTransform OppositeClosestTransform = OppositeLedge->FindTransformClosestToWorldLocation(TraversalResult.FrontLedgeLocation, ESplineCoordinateSpace::World);

	TraversalResult.bHasBackLedge = true;
	TraversalResult.BackLedgeLocation = OppositeClosestTransform.GetLocation();
	TraversalResult.BackLedgeNormal = OppositeClosestTransform.GetRotation().GetUpVector();
}

USplineComponent* UAO_TraversableComponent::FindLedgeClosestToActor(FVector& ActorLocation)
{
	if (Ledges.Num() == 0)
	{
		return nullptr;
	}	

	float ClosestDistance = 0.f;
	int32 ClosestIndex = 0;

	for (int32 i = 0; i < Ledges.Num(); ++i)
	{
		USplineComponent* Ledge = Ledges[i];

		// UpVector * 10을 해서 비교하는가? Ledge 위쪽의 실제 접근 위치를 기준으로 비교하기 위해서
		FVector ClosestLocation = Ledge->FindLocationClosestToWorldLocation(ActorLocation, ESplineCoordinateSpace::World);
		FVector ClosestUpVector = Ledge->FindUpVectorClosestToWorldLocation(ActorLocation, ESplineCoordinateSpace::World);
		ClosestUpVector *= 10.f;
		float Distance = FVector::Dist(ClosestLocation + ClosestUpVector, ActorLocation);

		if (i == 0 || Distance < ClosestDistance)
		{
			ClosestDistance = Distance;
			ClosestIndex = i;
		}
	}

	return Ledges[ClosestIndex];
}

  • 액터에 붙어있는 Spline Component를 검사해서 컴포넌트의 TArray<USplineComponent*> LedgesTMap<USplineComponent*, USplineComponent*> OppositeLedges에 할당하는 과정 추가
  • 이는 OnRegister() 함수에서 실행됨
  • 이를 통해 Spline Component 개수가 고정되지 않아서 확장성이 더 좋을 것으로 보임
void UAO_TraversableComponent::ScanSplines()
{
	Ledges.Empty();
	OppositeLedges.Empty();

	AActor* Owner = GetOwner();
	if (!Owner)
	{
		return;
	}

	TArray<USplineComponent*> SplineComponents;
	Owner->GetComponents(USplineComponent::StaticClass(), SplineComponents);

	for (USplineComponent* Spline : SplineComponents)
	{
		if (!Spline) continue;
		
		Ledges.Add(Spline);
	}

	for (int32 i = 0; i + 1 < Ledges.Num(); i += 2)
	{
		USplineComponent* A = Ledges[i];
		USplineComponent* B = Ledges[i + 1];

		if (!A || !B) continue;

		OppositeLedges.Add(A, B);
		OppositeLedges.Add(B, A);
	}
}

2. 파쿠르 동작을 진행하는 데에 필요한 구조체 정의

// Hurdle: Traverse over a thin object and end on the ground at a similar level (Low fence)
// Vault: Traverse over a thin object and end in a falling state (Tall fence, or elevated obstacle with no floor on the other side)
// Mantle: Traverse up and onto an object without passing over it
UENUM(BlueprintType)
enum class ETraversalActionType : uint8
{
	None		UMETA(DisplayName = "None"),
	Hurdle		UMETA(DisplayName = "Hurdle"),	
	Vault		UMETA(DisplayName = "Vault"),
	Mantle		UMETA(DisplayName = "Mantle")
};

// Has all the information needed to perform traversal action
USTRUCT()
struct FTraversalCheckResult
{
	GENERATED_BODY()

public:
	// Traversal Action Type
	UPROPERTY(EditAnywhere, Category = "Traversal")
	ETraversalActionType ActionType = ETraversalActionType::None;

	// Front Ledge Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	bool bHasFrontLedge = false;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector FrontLedgeLocation = FVector::ZeroVector;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector FrontLedgeNormal = FVector::ZeroVector;

	// Back Ledge Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	bool bHasBackLedge = false;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector BackLedgeLocation = FVector::ZeroVector;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector BackLedgeNormal = FVector::ZeroVector;

	// Back Floor Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	bool bHasBackFloor = false;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector BackFloorLocation = FVector::ZeroVector;

	// Obstacle Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float ObstacleHeight = 0.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float ObstacleDepth = 0.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float BackLedgeHeight = 0.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	UPrimitiveComponent* HitComponent = nullptr;

	// Montage Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	UAnimMontage* ChosenMontage = nullptr;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float StartTime = 0.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float PlayRate = 0.f;
};

// Input Structure For Evaluating Traversal Montage's Chooser Table
USTRUCT()
struct FTraversalChooserInput
{
	GENERATED_BODY()

public:
	// Traversal Action Type
	UPROPERTY(EditAnywhere, Category = "Traversal")
	ETraversalActionType ActionType = ETraversalActionType::None;

	// For Selecting Traversal Action Type
	UPROPERTY(EditAnywhere, Category = "Traversal")
	bool bHasFrontLedge = false;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	bool bHasBackLedge = false;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	bool bHasBackFloor = false;

	// Obstacle Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float ObstacleHeight = 0.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float ObstacleDepth = 0.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float BackLedgeHeight = 0.f;

	// Character Movement Information
	UPROPERTY(EditAnywhere, Category = "Traversal")
	ECustomMovementMode MovementMode = ECustomMovementMode::OnGround;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	EGait Gait = EGait::Run;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float Speed2D = 0.f;
};

// Output Structure For Evaluating Traversal Montage's Chooser Table
USTRUCT()
struct FTraversalChooserOutput
{
	GENERATED_BODY()

public:
	// Traversal Action Type
	UPROPERTY(EditAnywhere, Category = "Traversal")
	ETraversalActionType ActionType = ETraversalActionType::None;
};

// Input Structure For Tracing Traversable Objects
USTRUCT()
struct FTraversalCheckInput
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector TraceForwardDirection = FVector::ForwardVector;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float TraceForwardDistance = 75.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector TraceOriginOffset = FVector::ZeroVector;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	FVector TraceEndOffset = FVector::ZeroVector;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float TraceRadius = 30.f;
	UPROPERTY(EditAnywhere, Category = "Traversal")
	float TraceHalfHeight = 86.f;
};

오늘의 CS

Move Semantics

개념

  • C++11 표준에 도입된 불필요한 데이터 복사를 줄여 프로그램 성능을 최적화하기 위한 기능
  • 임시 객체나 수명이 다할 객체가 소유한 자원을 복사하는 대신 단순히 이동하여 소유권을 이전하는 방식으로 작동함

필요성

  • 복사 작업은 큰 자원(벡터, 문자열)을 다룰 때 성능 저하를 일으킴
  • Deep Copy를 하게 되면 새로운 힙 메모리를 할당하고 모든 데이터를 복사하는 과정을 거치게 됨
    -> 원본 객체가 곧 소멸될 임시 객체라면, 그 객체가 소유한 메모리 주소 자체를 가져와서 사용하는 것이 더 효과적임

Rvalue Reference (&&)

  • Lvalue : 식별 가능한 메모리 위치를 가지는 값 (예: int a;, a는 Lvalue)
  • Rvalue : 임시적이고, 식별자가 없으며, 수명이 짧은 값
  • Rvalue Reference(&&) : Rvalue에 바인딩되는 참조 타입
    -> 해당 객체가 안전하게 이동될 수 있는 임시 객체임을 표시함


이동 생성자와 이동 대입 연산자

  • 이동 생성자 : 새 객체를 생성할 때, 원본 임시 객체의 자원을 복사 대신 이동시킴
MyVector(MyVector&& other) noexcept {
    // 1. 자원(ptr) 소유권 이전: 포인터 복사
    data_ptr = other.data_ptr;
    size = other.size;

    // 2. 원본 객체 무력화: 원본 객체가 소멸될 때 자원을 해제하지 않도록 막음
    other.data_ptr = nullptr; 
    other.size = 0;
}
  • 이동 대입 연산자 : 이미 존재하는 객체에 임시 객체의 대입할 때 이동시킴
MyVector& operator=(MyVector&& other) noexcept {
    if (this != &other) {
        // 1. 기존 자원 해제 (필수)
        delete[] data_ptr; 

        // 2. 자원 소유권 이전 및 원본 무력화
        data_ptr = other.data_ptr;
        size = other.size;
        other.data_ptr = nullptr;
        other.size = 0;
    }
    return *this;
}

std::move()

  • Lvalue를 Rvalue Reference로 형변환하여 컴파일러에게 이동시켜도 안전하다고 알려주는 역할
  • 기존의 메모리는 비우고, 다른 객체가 해당 메모리를 소유하게 함

장점

  • 성능 향상 : 대용량의 데이터 복사를 포인터 교체 작업으로 대체하여 성능을 극대화할 수 있음
  • 예외 안전성 : 이동 연산은 일반적으로 예외가 발생하지 않도록 noexcept를 선언하여 안전성을 높임
  • RAII 최적화 : 스마트 포인터나 컨테이너 같은 RAII 객체들이 효율적으로 자원을 관리할 수 있게 함
profile
게임 개발자를 향해..

0개의 댓글