방어력 & 피해 감소 연산: 방어율, 원소 저항력, 피해 감소 체계를 위한 수식 설계

게임 개발에서 방어력과 피해 감소 시스템을 구현할 때 유효 체력(EHP)의 선형성을 유지하고 밸런스 붕괴를 방지하는 방어율, 원소 저항력, 피해 감소 연산 수식 설계 방안을 다룹니다.

✓ 복사 완료!
챕터수치 공식 & 밸런싱
난이도중급
상태● 검토 완료
대상 엔진
Engine Agnostic
최종 업데이트
방어력 & 피해 감소 연산: 방어율, 원소 저항력, 피해 감소 체계를 위한 수식 설계

게임 데미지 연산과 방어 수식 설계의 중요성

게임에서 방어 수치는 단순히 “피해를 줄이는 숫자”가 아닙니다. 플레이어가 장비를 교체했을 때 얼마나 단단해졌다고 느끼는지 보스전이 몇 분 동안 이어지는지 작은 공격과 큰 공격 중 어느 쪽이 더 위협적인지를 모두 좌우합니다. 공식이 조금만 어긋나도 방어력을 올릴수록 캐릭터가 지나치게 강해지거나 반대로 스탯을 투자해도 체감이 거의 없는 구간이 생깁니다.

대다수 RPG와 액션 게임은 방어력(Armor), 속성 저항력(Elemental Resistance), 퍼센트 피해 감소(Percentage Damage Reduction), 고정 피해 차감(Flat Damage Reduction)을 여러 층으로 나누어 사용합니다. 중요한 것은 요소를 많이 넣는 일이 아니라 각 요소의 역할과 적용 순서를 먼저 정의하는 일입니다. 예를 들어 방어력은 모든 물리 피해에 대응하고 화염 저항은 화염 피해에만 대응하며 고정 차감은 연타 공격에 강하게 작동하도록 설계할 수 있습니다.

데미지 연산 파이프라인과 방어율 피해 감소 적용 순서

이 글에서는 특정 게임의 정답을 제시하기보다 여러 장르에 재사용하기 쉬운 기준 모델을 정리합니다. 실제 프로젝트에서는 피해 타입, 관통, 치명타, 보호막, 무적 시간 같은 규칙을 이 모델에 추가하되 추가된 규칙이 어느 단계에 속하는지 명확히 기록하는 것이 좋습니다.

방어력과 방어율 공식: 유효 체력(EHP)의 선형성

방어력 수치가 커질수록 피해 감소율이 무한히 100%에 가까워지도록 만드는 대표적인 공식은 다음과 같습니다.

DRarmor=ArmorArmor+KDR_{armor} = \frac{Armor}{Armor + K}

여기서 KK는 방어율이 50%가 되는 기준점입니다. K=100K = 100이라면 방어력 100에서 50%, 300에서 75%, 900에서 약 90%의 피해 감소가 적용됩니다. 방어율만 보면 증가 폭이 점점 작아지지만 이 공식에는 밸런스 설계에 유용한 성질이 있습니다.

실제 체력이 HPHP일 때 방어력까지 고려한 유효 체력(Effective Hit Points, EHP)은 다음과 같습니다.

EHP=HP1−DRarmor=HP×(1+ArmorK)EHP = \frac{HP}{1 - DR_{armor}} = HP \times \left(1 + \frac{Armor}{K}\right)

즉, 방어력이 KK만큼 증가할 때마다 EHP가 기본 체력의 100%100\%만큼 늘어납니다. 방어율은 체감상 덜 오르는 것처럼 보여도, 생존 가능 시간이라는 관점에서는 같은 방어력 투자에 같은 효과가 돌아갑니다. 이 특성은 “방어력 100을 더 얻었을 때 언제나 비슷한 가치가 있어야 한다”는 게임에 특히 잘 맞습니다.

반대로 방어율을 단순히 Armor / 100처럼 계산하면 100을 넘는 순간 음수 피해가 되거나 상한을 별도로 넣어야 합니다. 상한만으로 문제를 막을 수는 있지만 상한 직전과 직후의 체감이 급격히 달라지는 절벽이 생기기 쉽습니다.

public class ArmorCalculator
{
    public static float CalculateArmorDR(float armor, float kConstant = 100f)
    {
        if (armor <= 0f) return 0f;
        return armor / (armor + kConstant);
    }
}

방어력 수치만 보지 말고 EHP로 비교하기

예를 들어 체력 1,000, K=100K = 100인 캐릭터를 생각해 보겠습니다.

방어력방어율물리 피해에 대한 EHP
00%1,000
10050%2,000
200약 66.7%3,000
500약 83.3%6,000

방어력 0에서 100으로 올리면 방어율은 50%p 증가하지만 500에서 600으로 올릴 때는 방어율이 약 3.8%p만 증가합니다. 그런데 두 경우 모두 EHP는 1,000씩 증가합니다. 기획자가 방어율의 큰 숫자를 보고 후반 방어력의 효율을 과소평가하지 않도록 장비 비교와 밸런스 시트에서는 EHP와 생존 시간도 함께 확인해야 합니다.

원소 저항력과 중첩 방식

원소 저항력은 화염, 냉기, 번개처럼 특정 속성의 피해만 줄이는 별도 레이어입니다. 여러 아이템과 버프의 저항을 합연산으로 처리하면 60%와 50%만으로도 110%가 됩니다. 이때 피해가 음수가 되거나 임의의 상한에 걸리면서 아이템 가치가 갑자기 사라지는 문제가 발생합니다.

저항 소스를 곱연산으로 결합하면 다음과 같이 계산할 수 있습니다.

DRtotal=1−∏i=1n(1−DRi)DR_{total} = 1 - \prod_{i=1}^{n} (1 - DR_i)

20% 저항 버프와 30% 장비 저항을 함께 적용하면 최종 저항은 50%가 아니라 다음과 같습니다.

1−(1−0.2)×(1−0.3)=0.441 - (1 - 0.2) \times (1 - 0.3) = 0.44

최종 저항은 44%입니다. 곱연산은 100%를 자연스럽게 넘지 않는다는 장점이 있지만 플레이어에게는 “20%와 30%를 얻었는데 왜 50%가 아니지?”라는 의문을 만들 수 있습니다. 따라서 UI에는 소스별 수치를 보여 주되 최종 적용 저항과 최대 상한을 함께 표시하는 편이 이해하기 쉽습니다.

방어력 상승에 따른 유효 체력 EHP의 선형 증가 및 방어율 곡선 그래프

최종 피해 감소 스태킹과 연산 순서

같은 감소 요소라도 순서가 달라지면 결과가 달라집니다. 프로젝트 초기에 아래와 같은 기본 파이프라인을 문서화해 두면 기획자와 프로그래머 사이의 해석 차이를 줄일 수 있습니다.

  1. 원시 피해량과 피해 타입 판정
  2. 방어력 기반 방어율 적용
  3. 해당 피해 타입의 원소 저항력 적용
  4. 퍼센트 피해 감소 버프와 특성 적용
  5. 고정 피해 차감
  6. 최소 피해량, 반올림, 치명타 후처리 등 최종 규칙 적용

이를 하나의 식으로 단순화하면 다음과 같습니다.

Damagefinal=max⁡(Damageraw×(1−DRarmor)×(1−DRelem)×∏i(1−DRpercent,i)−Flatreduction,1)Damage_{final} = \max\left(Damage_{raw} \times (1 - DR_{armor}) \times (1 - DR_{elem}) \times \prod_i(1 - DR_{percent,i}) - Flat_{reduction}, 1\right)

예를 들어 원시 피해 500, 방어력 100, 원소 저항 20%, 추가 피해 감소 10%, 고정 차감 15라면 다음 순서로 계산됩니다.

500×0.5×0.8×0.9−15=165500 \times 0.5 \times 0.8 \times 0.9 - 15 = 165

고정 차감을 퍼센트 감소 뒤에 두면 작은 연타 공격에 더 강하게 작동합니다. 이것이 의도라면 좋은 선택이지만 모든 공격에 동일한 고정 차감을 적용할 경우 1 피해짜리 다단 히트가 사실상 무효화될 수 있습니다. 연타 공격이 많은 게임에서는 고정 차감을 타격당이 아니라 공격 묶음당 한 번 적용하거나 피해량에 따른 최소 차감 비율을 두는 방식도 검토할 수 있습니다.

구현할 때 자주 놓치는 예외

공식보다 어려운 부분은 경계값과 규칙 충돌입니다. 다음 항목은 코드와 기획 문서에서 명시적으로 다루는 것이 좋습니다.

  • 방어력과 저항력은 음수가 될 수 있는가? 음수 방어력을 허용한다면 별도의 취약성 규칙이 필요합니다.
  • 저항력 상한은 어디에 적용하는가? 소스별 상한인지 모든 소스를 합친 뒤의 최종 상한인지 결과가 다릅니다.
  • 관통은 방어력에서 먼저 차감하는가, 방어율 계산 뒤에 적용하는가? 일반적으로는 방어율 계산 전에 방어력을 수정하는 편이 설명하기 쉽습니다.
  • 반올림은 각 단계마다 하는가, 마지막에 한 번만 하는가? 단계별 반올림은 연타 피해에서 누적 오차를 만듭니다.
  • 보호막은 방어력 적용 전후 어느 시점에 소모하는가? 피해 이벤트 로그에도 같은 순서를 반영해야 디버깅이 가능합니다.

가능하면 내부 계산은 정수로 급하게 반올림하지 말고 부동소수점으로 끝까지 진행한 뒤, 최종 표시나 체력 차감 직전에만 반올림합니다. 또한 피해 계산 함수가 결과만 반환하지 않고 적용된 레이어와 중간값을 선택적으로 기록하도록 만들면 밸런스 테스트가 훨씬 수월합니다.

interface DamageInput {
  rawDamage: number;
  isElemental: boolean;
  armor: number;
  elementalResist: number; // 0.0 ~ 1.0
  percentReductions: number[]; // e.g. [0.1, 0.15]
  flatReduction: number;
}

export function calculateFinalDamage(input: DamageInput): number {
  const armorK = 100;
  const armorDR = input.armor > 0 ? input.armor / (input.armor + armorK) : 0;

  let currentDamage = input.rawDamage * (1 - armorDR);

  if (input.isElemental) {
    const clampedResist = Math.min(Math.max(input.elementalResist, 0), 0.75);
    currentDamage *= 1 - clampedResist;
  }

  for (const dr of input.percentReductions) {
    const clampedReduction = Math.min(Math.max(dr, 0), 1);
    currentDamage *= 1 - clampedReduction;
  }

  currentDamage = Math.max(currentDamage - input.flatReduction, 1);
  return Math.round(currentDamage);
}

테스트 케이스로 밸런스를 확인하는 방법

수식이 맞는지 확인할 때는 평균적인 전투 상황만 테스트해서는 부족합니다. 최소한 다음 경계값은 자동 테스트로 고정해 두는 편이 안전합니다.

  • 방어력 0에서 원시 피해가 그대로 통과하는지
  • 방어력이 KK일 때 정확히 50% 감소하는지
  • 저항력이 0%, 상한, 상한 초과일 때 결과가 의도대로 제한되는지
  • 퍼센트 감소가 여러 개일 때 합연산이 아니라 곱연산으로 적용되는지
  • 고정 차감이 피해량을 초과해도 최소 피해 1이 보장되는지
  • 아주 큰 수치에서 오버플로와 정밀도 문제가 없는지

여기에 실제 전투 로그를 이용한 회귀 테스트를 더하면 좋습니다. 특히 장비를 하나씩 교체했을 때 EHP가 급격히 튀지 않는지 같은 피해를 물리와 속성 공격으로 받았을 때 설명 문구와 결과가 일치하는지를 함께 확인해야 합니다.

게임 밸런스에 맞는 방어 수식 선택 가이드

공식을 선택할 때는 “가장 복잡하고 정교한가”보다 “플레이어가 결과를 예측할 수 있는가”를 우선하는 것이 좋습니다. 다음 질문에 답할 수 있다면 기본 설계는 안정적인 편입니다.

  • 방어력 100을 추가했을 때 실제 생존 시간이 얼마나 늘어나는가?
  • 방어력, 원소 저항, 퍼센트 감소가 서로 같은 역할을 중복해서 수행하고 있지는 않은가?
  • 저항 상한과 최소 피해량이 특정 조합에서 무적 상태를 만들지는 않는가?
  • 연타 공격과 강한 단발 공격 중 어느 쪽을 고정 차감이 더 유리하게 만드는가?
  • 전투 로그와 UI에서 플레이어가 최종 피해량을 역산할 수 있는가?

자주 묻는 질문(FAQ)

방어율을 합연산으로 더하면 안 되나요?

방어력에서 계산된 방어율과 버프 방어율을 무조건 합연산할 수 없는 것은 아닙니다. 다만 합계가 100%에 도달할 가능성이 있다면 상한, 우선순위, 예외 처리를 모두 정의해야 합니다. 일반적인 피해 감소 버프는 곱연산으로 쌓고 최종 결과에 상한을 두는 방식이 안전합니다.

저항력 75% 상한은 꼭 필요한가요?

필수는 아니지만 대부분의 장기 성장형 게임에서는 강력한 안전장치가 됩니다. 상한을 75%로 할지 85%로 할지는 콘텐츠의 피해량과 회복 수단에 따라 달라지므로 수식만 보고 결정하기보다 최종 EHP와 보스전 시간을 함께 시뮬레이션해야 합니다.

관통과 방어력 감소는 어디에 넣어야 하나요?

보통은 방어율을 계산하기 전에 방어력 값을 먼저 수정합니다. 예를 들어 유효 방어력 = 기본 방어력 - 고정 관통을 계산한 다음 방어율 공식에 넣는 방식입니다. 퍼센트 관통과 고정 관통의 순서 역시 결과가 달라지므로 별도 규칙으로 고정해 두어야 합니다.

왜 마지막 피해를 최소 1로 제한하나요?

방어 수단이 겹쳤을 때 전투가 영원히 끝나지 않는 상황을 막기 위해서입니다. 다만 보스나 특정 공격에 최소 피해를 무시하는 규칙을 둘 수도 있으므로 항상 1을 코드에 하드코딩하기보다 피해 태그에 따라 선택할 수 있게 설계하는 편이 확장에 유리합니다.

단계별로 반올림하면 더 이해하기 쉽지 않나요?

표시용 숫자에는 단계별 반올림이 도움이 될 수 있지만 실제 체력 차감까지 반올림하면 작은 피해가 반복될 때 오차가 커집니다. 내부 계산은 정밀도를 유지하고 최종 결과만 반올림하는 방식을 권장합니다.

결론

방어 수식 설계의 핵심은 방어율 자체의 모양보다 플레이어의 투자와 실제 생존 경험이 어떻게 연결되는지 확인하는 데 있습니다. Armor/(Armor+K)Armor / (Armor + K) 형태의 공식은 방어율의 상한과 EHP의 선형성을 동시에 다루기 좋고 여러 피해 감소 요소는 곱연산과 명확한 상한을 사용하면 예측하기 쉬운 구조가 됩니다.

다만 좋은 공식도 연산 순서가 불분명하거나 예외 규칙이 코드 곳곳에 흩어져 있으면 금방 유지보수하기 어려워집니다. 방어력, 저항력, 관통, 고정 차감, 최소 피해량을 각각 하나의 레이어로 정의하고 대표 전투 로그와 경계값 테스트로 결과를 검증해 보세요. 그렇게 만든 수식은 콘텐츠가 늘어나도 설명 가능하고 플레이어에게도 납득할 수 있는 성장 체계를 제공하는 기반이 됩니다.