TIL

2025.02.04(화)

iiblueblue 2025. 2. 4. 10:39

Code KATA

오늘치의 알고리즘 코드카타를 풀이하고 정리하였다

https://iiblueblue.tistory.com/199

 

[2025.02.24] 문자열 나누기

문제 설명문자열 s가 입력되었을 때 다음 규칙을 따라서 이 문자열을 여러 문자열로 분해하려고 합니다.먼저 첫 글자를 읽습니다. 이 글자를 x라고 합시다.이제 이 문자열을 왼쪽에서 오른쪽으

iiblueblue.tistory.com

많이 고민할 필요 없이 문제에서 하라는대로만 구현하면 되는 문제여서 어렵지는 않았다. 마지막에 글자 등장 횟수가 다른 채로 그냥 끝나는 경우만 신경써서 처리해주면 되는 문제였다.

 

 

Quest

  • [6번 과제] 회전 발판과 움직이는 장애물 퍼즐 스테이지 
  • [7번 과제] Pawn 클래스로 3D 캐릭터 만들기 

드론 on/off 구현

비행 시와 낙하 시 움직임을 다르게 하는 것을 자연스럽게 구현하기 위해서는 드론의 전원을 끄기 켜는 기능을 한 가지 더 구현해야했다. 그래서 드론 메시에 다른 Static Mesh를 하나 붙여 전원을 표시하였다. 원래같았으면 프로펠러를 돌리고 아니고로 표현할 생각이었지만 애니메이션이 포함되지 않은 에셋인 관계로 전원으로 표시하였다.

on과 off를 각각 표시하기 위해 빨간색 불과 초록색 불을 표현할 Material이 필요했다. 그래서 각각 M_On, M_Off라는 이름으로 Material을 만들었다. 이제 코드로 Material을 변경하여야 했다.

그보다 먼저 해야할 것은 드론을 껐다켰다할 입력을 하나 더 만들어주는 것이다. 여직 입력을 만들던 것과 똑같이 IA_On을 하나 만들어주고 IMC에 등록해주었다. 플레이어 컨트롤러에 IA를 추가해주고 Drone 코드에서는 OnDrone이라는 함수를 만들어 InputAction과 바인딩하였다. 바인딩 할 때는 Triggered일 때가 아니라 Started일 때 함수를 호출하도록 한다. 한 번만 호출되어야 하기 때문이다. 만약 Triggered로 바인딩하면 함수가 여러번 호출되기 때문에드론의 onoff 여부가 여러번 바뀐다.

 

OnDrone에서는 두 가지 기능을 수행해야했다. 먼저 Drone의 On/Off를 표시하고 있는 bIsDroneActive의 값을 반대값으로 바꿔준다. 꺼져있으면 켜고, 켜있으면 끄는 것이다. 다음으로 드론의 상태를 보고 전원불의 색을 Material을 변경하여 바꿔준다.

void ADrone::OnDrone(const FInputActionValue& value)
{
	bIsDroneActive = !bIsDroneActive;
	if (bIsDroneActive)
	{
		StaticMeshComp->SetMaterial(0, MaterialOn);
	}
	else
	{
		StaticMeshComp->SetMaterial(0, MaterialOff);
	}
}

Material은 StaticMeshComponent의 SetMaterial을 이용한다. 첫 번째 매개변수는 여러개의 Material중 index를 뜻하며 MaterialOn과 MaterialOff는 UMaterial* 타입의 Material이다. 이는 블루프린트에서 직접 Material을 연결해준다. 그래서 두 Material 변수는 UPROPERTY의 설정에서 EditAnywhere으로 하는 것을 잊지 않는다. 이걸 잊고 편집이 안되는 문제로 꽤 걸렸다.

 

결과적으로는 아래처럼 구현하였다.

ON OFF

 

중력 및 낙하 구현

중력을 언리얼에서 제공하는 기능을 사용하지 않고 만들었다. 내가 구현하려고 하는 중력은 드론이 작동중일 때는 적용되지 않는다. 하지만 드론이 작동을 멈추면 중력이 적용되며 바닥으로 떨어지게 된다.

 

안타깝게도 나는 물리 선택자가 아니라 중력하면 바로 공식이 떠오르거나 하지 않는다. 그래서 일단 중력을 구현하는 공식부터 알아봤다.

 

필요한 변수들이 몇가지 있다.

	FVector3d Acceleration; // 가속도
	FVector3d Velocity; // 현재 속도
	const float Gravity = -980.0f; // 중력 가속도(cm/s^2)
  • Acceleration : 가속도
  • Velocity : 현재 속도
  • Gravity : 중력 가속도(-980.0f)

중력은 Tick에 구현하도록 한다. 계속 매 프레임마다 떨어지는 것을 구현해야하기 때문이다.

void ADrone::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 중력 적용
	if (!bIsDroneActive)
	{
		if (!bIsGrounded)
		{
			Velocity.Z += Gravity * DeltaTime;
		}
	}
    ...
	FVector NewLocation = GetActorLocation() + Velocity * DeltaTime;
	SetActorLocation(NewLocation, true, &Hit);
    ...
}

중력은 bIsDroneActive가 false일 때 적용되도록 한다. 즉, 드론의 전원이 꺼져있을 때만 적용되는 것이다. 그리고 bIsGrounded는 바닥에 붙어있는지를 확인하는 것이다. Z축으로의 속도에 Gravity*DeltaTime을 더해준다. 그리고 새로운 위치를 아래서 정의한다. 한 프레임 뒤 아래로 떨어지고 있는 새로운 위치일 것이다. 현재 위치에서 속도만큼 더해준다. 마지막으로 SetActionLocation을 이용해 위치를 이동시켜준다.

 

바닥 충돌 구현

지금은 중력으로 드론이 떨어지면 땅을 뚫고 떨어지게 된다. 이 부분을 해결하기 위해 바닥과의 충돌을 구현하였다. 우선 전원이 꺼져서 떨어져 땅에 닿을 경우 바닥과 충돌해야 한다. 그리고 Shift를 눌러서 드론을 아래로 내려가게 할 때도 바닥과의 충돌을 구현해야 한다.

 

바닥과의 충돌을 제한하기 위해 Tick에서 충돌한 것이 없는지 감지하고 충돌했다면 중력으로 인한 Z 속도를 0으로 하고 바닥과 붙도록 위치를 조정한다. 충돌을 감지하기 위한 것이기 때문에 SetActorLocation을 할 때는 Sweep을 사용하도록 한다.

void ADrone::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 중력 적용
	if (!bIsDroneActive)
	{
		if (!bIsGrounded)
		{
			Velocity.Z += Gravity * DeltaTime;
		}
	}

	FHitResult Hit;
	FVector NewLocation = GetActorLocation() + Velocity * DeltaTime;
	SetActorLocation(NewLocation, true, &Hit);

	if (Hit.bBlockingHit)
	{
		bIsGrounded = true;
		Velocity.Z = 0.0f;
		if (!bIsDroneActive)
		{
			SetActorLocation(Hit.Location + FVector(0.0f, 0.0f, 8.0f)); // 정확한 충돌 지점으로 이동
		}
	}
	else
	{
		bIsGrounded = false;
	}

	CheckGroundCollision();
    ...
}

CheckGroundCollision은 Sweep으로 충돌을 감지하지 못할 것을 대비하여 추가적으로 바닥을 감지한다. LineTrace를 이용해서 바닥을 체크 하기 때문에 좀 더 정확한 체크를 할 수 있어진다.

void ADrone::CheckGroundCollision()
{
	FVector Start = GetActorLocation();
	FVector End = Start + FVector(0.0f, 0.0f, -50.0f);

	FHitResult HitResult;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this); // 자기 자신은 제외

	bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_PhysicsBody, Params);

	if (bHit)
	{
		bIsGrounded = true;
		Velocity.Z = 0.0f;
		if (!bIsDroneActive)
		{
			SetActorLocation(HitResult.Location+FVector(0.0f, 0.0f, 8.0f)); // 정확한 충돌 위치 보정
		}
	}
	else
	{
		bIsGrounded = false;
	}
}

 

 

이렇게 구현한다고 해서 Shift를 눌렀을 때 바닥 충돌을 하는 것이 아니기 때문에 Shift를 눌렀을 때, 즉 MoveUp이 호출되었을 때도 바닥 충돌을 해주도록 수정하였다.

void ADrone::MoveUp(const FInputActionValue& value)
{
	if (!Controller) return;

	const FVector3d MoveInput = value.Get<FVector3d>();

	if (!FMath::IsNearlyZero(MoveInput.Z))
	{
		if (bIsDroneActive)
		{
			//AddActorLocalOffset(FVector(0.0f, 0.0f, MoveInput.Z)*MoveSpeed);
			FVector NewLocation = GetActorLocation() + FVector(0.0f, 0.0f, MoveInput.Z) * MoveSpeed;
			// 바닥 충돌 감지
			FHitResult HitResult;
			FCollisionQueryParams Params;
			Params.AddIgnoredActor(this);

			FVector Start = GetActorLocation();
			FVector End = Start + FVector(0.0f, 0.0f, MoveInput.Z * MoveSpeed);

			bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_PhysicsBody, Params);

			if (bHit && MoveInput.Z < 0) // 바닥과 충돌했으며 아래로 이동 중이라면
			{
				bIsGrounded = true;
				Velocity.Z = 0.0f;
				SetActorLocation(HitResult.Location + FVector(0.0f, 0.0f, 8.0f)); // 살짝 위로 보정
			}
			else
			{
				bIsGrounded = false;
				SetActorLocation(NewLocation, true); // Sweep 적용하여 자연스러운 충돌 감지
			}
		}
	}
}

 

마우스 회전 제한

마우스의 Y축 회전을 제한하였다. 조작하다보면 Y축 회전이 360도 회전하게 되는 경우가 빈번히 발생한다. 그러면 갑자기 조작하기 매우 어려워진다. 그래서 Y축으로 마우스를 움직일 때 카메라가 회전되는 각도에 제한을 주었다.

void ADrone::Look(const FInputActionValue& value)
{
	FVector2D LookInput = value.Get<FVector2D>();
	AddActorLocalRotation(FRotator(0.0f, LookInput.X, 0.0f)); // 액터 회전

	// 카메라 회전(Y축)
	// 현재 SpringArm의 상대적 pitch 값을 가져옴
	FRotator CurrentRotation = SpringArmComp->GetRelativeRotation();
	float NewPitch = CurrentRotation.Pitch + LookInput.Y;

	// 특정 각도 범위로 제한 (예: -45도 ~ 45도)
	NewPitch = FMath::Clamp(NewPitch, -45.0f, 45.0f);

	// 제한된 값을 적용
	SpringArmComp->SetRelativeRotation(FRotator(NewPitch, 0.0f, 0.0f));
}

Clamp를 이용해 값을 제한해주었는데 이걸 사용하기 위해 기존에 사용하던 AddRelativeRotation 함수가 아닌 SetRelativeRotation 함수를 사용해서 구현했다.

 

 

 

'TIL' 카테고리의 다른 글

2025.02.06(목)  (0) 2025.02.06
2025.02.05(수)  (0) 2025.02.05
2025.02.03(월)  (0) 2025.02.03
2025.01.31(금)  (0) 2025.01.31
2025.01.29(수)  (0) 2025.01.29