오늘은 룬 시스템에서 마우스 위치의 그리드 셀을 찾는 GetCellAtPos() 함수를 구현할 생각이었음
처음엔 단순하게 만들려다가 결국 수학적 계산으로 최적화하고, 한번 더 안정성까지 개선했음
처음엔 가장 생각하기 쉬운 방법으로 만들었음
// 처음 만든 코드
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번 호출되는데 그때마다 모든 셀을 다 확인하는 게 맞나? 싶었음
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로 줄어들었음
반복문 없이 바로 찾으니까 훨씬 빠름
코드는 빨라졌는데 실제 쓰다보니 몇 가지 문제가 있었음
그래서 방어 코드 추가함
// 최종 버전
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;
}
중간에 가로세로 좌표 매핑이 헷갈리는 문제가 있었음
함수 이름이랑 변수명을 명확하게 바꿔서 해결
// 이전: 헷갈리는 이름들
GetGridDimensions(int32& OutWidth, int32& OutHeight);
int32 GridX, GridY;
// 변경: 명확한 이름들
GetGridDimensions(int32& OutRows, int32& OutColumns);
int32 Row, Column;
이렇게 하니 실수할 일이 없어짐
처음엔 성능 최적화가 잘 됐다고 생각했음
근데 돌이켜보니 우리 그리드는 4×5 (20개 셀) 정도라서 원래 반복문도 충분했을 것 같음
실제로 20개 정도 도는 건 사용자가 느낄 수 없는 수준이었을 듯
그래서 결국 간단한 반복문 방식으로 되돌렸음
복잡한 최적화 코드에 비해 처음 방식이 읽기 쉽고, 유지보수 편할 것 같음
이 과정에서 성능 분석하는 방법이랑 좌표 변환 로직을 제대로 이해할 수 있었음
하지만 가장 큰 교훈은 "기능과 성능에 문제가 없다면 간단하고 명확한 코드가 최고"라는 것
앞으로는 실제로 문제가 있는지부터 확인하고, 복잡한 해결책보다 단순한 해결책을 우선 고려해야겠다고 생각함