오늘은 아케인보드 시스템에서 특수 셀을 시작점으로 연결된 룬들에게 보너스 스탯을 부여하는 연결로직 시스템을 구현했음
처음엔 단순히 룬 ID만 저장하면 될 줄 알았는데, UI 시각효과와 보너스 계산을 고려하니 데이터 구조 설계가 생각보다 복잡했음. 특히 어떤 자료구조를 사용할지, 어떤 탐색 알고리즘을 쓸지에 대한 고민이 많았음
특수 그리드 셀 하나에서 시작해서 인접한 룬들을 탐색하고, 연결된 모든 룬에 보너스 스탯을 부여하는 시스템이 필요했음
시스템 규모
1차 고민: 연결된 룬 ID만 저장
TArray<uint8> ConnectedRuneIDs;
문제점: UI에서 시각 효과를 위해 룬이 차지하는 셀 위치가 필요한데 역추적이 복잡함
2차 고민: 연결된 셀 위치만 저장
TArray<FIntPoint> ConnectedCellPositions;
문제점: 보너스 스탯 계산 시 어떤 룬인지 알기 어려움
최종 결정: 룬별 셀 위치 저장
// FArcaneBoardStats 구조체 내부
TMap<uint8, TArray<FIntPoint>> ConnectedRuneCells;
결정 이유:
DFS 선택 이유:
void FindConnectedCells(const FIntPoint CellPos, TSet<FIntPoint>& VisitedCell) {
// 방문 체크
if (VisitedCell.Contains(CellPos)) return;
// 빈 셀에서 가지 중단
FGridCellData CellData;
if (!GetCellData(CellPos, CellData) || CellData.State == EGridCellState::Empty) {
return;
}
// 현재 셀 처리
VisitedCell.Add(CellPos);
uint8 RuneID = CellData.PlacedRuneID;
if(!CurrBoardStats.ConnectedRuneCells.Contains(RuneID)) {
CurrBoardStats.ConnectedRuneCells.Add(RuneID, TArray<FIntPoint>());
}
CurrBoardStats.ConnectedRuneCells[RuneID].Add(CellPos);
// 4방향 재귀 탐색
TArray<FIntPoint> Directions = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} };
for (const FIntPoint& Dir : Directions) {
FIntPoint NextPos = CellPos + Dir;
FindConnectedCells(NextPos, VisitedCell);
}
}
핵심 장점:
1차: 기존 스탯 델리게이트 활용
OnStatsChanged.Broadcast(CurrStatEffects); // FGS_StatRow만 전달
문제: UI 시각 효과를 위한 연결 정보가 없음
2차: 매개변수 두 개로 분리
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnConnectionsChangedDelegate,
const FGS_StatRow&, RuneStats,
const TMap<uint8, TArray<FIntPoint>>&, ConnectedRuneCells);
문제:
TMap<uint8, TArray>와 같은 타입은 매개변수로 사용 안됨
보너스 스탯을 UI에서 계산해야 하는 책임 분리 문제
최종: 통합 구조체로 해결
// FArcaneBoardStats 구조체
struct FArcaneBoardStats {
FGS_StatRow RuneStats; // 룬 기본 스탯
FGS_StatRow BonusStats; // 연결 보너스 스탯
TMap<uint8, TArray<FIntPoint>> ConnectedRuneCells; // 시각효과용
};
// 델리게이트는 단순하게
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStatsChangedDelegate,
const FArcaneBoardStats&, BoardStats);
실제 사용:
void CalculateStatEffects() {
UpdateConnections(); // 연결 로직 먼저 실행
// 기본 스탯 계산
FGS_StatRow BaseResult, BonusResult;
TMap<uint8, TArray<FIntPoint>> ConnectedRunes = CurrBoardStats.ConnectedRuneCells;
float BonusValue = ConnectedRunes.Num();
// 룬별 스탯 계산 및 연결 보너스 적용
for (const FPlacedRuneInfo& Rune : PlacedRunes) {
FRuneTableRow RuneData;
if (GetRuneData(Rune.RuneID, RuneData)) {
// 기본 스탯 추가
if (RuneData.StatEffect.StatName == FName("HP")) {
BaseResult.HP += RuneData.StatEffect.Value;
// 연결 보너스 추가
if (ConnectedRunes.Contains(Rune.RuneID) && BonusResult.HP == 0) {
BonusResult.HP = BonusValue;
}
}
// 다른 스탯들도 동일하게...
}
}
CurrBoardStats.RuneStats = BaseResult;
CurrBoardStats.BonusStats = BonusResult;
}

구현 완료된 기능:
SpecialCellPos) 기준 4방향 DFS 연결 탐색TMap<uint8, TArray<FIntPoint>>)핵심 깨달음: