게임 밸런스를 위한 엑셀 활용 실무 가이드 : 함수·수식

지식

게임 밸런스 작업에서 자주 쓰이는 엑셀 함수와 수식 패턴을 정리했다. 참조 기초부터 VLOOKUP, INDEX+MATCH, SUMPRODUCT 곱하기 패턴, 성장 곡선 설계까지 실무 예시와 함께 상세히 다룬다.

게임 밸런스 작업에서 엑셀은 빠질 수 없는 도구다. 스탯 테이블, 경험치 곡선, 드롭 확률, 경제 시스템까지 모든 수치 설계가 엑셀에서 이뤄진다. 이 글에서는 실무에서 자주 쓰이는 엑셀 수식 패턴을 게임 밸런스 예시와 함께 상세히 정리한다. 단순히 함수 사용법이 아니라, 언제 어떤 상황에 이 함수를 선택해야 하는지, 그리고 실수하기 쉬운 함정은 무엇인지를 다룬다.

1. 참조 기초: 절대참조와 상대참조

엑셀에서 수식을 복사해 여러 셀에 적용하려면, 가장 먼저 '참조'라는 개념을 이해해야 한다. 캐릭터 100명의 공격력에 동일한 배율을 곱하는 상황을 생각해보자. 배율이 들어있는 셀 하나를 모든 수식에서 참조해야 하는데, 수식을 복사하면 참조 위치가 덩달아 이동해버린다. 이 문제를 해결하는 게 절대참조다. 반대로 '레벨 × 등급' 매트릭스처럼 행과 열이 각각 다른 기준을 참조해야 할 때는 혼합참조가 필요하다. 이 개념을 제대로 모르면 수식을 복사할 때마다 참조가 깨져서 매번 수동으로 고쳐야 하는 상황에 빠진다.

참조 유형 정리

A1 — 상대참조. 수식을 복사하면 행과 열이 함께 이동한다.
$A$1 — 절대참조. 복사해도 항상 같은 셀을 가리킨다.
$A1 — 혼합참조. 열(A)만 고정되고 행은 이동한다.
A$1 — 혼합참조. 행(1)만 고정되고 열은 이동한다.

팁: 수식 입력 중 셀 주소를 선택한 상태에서 F4를 누르면 A1 → $A$1 → A$1 → $A1 → A1 순으로 순환한다. 달러 기호를 직접 입력할 필요가 없다.

예시 1: 전역 밸런스 배율 적용

상황을 가정해보자. CBT 피드백을 분석했더니 전체적으로 데미지가 너무 낮아서 전투가 지루하다는 의견이 많았다. 모든 캐릭터의 공격력에 1.2배를 곱해서 테스트해보고 싶다. 캐릭터가 100명이라면 100개의 수식을 일일이 고쳐야 할까?

해법은 배율을 별도 셀에 두고 절대참조로 연결하는 것이다. G1 셀에 1.2를 넣고, B열에 기본 공격력이 있다면:

=B2 * $G$1

이 수식을 B2에 쓰고 아래로 드래그하면, B3 * $G$1, B4 * $G$1... 이렇게 자동으로 채워진다. B는 상대참조라 행에 따라 이동하고, $G$1은 절대참조라 항상 고정된다. 나중에 1.3배로 바꾸고 싶으면 G1 셀 하나만 수정하면 100개 셀이 전부 자동 갱신된다.

주의: 절대참조를 빼먹고 =B2*G1로 쓰면, 복사할 때 G1이 G2, G3으로 이동해버린다. 의도치 않은 셀을 참조하게 되어 완전히 틀린 결과가 나온다.

예시 2: 레벨 × 등급 매트릭스

캐릭터 스탯 테이블을 만들 때, 레벨(1~100)과 등급(노멀~SSR)에 따른 HP 매트릭스가 필요하다고 하자. A열에 레벨별 기본HP(100, 105, 110...), 1행에 등급 배율(1, 1.2, 1.5, 2)이 있다.

=$A2 * B$1

이 수식 하나로 전체 매트릭스를 채울 수 있다.

$A2: A열은 고정(레벨 기본값), 행은 이동
B$1: 1행은 고정(등급 배율), 열은 이동

B2에 이 수식을 쓰고 오른쪽+아래로 드래그하면, 각 셀이 자기 행의 레벨값과 자기 열의 등급 배율을 자동으로 곱한다. 100레벨 × 5등급 = 500개 셀을 수식 하나로 채울 수 있다.

주의: 혼합참조는 달러 기호 위치가 헷갈리기 쉽다. '$A2'와 'A$2'는 완전히 다르다. '$A2'는 열 고정, 'A$2'는 행 고정이다. 매트릭스 복사 방향을 먼저 생각하고 어느 쪽을 고정할지 결정해야 한다.

2. 이름 정의: 계수와 테이블 관리

밸런스 시트가 복잡해지면 수식에 '$G$1', '$Sheet2.$A$5' 같은 셀 주소가 난무하게 된다. 한 달 뒤에 이 시트를 다시 열면 'G1이 뭐였지?' 하고 헤매게 된다. 다른 사람이 이 시트를 받으면 더 심하다. 이름 정의는 이 문제를 해결한다. 셀이나 범위에 의미 있는 이름을 붙여서 '$G$1' 대신 '크리배율'로 참조하는 것이다. 수식 가독성이 올라가고, 밸런스 상수를 한곳에 모아 관리할 수 있다. 거의 모든 실무 프로젝트에서 사용하는 표준 패턴이다.

설정 방법: 셀이나 범위를 선택하고 수식 입력창 왼쪽의 이름 상자에 이름을 입력한 뒤 엔터를 치면 된다. 이름 관리자(Ctrl+F3)에서 정의된 이름을 확인하고 수정할 수 있다. 범위도 이름을 붙일 수 있어서, 테이블 전체를 '스탯테이블'로 지정해두면 VLOOKUP에서 편하게 참조할 수 있다.

예시: 밸런스 계수 시트 구성

별도의 '계수' 시트를 만들고 모든 밸런스 상수를 모아둔다. 이 방식은 거의 모든 실무 프로젝트에서 사용하는 표준 패턴이다.

계수 시트 구성 예시:
• 레벨보정 = 1.05 (레벨당 성장률)
• 크리배율 = 1.5 (크리티컬 데미지 배율)
• 방어력감소율 = 0.8 (방어력 적용 비율)
• 골드드롭배율 = 1.0 (이벤트 때 2.0으로 변경)
• 스탯테이블 = 캐릭터시트!A1:Z100 (캐릭터 스탯 전체 범위)

다른 시트의 데미지 계산 수식에서는 이렇게 쓴다:

=기본공격력 * 레벨보정 * IF(크리티컬, 크리배율, 1)

이렇게 하면 몇 가지 큰 장점이 생긴다:
1. 수식만 봐도 무슨 계산인지 즉시 이해된다
2. 밸런스 조정할 때 계수 시트만 수정하면 모든 곳에 자동 반영
3. 이벤트 때 '골드드롭배율'만 2.0으로 바꾸면 끝
4. 신입이 와도 시트 구조를 빠르게 파악할 수 있다

주의: 이름에 공백이나 특수문자는 쓸 수 없다. '크리 배율'이 아니라 '크리배율' 또는 '크리_배율'로 써야 한다. 또한 이름은 워크북 전체에서 고유해야 하므로, 여러 시트에서 같은 이름을 다른 의미로 쓰면 충돌이 발생한다.

3. VLOOKUP: 테이블 참조의 기본

밸런스 작업의 핵심은 테이블 참조다. 몬스터 ID를 넣으면 HP가 나오고, 아이템 코드를 넣으면 가격이 나오고, 스킬 ID를 넣으면 쿨타임이 나와야 한다. VLOOKUP은 이런 '키 값으로 테이블에서 데이터 찾아오기'를 수행하는 가장 기본적인 함수다. 엑셀을 쓰는 기획자라면 누구나 알아야 하고, 실제로 밸런스 시트에서 가장 많이 등장하는 함수이기도 하다.

기본 문법:=VLOOKUP(찾을값, 범위, 열번호, FALSE)

• 찾을값: 검색할 ID나 키 (예: 몬스터ID)
• 범위: 데이터가 있는 테이블. 중요: 찾을값이 반드시 이 범위의 첫 번째 열에 있어야 함
• 열번호: 가져올 데이터가 있는 열 번호 (1부터 시작, 범위 내에서의 상대 위치)
• FALSE: 정확히 일치하는 값만 찾음. TRUE는 근사값 매칭인데, 밸런스 작업에서는 거의 항상 FALSE를 쓴다.

예시 1: 몬스터 스탯 테이블 참조

'몬스터테이블' 시트에 다음과 같은 데이터가 있다고 하자:
• A열: 몬스터ID (1001, 1002, 1003...)
• B열: 몬스터이름 (고블린, 오크, 트롤...)
• C열: HP (100, 250, 500...)
• D열: 공격력 (10, 25, 40...)
• E열: 방어력 (5, 15, 30...)

전투 시뮬레이션 시트에서 몬스터 ID를 입력하면 해당 스탯을 가져오고 싶다:

=VLOOKUP(A2, 몬스터테이블!$A:$E, 3, FALSE) → HP
=VLOOKUP(A2, 몬스터테이블!$A:$E, 4, FALSE) → 공격력
=VLOOKUP(A2, 몬스터테이블!$A:$E, 5, FALSE) → 방어력

A2에 1002를 넣으면 오크의 HP 250, 공격력 25, 방어력 15가 각각 반환된다.

주의: 열번호는 범위 내에서의 상대 위치다. 범위가 A:E면 A가 1번, B가 2번, C가 3번이다. 범위가 B:E면 B가 1번이 된다. 이 점을 혼동하면 엉뚱한 열의 값을 가져오게 된다.

중요: 범위는 열 전체로 지정하라

=VLOOKUP(A2, A:E, 3, FALSE) ← 권장
=VLOOKUP(A2, A1:E100, 3, FALSE) ← 비권장

열 전체(A:E)로 지정하면 데이터가 추가되어도 범위를 수정할 필요가 없다. A1:E100처럼 고정 범위를 쓰면 101번째 데이터가 추가될 때 수식을 전부 고쳐야 한다. 프로젝트가 진행되면서 몬스터가 계속 추가되는데, 매번 범위를 늘리는 건 실수를 유발한다.

단, 열 전체 참조는 데이터가 아주 많을 때(수만 행 이상) 약간의 성능 저하가 있을 수 있다. 하지만 게임 밸런스 테이블 규모에서는 체감하기 어려운 수준이니 신경 쓰지 않아도 된다.

VLOOKUP의 치명적 단점

찾을 값이 반드시 범위의 첫 번째 열에 있어야 한다. 왼쪽 방향 검색이 불가능하다.

예를 들어 아이템 테이블이 [ID, 이름, 가격] 순서인데, '장검'이라는 이름으로 ID를 찾고 싶다면? VLOOKUP으로는 불가능하다. 이름이 ID보다 오른쪽에 있기 때문이다. 테이블 열 순서를 바꾸면 되긴 하지만, 이미 다른 수식들이 그 테이블을 참조하고 있다면 전부 깨진다.

이 한계를 극복하려면 INDEX+MATCH 조합을 써야 한다. 다음 섹션에서 다룬다.

4. INDEX + MATCH: VLOOKUP의 한계 극복

VLOOKUP에는 치명적인 제약이 있다. 검색하려는 열이 반드시 테이블의 첫 번째 열이어야 한다는 점이다. 아이템 테이블이 [ID, 이름, 가격] 순서인데, '장검'이라는 이름으로 ID를 역참조하고 싶다면? VLOOKUP으로는 불가능하다. 테이블 열 순서를 바꾸자니, 이미 다른 수식들이 그 테이블을 참조하고 있다.

INDEX+MATCH 조합은 이 한계를 완전히 극복한다. 검색 열과 반환 열을 자유롭게 지정할 수 있고, 열 삽입/삭제에도 강하다. 처음에는 수식이 길어 보이지만, 익숙해지면 VLOOKUP보다 유연해서 이쪽을 선호하는 기획자도 많다.

기본 문법:=INDEX(반환범위, MATCH(찾을값, 검색범위, 0))

MATCH(찾을값, 검색범위, 0): 찾을값이 검색범위에서 몇 번째 행인지 반환
INDEX(반환범위, 행번호): 반환범위에서 해당 행 번호의 값을 반환

두 함수를 조합하면 '검색 열'과 '반환 열'을 완전히 분리해서 자유롭게 지정할 수 있다.

예시 1: 아이템 이름으로 ID 역참조 (왼쪽 검색)

아이템 테이블이 [A:ID, B:이름, C:가격] 순서다. 기획자가 시뮬레이션 시트에서 '장검'을 입력하면 해당 아이템의 ID를 가져오고 싶다. VLOOKUP으로는 불가능한 상황이다.

=INDEX(A:A, MATCH("장검", B:B, 0))

동작 원리:
1. MATCH("장검", B:B, 0) → B열에서 "장검"을 찾아서 행 번호 반환 (예: 5)
2. INDEX(A:A, 5) → A열의 5번째 행 값 반환 → ID 획득

검색은 B열(이름)에서 하고, 반환은 A열(ID)에서 한다. 열 순서에 구애받지 않는다.

예시 2: 스킬 테이블에서 다양한 정보 조회

스킬 테이블 구조가 [A:스킬명, B:스킬ID, C:데미지, D:쿨타임, E:마나소모]라고 하자. 스킬ID로 검색해서 쿨타임을 가져오려면:

=INDEX(D:D, MATCH(스킬ID, B:B, 0))

B열(스킬ID)에서 검색하고, D열(쿨타임)에서 값을 가져온다. 열 순서가 어떻든 상관없다. 나중에 C열과 D열 사이에 '스킬타입' 열이 추가되어도 수식을 고칠 필요가 없다 (D:D 참조이므로 자동으로 따라감).

반면 VLOOKUP은 열번호(4)를 직접 지정하므로, 열이 추가되면 수식을 전부 수정해야 한다.

INDEX+MATCH의 장점 정리

1. 검색 열이 첫 번째가 아니어도 된다 (왼쪽 검색 가능)
2. 열 삽입/삭제에 강하다 (열 범위를 직접 참조하므로)
3. 행 방향 검색도 가능하다 (MATCH의 범위를 행으로 바꾸면 됨)
4. 대용량 데이터에서 VLOOKUP보다 빠르다 (정확한 열만 탐색)

주의: MATCH의 세 번째 인수는 반드시 0으로 써야 정확히 일치하는 값을 찾는다. 1이나 -1은 정렬된 데이터에서 근사값을 찾는 옵션인데, 밸런스 작업에서는 거의 쓸 일이 없다.

5. VLOOKUP + MATCH: 동적 열 참조

캐릭터 스탯 조회 시트를 만들 때, HP 따로, 공격력 따로, 방어력 따로 VLOOKUP을 쓰면 열번호를 3, 4, 5로 일일이 지정해야 한다. 그런데 나중에 HP와 공격력 사이에 '마나' 열이 추가되면? 모든 열번호를 하나씩 밀어야 한다. 스탯이 20개면 수식 20개를 다 고쳐야 하는 것이다.

VLOOKUP과 MATCH를 조합하면 이 문제가 해결된다. 열번호를 숫자로 직접 쓰는 대신, MATCH가 헤더 행에서 스탯명의 위치를 찾아서 넘겨준다. 헤더 이름만 바꾸면 다른 스탯이 나오는 유연한 구조가 된다.

기본 문법:=VLOOKUP(ID, 범위, MATCH(스탯명, 헤더행, 0), FALSE)

MATCH가 헤더 행에서 스탯명의 위치(열 번호)를 찾아서 VLOOKUP에 넘겨준다.

예시: 캐릭터 스탯 조회 시트 만들기

'캐릭터DB' 시트에 100명의 캐릭터 데이터가 있다:
• 1행(헤더): ID, 이름, HP, 공격력, 방어력, 속도, 크리율, 크리데미지...
• 2~101행: 각 캐릭터 데이터

조회 시트에서 왼쪽에 캐릭터 ID를 넣고, 위쪽에 보고 싶은 스탯명을 넣으면 해당 값이 나오게 하고 싶다.

조회 시트 구성:
• A열: 캐릭터 ID (1001, 1002, 1003...)
• 1행: HP, 공격력, 방어력, 속도 (B1, C1, D1, E1)

B2 셀에 다음 수식을 쓴다:

=VLOOKUP($A2, 캐릭터DB!$A:$Z, MATCH(B$1, 캐릭터DB!$1:$1, 0), FALSE)

$A2: 캐릭터 ID. 열을 고정해서 오른쪽으로 복사해도 A열 참조 유지
B$1: 스탯명. 행을 고정해서 아래로 복사해도 1행 참조 유지
MATCH(B$1, 캐릭터DB!$1:$1, 0): 헤더 행에서 스탯명의 열 위치를 동적으로 찾음

이 수식을 가로세로로 복사하면, 각 캐릭터의 각 스탯이 자동으로 채워진다.

장점: '치명타저항' 같은 새 스탯 열이 캐릭터DB에 추가되어도, 조회 시트의 헤더에 '치명타저항'만 추가하면 바로 조회 가능. 수식 수정이 전혀 필요 없다.

6. IF / IFERROR: 조건 분기와 에러 처리

밸런스 수식에서 '조건에 따라 다르게 처리'하는 상황은 매우 흔하다. SSR 등급은 2배, SR은 1.5배, R은 1.2배 배율을 적용한다거나, 레벨 50 이상이면 레이드를 해금하는 식이다. IF 함수가 이 역할을 담당한다.

한편, VLOOKUP으로 테이블을 참조할 때 찾는 값이 없으면 #N/A 에러가 뜬다. 개발 중에는 데이터 누락이 흔한데, 에러 하나가 관련 계산을 전부 망가뜨린다. IFERROR는 이런 에러를 잡아서 '0' 또는 '미등록' 같은 의미 있는 값으로 바꿔준다.

기본 문법:=IF(조건, 참일때값, 거짓일때값)
=IFERROR(수식, 에러시반환값)

예시 1: 등급별 스탯 배율 적용

캐릭터 등급(N, R, SR, SSR)에 따라 기본 스탯에 다른 배율을 곱하고 싶다.

=IF(등급="SSR", 2, IF(등급="SR", 1.5, IF(등급="R", 1.2, 1)))

• SSR: 2배
• SR: 1.5배
• R: 1.2배
• 나머지(N): 1배

IF를 중첩해서 다중 조건을 처리한다. 단, 너무 많이 중첩하면 가독성이 떨어지니 5개 이상이면 별도 배율 테이블 + VLOOKUP 조합을 고려하자.

예시 2: 레벨 구간별 콘텐츠 해금

=IF(레벨>=50, "레이드", IF(레벨>=30, "던전", IF(레벨>=10, "PvP", "튜토리얼")))

레벨에 따라 해금되는 콘텐츠가 다르다. 50 이상이면 레이드, 30 이상이면 던전, 10 이상이면 PvP, 그 이하면 튜토리얼.

주의: IF 중첩 시 조건 순서에 주의해야 한다. 위 수식에서 '레벨>=10'을 먼저 체크하면, 50레벨도 PvP가 반환된다. 큰 값부터 체크해야 의도대로 동작한다.

예시 3: IFERROR로 참조 실패 처리

VLOOKUP은 찾는 값이 없으면 #N/A 에러를 반환한다. 시트에 #N/A가 하나라도 있으면 관련 계산이 전부 에러로 전파된다. IFERROR로 감싸서 깔끔하게 처리하자.

=IFERROR(VLOOKUP(A2, 몬스터테이블, 3, FALSE), 0)

몬스터 ID가 테이블에 없으면 #N/A 대신 0을 반환한다. 에러 전파를 막을 수 있다.

=IFERROR(VLOOKUP(아이템ID, 아이템테이블, 2, FALSE), "미등록")

아이템이 테이블에 없으면 "미등록"이라고 표시해서 누락된 데이터를 쉽게 발견할 수 있다.

실무 팁: 실무에서는 거의 모든 VLOOKUP/INDEX-MATCH에 IFERROR를 감싸는 게 좋다. 개발 중에는 데이터가 빠지거나 ID가 바뀌는 일이 빈번하다. IFERROR가 없으면 시트 전체가 에러 범벅이 되어 어디가 문제인지 찾기 어렵다.

7. SUMIF / COUNTIF: 조건부 집계

드롭 테이블을 설계했는데, '레어 등급 아이템의 총 드롭 확률이 얼마나 되지?' 하고 궁금해질 때가 있다. 또는 '현재 SSR 캐릭터가 몇 명이지?', '화속성 스킬의 총 데미지 합은?' 같은 질문도 자주 나온다. 이처럼 특정 조건을 만족하는 데이터만 골라서 합계나 개수를 구해야 할 때 SUMIF와 COUNTIF를 쓴다. 밸런스 검증 과정에서 없어서는 안 될 함수들이다.

기본 문법:=SUMIF(조건범위, 조건, 합계범위) — 조건을 만족하는 행의 합계
=COUNTIF(범위, 조건) — 조건을 만족하는 셀 개수

예시 1: 등급별 총 드롭 확률

드롭 테이블에서 각 등급 아이템의 총 드롭 확률을 계산하고 싶다.

아이템등급드롭확률
장검레어1%
단검일반5%
레어2%
도끼에픽0.5%

=SUMIF(등급열, "레어", 확률열) → 3% (장검 1% + 활 2%)
=SUMIF(등급열, "일반", 확률열) → 5%

드롭 테이블에 레어 아이템이 얼마나 있고, 총 드롭 확률이 어느 정도인지 빠르게 파악할 수 있다.

예시 2: 등급별 아이템 개수 집계

=COUNTIF(등급열, "SSR") → SSR 아이템이 몇 개인지
=COUNTIF(등급열, "SR") → SR 아이템이 몇 개인지

등급별 콘텐츠 수량 밸런스를 검토할 때 유용하다. SSR이 너무 적거나 많으면 희소성 밸런스가 무너진다.

다중 조건: SUMIFS / COUNTIFS

조건이 2개 이상일 때는 SUMIFS, COUNTIFS를 쓴다. 주의할 점은 인수 순서가 SUMIF와 다르다는 것.

=SUMIFS(합계범위, 조건범위1, 조건1, 조건범위2, 조건2)

예시: 화속성이면서 공격타입인 스킬의 총 데미지

=SUMIFS(데미지열, 속성열, "화", 타입열, "공격")

속성이 '화'이고 타입이 '공격'인 스킬만 골라서 데미지를 합산한다.

예시: 레벨 30 이상인 화속성 캐릭터 수

=COUNTIFS(속성열, "화", 레벨열, ">=30")

부등호 조건도 사용 가능하다. 단, 부등호와 숫자를 따옴표로 묶어야 한다.

8. SUMPRODUCT: 다중 조건 집계 + 곱하기 패턴

SUMPRODUCT는 배열끼리 곱해서 합산하는 함수다. 단순히 '수량 × 단가'의 총합을 구하는 데도 쓰지만, 진짜 힘은 '곱하기 패턴'에 있다. 엑셀에서 조건식은 TRUE=1, FALSE=0으로 처리된다. 이 성질을 이용하면 조건을 곱셈으로 표현해서 복잡한 다중 조건 집계를 한 줄로 해결할 수 있다. SUMIFS로 처리하기 어려운 범위 조건(30 이상 AND 50 이하)이나 OR 조건도 SUMPRODUCT로는 깔끔하게 처리된다.

기본 사용: 가중 합계

=SUMPRODUCT(수량범위, 단가범위)

아이템수량단가
포션10100
폭탄5200
스크롤3500

=SUMPRODUCT(B:B, C:C) → 10×100 + 5×200 + 3×500 = 3,500

인벤토리 총 가치, 상점 총 매출 등을 계산할 때 유용하다.

곱하기 패턴: 조건을 곱셈으로 표현

이게 SUMPRODUCT의 진짜 힘이다. 엑셀에서 조건식은 TRUE=1, FALSE=0으로 처리된다. 이걸 곱하면 AND 연산이 된다.

=SUMPRODUCT((조건1)*(조건2)*값범위)

원리:
• 조건1이 TRUE(1)이고 조건2도 TRUE(1)면: 1 × 1 × 값 = 값 (살아남음)
• 조건1이 FALSE(0)면: 0 × 1 × 값 = 0 (사라짐)
• 조건2가 FALSE(0)면: 1 × 0 × 값 = 0 (사라짐)

예시 1: 화속성 + 공격타입 스킬의 총 데미지

=SUMPRODUCT((속성열="화")*(타입열="공격")*데미지열)

SUMIFS와 결과는 같지만, SUMPRODUCT는 더 복잡한 조건도 처리할 수 있다.

예시 2: 레벨 30~50 사이 캐릭터의 총 전투력

=SUMPRODUCT((레벨열>=30)*(레벨열<=50)*전투력열)

범위 조건(30 이상 AND 50 이하)을 한 번에 처리한다. SUMIFS로도 가능하지만, 조건이 더 복잡해지면 SUMPRODUCT가 편하다.

예시 3: 가중 평균 계산

단순 평균이 아니라 수량에 따른 가중 평균이 필요할 때:

=SUMPRODUCT(수량범위, 단가범위) / SUM(수량범위)

아이템 A: 10개 × 100원
아이템 B: 5개 × 200원

단순 평균: (100 + 200) / 2 = 150원
가중 평균: (10×100 + 5×200) / 15 = 133.3원

수량이 더 많은 아이템이 평균에 더 큰 영향을 미친다. 드롭률 조정이나 경제 밸런스 분석에 자주 쓰인다.

주의: SUMPRODUCT에 전체 열(A:A)을 넣으면 빈 셀까지 계산해서 느려지거나 에러가 날 수 있다. 데이터가 있는 범위(A2:A100)를 명시적으로 지정하는 게 안전하다.

9. ROUND 함수들: 수치 정리

밸런스 공식을 세우면 계산 결과가 127.384 같은 소수점으로 나온다. 하지만 플레이어에게 '공격력 127.384'를 보여줄 수는 없다. 골드 보상이 127원이면 어색하고, 300원처럼 깔끔한 숫자가 훨씬 자연스럽다. 스탯도 5의 배수면 보기 좋고 비교도 쉽다. ROUND 계열 함수는 이렇게 계산된 수치를 사람이 보기 좋은 형태로 정리해준다. 반올림, 올림, 내림, 단위 맞춤까지 상황에 맞게 선택할 수 있다.

함수 종류:ROUND(값, 자릿수) — 반올림
ROUNDUP(값, 자릿수) — 올림 (무조건 위로)
ROUNDDOWN(값, 자릿수) — 내림 (무조건 아래로)
CEILING(값, 단위) — 지정 단위로 올림
FLOOR(값, 단위) — 지정 단위로 내림

자릿수 이해:• 0: 정수 (소수점 제거)
• 1: 소수점 첫째 자리까지
• 2: 소수점 둘째 자리까지
• -1: 10의 자리까지 (127 → 130)
• -2: 100의 자리까지 (127 → 100)

예시 1: 골드 보상을 100 단위로 맞추기

계산된 보상이 127골드인데, 이대로 주면 어색하다. 100단위 깔끔한 숫자로 올림하자.

=CEILING(계산된골드, 100)

127 → 200, 350 → 400, 901 → 1000

플레이어가 보기 좋은 숫자가 된다. 약간 후한 방향이라 플레이어 경험에도 긍정적이다.

예시 2: 경험치를 10 단위로 내림

반대로 약간 손해보는 방향으로 정리하고 싶다면 FLOOR.

=FLOOR(계산된경험치, 10)

127 → 120, 999 → 990

경험치 인플레이션을 억제하는 효과가 있다.

예시 3: 확률을 소수점 2자리까지

드롭 확률 0.12345를 표기할 때:

=ROUND(확률, 2) → 0.12 (12%)

확률 표기에 적합하다. 소수점이 길면 플레이어가 읽기 어렵다.

예시 4: 스탯을 5의 배수로 맞추기

스탯이 5의 배수면 보기 좋고 비교도 쉽다.

=ROUND(스탯/5, 0)*5

127 → 125, 128 → 130, 132 → 130

반올림해서 가장 가까운 5의 배수로 맞춘다.

주의: ROUND와 CEILING/FLOOR의 차이를 명확히 이해해야 한다. ROUND는 '자릿수'를 지정하고, CEILING/FLOOR는 '단위'를 지정한다. 100 단위로 올림하려면 ROUND가 아니라 CEILING을 써야 한다.

10. MIN / MAX: 스탯 캡핑

데미지 공식이 '공격력 - 방어력'이라면, 방어력이 공격력보다 높을 때 음수가 나온다. 이대로 두면 힐을 받는 셈이 되어버린다. 크리티컬 확률을 무한정 쌓으면 100%를 넘어서 200%가 될 수도 있다. 쿨타임 감소 버프를 너무 많이 받으면 쿨타임이 0이나 음수가 되어 무한 시전이 가능해진다. 밸런스에서 이런 '범위 이탈'은 심각한 문제를 일으킨다. MIN과 MAX는 값에 상한선과 하한선을 걸어서 이런 문제를 방지한다.

기본 문법:=MAX(값, 하한) — 하한 보장. 값이 하한보다 작으면 하한을 반환
=MIN(값, 상한) — 상한 제한. 값이 상한보다 크면 상한을 반환

직관과 반대라서 헷갈리기 쉽다. '최소 1을 보장'하려면 MAX를 쓴다.

예시 1: 최소 1 데미지 보장

데미지 공식이 '공격력 - 방어력'이라면, 방어력이 공격력보다 높을 때 음수가 된다.

=MAX(공격력 - 방어력, 1)

공격력 50, 방어력 100 → 계산상 -50이지만, MAX가 1을 반환
공격력 200, 방어력 100 → 100 그대로 반환

예시 2: 크리티컬 확률 80% 캡

장비, 버프, 스킬을 다 합쳐도 크리율이 80%를 넘지 못하게 하고 싶다.

=MIN(기본크리율 + 장비보너스 + 버프, 0.8)

합이 0.5면 → 0.5 그대로
합이 1.2면 → 0.8로 캡핑

과도한 스탯 쌓기로 밸런스가 붕괴되는 걸 방지한다.

예시 3: 양쪽 제한 (클램핑)

하한과 상한을 동시에 걸어야 할 때. 예를 들어 스킬 쿨타임이 최소 0.5초, 최대 10초여야 한다.

=MIN(MAX(계산된쿨타임, 0.5), 10)

계산 순서:
1. MAX(값, 0.5) → 0.5 미만이면 0.5로 올림
2. MIN(결과, 10) → 10 초과면 10으로 내림

또는 MEDIAN을 활용한 트릭:

=MEDIAN(0.5, 계산된쿨타임, 10)

세 값 중 중간값을 반환한다. 계산된쿨타임이 범위 안이면 그대로, 범위 밖이면 경계값이 반환된다. MIN+MAX보다 직관적이라 선호하는 기획자도 있다.

주의: MIN/MAX 순서를 헷갈리면 의도와 정반대 결과가 나온다. '최소 보장'에는 MAX, '최대 제한'에는 MIN. 외워두자.

11. POWER / LOG: 성장 곡선 설계

레벨업 시스템을 설계할 때, 레벨 1에서 100까지 스탯이 어떤 곡선을 그리며 성장할지 결정해야 한다. 선형 성장(레벨당 +10)은 단순하지만 밋밋하다. 실제 게임에서는 초반에 완만하고 후반에 급격히 오르는 '지수 성장'이나, 초반에 빠르고 후반에 완만해지는 '로그 성장'을 많이 쓴다.

POWER 함수는 지수 성장을, LOG 함수는 로그 성장을 만든다. 경험치 테이블, 스탯 곡선, 피로도 시스템, 과금 효율 체감 등 게임 밸런스의 핵심 곡선들이 이 두 함수로 설계된다.

POWER 기본 문법:=기본값 * POWER(성장률, 레벨-1)

레벨 1에서 기본값으로 시작하고, 레벨이 오를 때마다 성장률만큼 곱해진다.

예시 1: 레벨당 1.05배 지수 성장 (스탯)

=100 * POWER(1.05, 레벨-1)

레벨스탯
1100
10155
30432
501,147
10013,150

초반 10레벨 성장폭: 55 (100→155)
후반 10레벨 성장폭(90→100): 약 5,600

후반으로 갈수록 성장 체감이 커진다. 고레벨 콘텐츠에서 뉴비와의 격차를 만들 때 사용한다.

주의: 성장률이 너무 높으면 후반 수치가 폭발한다. 1.1배로만 해도 레벨 100에서 13,780배가 된다. 최종 스탯 범위를 먼저 정해놓고 역산해서 성장률을 결정해야 한다.

LOG 기본 문법:=LOG(값, 밑)

밑이 2면 '값이 2배가 될 때마다 1 증가'. 밑이 10이면 '값이 10배가 될 때마다 1 증가'.

예시 2: 플레이 시간에 따른 보상 체감

과도한 플레이를 억제하면서도 계속 보상은 주고 싶다.

=LOG(플레이시간+1, 2) * 100

플레이시간보상
1시간100
2시간158
4시간232
8시간316
16시간406

플레이 시간이 2배가 되어도 보상 증가폭은 점점 줄어든다. '더 하면 더 주지만, 효율은 떨어진다'는 설계.

예시 3: 과금 효율 체감

첫 결제는 효율이 좋고, 많이 결제할수록 효율이 떨어지게 하고 싶다. 소액 과금 유저에게 좋은 경험을 주면서 고래의 무한 파워를 제한한다.

주의: LOG에 0 이하 값을 넣으면 에러가 난다. LOG(플레이시간+1, 2)처럼 +1을 해서 최소값을 1로 만들어야 한다.

12. MOD: 레벨 구간, 사이클 계산

게임에는 주기적으로 반복되는 패턴이 많다. 10 스테이지마다 보스가 등장하고, 요일마다 다른 던전이 열리고, 10레벨 단위로 새로운 콘텐츠가 해금된다. 이런 '반복 사이클'을 수식으로 표현할 때 MOD(나머지 연산)가 필요하다. 스테이지 번호를 10으로 나눈 나머지가 0이면 보스전, 일차를 7로 나눈 나머지로 요일을 판별하는 식이다.

기본 문법:=MOD(값, 나누는수)

값을 나누는수로 나눈 나머지를 반환한다. MOD(7, 3) = 1, MOD(10, 5) = 0

예시 1: 10 스테이지마다 보스

=IF(MOD(스테이지, 10)=0, "보스", "일반")

10, 20, 30, 40... 스테이지는 보스전.
1~9, 11~19, 21~29... 스테이지는 일반전.

스테이지를 10으로 나눈 나머지가 0이면 보스라는 간단한 로직이다.

예시 2: 요일 던전 (7일 사이클)

게임 출시 후 일차를 기준으로 요일 던전을 열고 싶다.

=MOD(일차-1, 7)+1

일차계산결과
1일차MOD(0,7)+11 (월)
2일차MOD(1,7)+12 (화)
7일차MOD(6,7)+17 (일)
8일차MOD(7,7)+11 (월)

8일차부터 다시 1로 돌아가서 사이클이 반복된다.

예시 3: 레벨 구간 판별

10레벨 단위로 구간을 나눠서 콘텐츠를 다르게 적용하고 싶다.

방법 1: CEILING 활용
=CEILING(레벨, 10)

1~10레벨 → 10, 11~20레벨 → 20, 21~30레벨 → 30...

방법 2: INT 활용 (구간 번호로 표현)
=INT((레벨-1)/10)+1

1~10레벨 → 1구간, 11~20레벨 → 2구간, 21~30레벨 → 3구간...

용도에 따라 선택하면 된다. 구간별 보상 테이블 참조에는 구간 번호가 편하고, 표시용으로는 CEILING이 직관적이다.

주의: MOD(0, N)은 0을 반환한다. 1부터 시작하는 사이클을 만들 때는 위 예시처럼 -1하고 +1 처리를 해야 한다.

마치며

핵심 원칙 1: 하드코딩 금지

수식에 숫자를 직접 넣지 말고 별도 셀에서 참조하라. =A1*1.5 대신 =A1*크리배율처럼. 나중에 1.5를 1.8로 바꿀 때 수식 100개를 고칠 건가? 특히 여러 시트에서 같은 값을 쓴다면 이름 정의는 필수다.

핵심 원칙 2: 시트 분리

원본 데이터 / 계산 시트 / 출력용 시트를 분리하면 관리가 편하다. 원본 데이터는 건드리지 않고, 계산 시트에서 가공하고, 출력용 시트에서 보기 좋게 정리한다. 원본이 바뀌면 나머지가 자동으로 갱신되는 구조가 이상적이다.

핵심 원칙 3: 에러 처리 습관화

모든 VLOOKUP/INDEX-MATCH에 IFERROR를 감싸는 습관을 들여라. 개발 중에는 데이터 누락이 흔하다. 에러 하나가 시트 전체를 망가뜨리기 전에 방어해야 한다.

이 수식들을 조합하면 웬만한 밸런스 시뮬레이션은 엑셀만으로 가능하다. 결국 도구는 도구일 뿐, 중요한 건 '이 숫자가 왜 이 값이어야 하는가'에 대한 기획 의도다. 수식은 의도를 수치로 표현하는 수단이다.

메뉴