Code KATA
오늘치의 알고리즘 코드카타를 풀이하고 정리하였다
https://iiblueblue.tistory.com/180
[2025.01.23] 명예의 전당 (1)
문제 설명"명예의 전당"이라는 TV 프로그램에서는 매일 1명의 가수가 노래를 부르고, 시청자들의 문자 투표수로 가수에게 점수를 부여합니다. 매일 출연한 가수의 점수가 지금까지 출연 가수들
iiblueblue.tistory.com
sort함수에 대해서 명확히 알고 있지 못했다는 점 때문에 오답이 한 번 나왔었고 단순히 문제에서 준 값을 명확히 이해하지 못해 실수로 잘못 하용하여 오답이 나왔었다. 자세한 내용은 링크 안에 적어 놓았지만 단순히 말하자면, sort 함수의 두 번째 매개변수는 그 위치를 포함하지 않고 그 앞까지 정렬의 범위가 된다는 점이 헷갈려 코드를 잘못 적은 이슈가 있었다. 그리고 k번재 참가자의 점수를 저장하여야 하는데 k번째라면 인덱스에서는 k-1로 접근해야 하는데 이 부분을 실수했다.
덕분에 sort 함수에 대해서 더 명확히 알았다.
Unreal
1주차 강의를 모두 들어 일단 강의 듣기를 중단하고 6번 과제를 시작하였다.
Quest
- [6번 과제] 회전 발판과 움직이는 장애물 퍼즐 스테이지 ◀
- [7번 과제] Pawn 클래스로 3D 캐릭터 만들기
회전하는 발판 만들기
이전에 강의에서 해봤던 것이라서 가장 쉽게 금방 만들었다.
왕복 이동하는 발판 만들기
이동하는 발판 만들기는 어떻게 해야할지 바로 생각이 났는데 왕복하는 발판을 만드는 것은 조금 생각이 필요했다. 일정 시간이 지나면 방향을 바꾸도록 해야하는지, 아니면 일정 거리를 넘어가면 방향을 바꾸게 해야 하는지 고민을 좀 했다. 결국 거리로 구현하였다.
프레임 때문에 점점 위치가 변할 수도 있다는 생각이 들긴 했는데 나중에 시간이 나면 이 부분을 수정해보려고 한다.
일정 시간 후 사라지는 발판 만들기
강의에서 나오지 않은 내용이었지만 관련 내용들을 조금 찾아봤다. 아래는 타이머 사용에 관한 언리얼의 공식 문서이다.
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-timers-in-unreal-engine
이런 기능이 언리얼에도 있지 않을까 생각했는데 Unity의 Coroutine과 다르긴하지만 비슷하게 사용할 수 있을 것 같았다. 아래는 Unity의 공식 문서이다.
https://docs.unity3d.com/kr/2022.3/Manual/Coroutines.html
코루틴 - Unity 매뉴얼
코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니
docs.unity3d.com
"n초 뒤에 기능이 실행되도록 하겠다" 라는 목적이 비슷해보였다. 심지어 언리얼의 타이머에는 반복을 매개변수로 지시할 수 있었다. Unity의 Coroutine과는 다른 점이다. Coroutine에서 반복을 사용하려면 안에 직접 반복문을 선언해주어야 한다.
이렇게 생각하니 조금 더 기능과 가까워진 것 같았다. 하지만 사용하는 방식이 좀 다르고 개념도 다른 것 같아 분석이 필요했다. 공식 문서를 보고도 완전히 코드의 뜻을 이해햐기가 어려워서 챗 GPT에게 설명을 부탁했다.
GetWorldTimerManager().SetTimer(BoardTimerHandle, this, &ATimerBoard::DeactiveBoard, 1.0f, true);
- GetWorldTimerManager() : 월드의 타이머 매니저 객체를 가져온다. 언리얼에서 시간 기반 작업을 관리하기 위해 제공되는 API로, 월드의 상태와 관련된 타이머를 제어한다.
- SetTimer() : 타이머를 설정하는 함수이다. 이 함수는 일정시간 마다 특정 함수가 호출되도록 예약한다.
- BoardTimerHandle : 타이머의 고유 식별자이다. 이 핸들은 타이머를 시작, 정지, 리셋할 때 사용할 수 있다. FTimerHandle 타입이어야 한다.
- this : 이 타이머가 속한 객체를 나타낸다. 여기서는 현재 클래스의 인스턴스를 가리킨다.
- &ATimerBoard::DeactiveBoard : 타이머가 트리거될 때 실행될 함수이다. ATimerBoard의 멤버 변수인 DeactiveBoard()를 호출하도록 설정한다.
- 1.0f : 타이머의 간격(시간)이다. 여기서는 1초마다 DeactiveBoard()가 호출된다.
- true : 반복 여부를 나타낸다. true로 설정했으므로 타이머가 반복적으로 실행된다.
이런 뜻으로 사용하는데 참고하였다.
Timer보다 나를 더 괴롭혔던건 사실 Interval 이었다. 발판이 사라지는 시간 간격을 Interval이라는 변수로 하여 언리얼 에디터 상에서도 수정하면 바로 화면에서 적용되게 하고 싶었다. 위에 두 개의 발판은 Tick에서 조정하기 때문에 변수의 값을 바꾸면 바로 적용된다. 하지만 Timer의 경우는 좀 다르다. BeginPlay()에서 Interval을 한번 적용하고 말기 때문에 게임 중간에 Interval을 아무리 바꿔도 적용되지 않는다. 그래서 Interval이 언리얼 에디터에서 바뀔 때마다 타이머를 다시 설정해주었으면 했는데 지금의 코드에서는 구현할 수 없어서 다른 함수 하나를 모셔왔다. 바로 PostEditChangeProperty() 함수이다. 아래와 같이 구현했다.
#if WITH_EDITOR
void ATimerBoard::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
FName PropertyName = (PropertyChangedEvent.Property != nullptr)
? FName(*PropertyChangedEvent.Property->GetName()) // FString -> FName 변환
: NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(ATimerBoard, Interval))
{
StartTimer(); // Interval 값이 변경되었을 때만 타이머 갱신
}
}
#endif
PostEditChangeProperty() 함수는 에디터에서 값이 변경될 때마다 호출되는 함수이다. 사실 에디터에서 레벨 디자인을 하는 사람이 에디터를 건드린다고 생각하고 만든 것이기 때문에 실제 게임에는 이 부분이 반영될 필요는 없다. 따라서 에디터에서만 이 기능을 사용하도록 #if WITH_EIDITOR, #endif 를 선언해주었다. 이 안에 들어가는 기능은 에디터에서만 사용하는 기능이다.
함수 안에서는 변경된 변수가 Interval이라면 타이머를 다시 만들도록 한다.
이 발판을 만들면서 가장 많이 새로운 것을 알고 공부한 것 같다.
발판 랜덤 스폰하기
어떻게 해야할지 가장 암담했던 것 같다. 일단 규칙을 짜보기로 했다.
가운데 스포너를 두고 정사각형의 한 변을 변수로 받아서 그 만큼의 공간에서만 발판이 생기도록 하는 것이다. 발판은 청므에 랜덤한 위치에서 하나 생성되고 다음 발판은 일정 거리 범위 안에서 생성되는데 이전 발판의 위치에는 설치되지 않는다. 이전 발판에서 distance 만큼 떨어지지만 방향은 랜덤으로 하였다. XY좌표 상에서 360도 중 원하는 각도의 위치에 distance 만큼의 간격이 떨어져서 발판이 생기는 것이다. 그리고 마지막으로 Z는 살짝 올려주기만 하는 것이다. 이렇게 점점 올라가는 발판을 랜덤으로 생성한다.
챗 GPT의 도움을 조금씩 받으면서 코드를 완성해나갔다. 아래와 같이 구현하였다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "BoardSpawner.h"
#include "Kismet/KismetMathLibrary.h"
// Sets default values
ABoardSpawner::ABoardSpawner()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
SetRootComponent(SceneComponent);
}
// Called when the game starts or when spawned
void ABoardSpawner::BeginPlay()
{
Super::BeginPlay();
CenterLocation = GetActorLocation();
SpawnBoard();
}
// Called every frame
void ABoardSpawner::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ABoardSpawner::SpawnBoard()
{
TArray<FVector> SpawnedLocations;
FVector CurrentLocation = GetActorLocation(); // BoardSpawner의 시작 위치
// NumberOfBoards 만큼 스폰
for (int i = 0; i < NumberOfBoards; i++)
{
FVector SpawnLocation;
bool bIsLocationValid = false;
// x, y, z 좌표 랜덤 생성 (알맞은 위치가 나올 때까지)
// 새로운 발판 위치 계산
while (!bIsLocationValid)
{
// 이전 위치에서 MinDistance ~ MaxDistance 사이로 이동
float Distance = FMath::RandRange(MinDistanceBetweenBoard, MaxDistanceBetweenBoard);
// 랜덤 방향으로 위치 계산
FRotator RandomDirection = FRotator(0, FMath::RandRange(0.0f, 360.0f), 0);
FVector Direction = RandomDirection.Vector();
SpawnLocation = CurrentLocation + Direction * Distance;
// 전체 범위 안에 있는지 확인
bIsLocationValid = IsLocationWithinBounds(SpawnLocation);
}
SpawnLocation += FVector(0.0f, 0.0f, AdditionalHeight);
SpawnedLocations.Add(SpawnLocation);
CurrentLocation = SpawnLocation; // 현재 위치 갱신
// 랜덤한 발판 클래스 선택
TSubclassOf<AActor> SelectedBoardClass;
int32 RandomIndex = FMath::RandRange(0, 2);
switch (RandomIndex)
{
case 0:
SelectedBoardClass = SpinningBoardClass;
break;
case 1:
SelectedBoardClass = MovingBoardClass;
break;
case 2:
SelectedBoardClass = TimerBoardClass;
break;
}
if (SelectedBoardClass)
{
GetWorld()->SpawnActor<AActor>(SelectedBoardClass, SpawnLocation, FRotator::ZeroRotator);
}
}
}
bool ABoardSpawner::IsLocationWithinBounds(const FVector& Location)
{
// Location이 스폰 범위 내에 있는지 확인
return FMath::Abs(Location.X - CenterLocation.X) <= SpawnCubeSize / 2.0f &&
FMath::Abs(Location.Y - CenterLocation.Y) <= SpawnCubeSize / 2.0f &&
Location.Z - CenterLocation.Z >= 0.0f &&
Location.Z - CenterLocation.Z <= SpawnCubeSize;
}
'TIL' 카테고리의 다른 글
2025.01.27(월) (0) | 2025.01.27 |
---|---|
2025.01.24(금) (1) | 2025.01.24 |
2025.01.22(수) (0) | 2025.01.22 |
2025.01.21(화) (0) | 2025.01.21 |
2025.01.20(월) (0) | 2025.01.20 |