오늘의 코드카타
오늘의 공부
팀 프로젝트 진행 내용
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*> Ledges와 TMap<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 객체들이 효율적으로 자원을 관리할 수 있게 함