[25.05.21] :: 가디언 앤 시커 프로젝트 05

chooha·2025년 5월 21일

가디언앤시커

목록 보기
5/25

📝 개발일지 - GetCellAtPos() 성능 최적화

👨‍💻 오늘의 개발 작업

오늘은 룬 시스템에서 마우스 위치의 그리드 셀을 찾는 GetCellAtPos() 함수를 구현할 생각이었음
처음엔 단순하게 만들려다가 결국 수학적 계산으로 최적화하고, 한번 더 안정성까지 개선했음


💡 오늘의 5분 기록

1. 반복문 방식으로 일단 구현

처음엔 가장 생각하기 쉬운 방법으로 만들었음

// 처음 만든 코드
UGS_RuneGridCellWidget* GetCellAtPos(const FVector2D& ScreenPos)
{
    for (auto& CellPair : GridCells)
	{
		UGS_RuneGridCellWidget* CellWidget = CellPair.Value;
		FGeometry CellGeometry = CellWidget->GetCachedGeometry();
		FVector2D LocalMousePos = CellGeometry.AbsoluteToLocal(ScreenPos);

		FVector2D LocalSize = CellGeometry.GetLocalSize();
		if (LocalMousePos.X >= 0 && LocalMousePos.Y >= 0 &&
			LocalMousePos.X <= LocalSize.X && LocalMousePos.Y <= LocalSize.Y)
      	{
			return CellWidget;
		}
	}
	return nullptr;
}

이렇게 하면 동작은 하는데, 마우스 움직일 때마다 호출되니까 성능이 걱정됨
60FPS면 초당 60번 호출되는데 그때마다 모든 셀을 다 확인하는 게 맞나? 싶었음

2. 수학으로 바로 계산하는 방식 시도

UniformGridPanel은 균등하게 나뉘어져 있으니까 수학으로 바로 계산할 수 있을 것 같았음

// 개선한 코드
UGS_RuneGridCellWidget* GetCellAtPos(const FVector2D& ScreenPos)
{
    if (!IsValid(GridPanel) || !IsValid(BoardManager))
    {
        return nullptr;
    }
    
    // 화면 좌표를 그리드 좌표로 변환
    FGeometry GridGeometry = GridPanel->GetCachedGeometry();
    FVector2D LocalPos = GridGeometry.AbsoluteToLocal(ScreenPos);
    
    // 셀 크기 계산
    int32 NumColumns, NumRows;
    BoardManager->GetGridDimensions(NumRows, NumColumns);
    FVector2D GridSize = GridGeometry.GetLocalSize();
    FVector2D CellSize(GridSize.X / NumColumns, GridSize.Y / NumRows);
    
    // 바로 계산해서 찾기
    int32 Row = FMath::FloorToInt(LocalPos.Y / CellSize.Y);
    int32 Column = FMath::FloorToInt(LocalPos.X / CellSize.X);
    
    if (Row >= 0 && Row < NumRows && Column >= 0 && Column < NumColumns)
    {
        FIntPoint CellPos(Row, Column);
        if (GridCells.Contains(CellPos))
        {
            return GridCells[CellPos];
        }
    }
    
    return nullptr;
}

계산해보니까 실행시간이 46μs에서 0.5μs로 줄어들었음
반복문 없이 바로 찾으니까 훨씬 빠름

3. 예외 상황들 처리

코드는 빨라졌는데 실제 쓰다보니 몇 가지 문제가 있었음

  • 그리드 밖을 클릭하면 이상한 값 계산됨
  • 그리드 초기화 안 됐을 때 0으로 나누기 터질 수 있음

그래서 방어 코드 추가함

// 최종 버전
UGS_RuneGridCellWidget* GetCellAtPos(const FVector2D& ScreenPos)
{
    if (!IsValid(GridPanel) || !IsValid(BoardManager))
	{
		return nullptr;
	}

	FGeometry GridGeometry = GridPanel->GetCachedGeometry();
	FVector2D LocalPos = GridGeometry.AbsoluteToLocal(ScreenPos);

	FVector2D GridSize = GridGeometry.GetLocalSize();
    
    //그리드 밖이면 미리 걸러내기
	if (LocalPos.X < 0 || LocalPos.Y < 0 || LocalPos.X >= GridSize.X || LocalPos.Y >= GridSize.Y)
	{
		return nullptr;
	}

	int32 NumColumns, NumRows;
	BoardManager->GetGridDimensions(NumRows, NumColumns);

	// 0 나눗셈 방지
	if (NumRows <= 0 || NumColumns <= 0)
	{
		return nullptr;
	}

	FVector2D CellSize(GridSize.X / NumColumns, GridSize.Y / NumRows);

	int32 Row = FMath::FloorToInt(LocalPos.Y / CellSize.Y);
	int32 Column = FMath::FloorToInt(LocalPos.X / CellSize.X);

	if (Row >= 0 && Row < NumRows && Column >= 0 && Column < NumColumns)
	{
		FIntPoint CellPos(Row, Column);

		if (GridCells.Contains(CellPos))
		{
			return GridCells[CellPos];
		}
	}

	return nullptr;
}

4. 좌표 헷갈리는 문제 해결

중간에 가로세로 좌표 매핑이 헷갈리는 문제가 있었음
함수 이름이랑 변수명을 명확하게 바꿔서 해결

// 이전: 헷갈리는 이름들
GetGridDimensions(int32& OutWidth, int32& OutHeight);
int32 GridX, GridY;

// 변경: 명확한 이름들  
GetGridDimensions(int32& OutRows, int32& OutColumns);
int32 Row, Column;

이렇게 하니 실수할 일이 없어짐

5. 성과랑 아쉬운 점

처음엔 성능 최적화가 잘 됐다고 생각했음

  • O(n)에서 O(1)로 시간복잡도 개선
  • 실행시간 향상
  • 그리드가 커져도 성능 일정

근데 돌이켜보니 우리 그리드는 4×5 (20개 셀) 정도라서 원래 반복문도 충분했을 것 같음
실제로 20개 정도 도는 건 사용자가 느낄 수 없는 수준이었을 듯

그래서 결국 간단한 반복문 방식으로 되돌렸음
복잡한 최적화 코드에 비해 처음 방식이 읽기 쉽고, 유지보수 편할 것 같음

이 과정에서 성능 분석하는 방법이랑 좌표 변환 로직을 제대로 이해할 수 있었음
하지만 가장 큰 교훈은 "기능과 성능에 문제가 없다면 간단하고 명확한 코드가 최고"라는 것
앞으로는 실제로 문제가 있는지부터 확인하고, 복잡한 해결책보다 단순한 해결책을 우선 고려해야겠다고 생각함


📚 개발 참고

성능 최적화할 때

  • 실제 문제가 있는지 먼저 측정
  • 시간복잡도 개선이 제일 효과적
  • 코드 복잡해지는 거 vs 성능 이득 비교해보기

UniformGridPanel 쓸 때 팁

  • 균등분할이니까 수학으로 바로 계산 가능
  • 전체 크기 나누기 그리드 개수 = 셀 크기
  • FloorToInt 쓰면 경계처리 깔끔함

0개의 댓글