턴제 RPG의 속도(SPD)와 행동 게이지 계산 공식 모음

턴제 RPG의 속도(SPD)와 행동 게이지 계산 공식 모음

속도 스탯을 실제 행동 순서와 행동 게이지로 변환하는 대표 공식을 정리합니다. 고정 턴 순서, 연속 시간 게이지, 초기 보정과 밸런싱까지 Unity/C# 예제로 설명합니다.

문제를 먼저 정의하기

턴제 RPG에서 SPD(속도)는 보통 두 가지 질문에 답해야 합니다.

  1. 다음 행동은 누가 먼저 하는가?
  2. 시간이 계속 흐를 때 각 유닛은 얼마나 자주 행동하는가?

첫 번째만 필요하면 단순 정렬로 충분합니다. 반면 재행동, 버프·디버프, 행동 지연, 게이지 밀기 같은 기능이 필요하다면 행동 게이지(Action Gauge) 또는 다음 행동 시각(Next Action Time) 모델이 관리하기 쉽습니다.

속도 수치가 서로 다른 세 유닛의 행동 게이지가 시간에 따라 차오르는 모습

1. 가장 단순한 공식: SPD 내림차순 정렬

라운드가 시작할 때 모든 유닛을 SPD가 높은 순서로 정렬합니다.

순서:sort(SPD,descending)\text{순서}: \text{sort}(\text{SPD}, \text{descending})

예를 들어 A=120, B=100, C=80이면 한 라운드의 순서는 항상 A → B → C입니다. 구현과 디버깅은 쉽지만 행동 한 번마다 시간이 흐르는 느낌이나 속도 차이에 따른 추가 행동을 표현하기 어렵습니다.

동률 처리

SPD가 같을 때 결과가 매번 바뀌면 플레이어가 규칙을 예측하기 어렵습니다. 다음처럼 명시적인 보조 규칙을 둡니다.

  • 우선순위 스탯이 높은 유닛
  • 전투 시작 시 정해진 난수 시드
  • 파티/적 배치 순서 또는 고유 ID

난수를 쓴다면 전투 중에는 같은 시드를 유지해 재현 가능한 결과를 만드는 편이 테스트에 유리합니다.

2. 연속 행동 게이지: gauge += SPD × dt

실시간으로 흐르는 내부 시간 dt마다 게이지를 채우고 임계값 T에 도달한 유닛이 행동합니다.

Gnew=Gold+SPD×dtG_{new} = G_{old} + SPD \times dt 행동 조건:GT\text{행동 조건}: G \ge T

행동 후에는 보통 임계값만큼 차감합니다.

GGTG \leftarrow G - T

임계값이 10000이고 SPD가 100이라면 행동 1회까지 필요한 시간은 100초입니다. SPD가 200이면 50초이므로 장기적으로 두 배 자주 행동합니다.

행동 주기와 행동 빈도

이 모델에서 한 번 행동하는 데 필요한 시간은 다음과 같습니다.

행동 주기:TSPD\text{행동 주기}: \frac{T}{SPD} 행동 빈도:SPDT\text{행동 빈도}: \frac{SPD}{T}

따라서 SPD는 곧 행동 빈도에 선형 비례합니다. SPD 100에서 110으로 올리면 행동 횟수도 장기적으로 약 10% 증가합니다.

flowchart LR
    A[시간 경과 dt] --> B[각 유닛: Gauge += SPD × dt]
    B --> C{Gauge ≥ Threshold?}
    C -- 아니오 --> A
    C -- 예 --> D[행동 선택 및 실행]
    D --> E[Gauge -= Threshold]
    E --> A

3. 프레임 의존성을 피하는 Unity C# 예제

Update()에서 게이지를 누적할 수 있지만 한 프레임에 여러 유닛이 임계값을 넘거나 매우 높은 SPD가 여러 행동을 만들 수 있습니다. 따라서 행동 가능한 유닛을 반복 처리해야 합니다.

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Combatant
{
    public string Name;
    public float Speed;
    public float Gauge;

    public Combatant(string name, float speed)
    {
        Name = name;
        Speed = speed;
    }
}

public class ActionGaugeBattle : MonoBehaviour
{
    [SerializeField] private float threshold = 10000f;
    private readonly List<Combatant> units = new();

    private void Start()
    {
        units.Add(new Combatant("Knight", 100f));
        units.Add(new Combatant("Rogue", 140f));
        units.Add(new Combatant("Slime", 80f));
    }

    private void Update()
    {
        float dt = Time.deltaTime;

        foreach (Combatant unit in units)
            unit.Gauge += unit.Speed * dt;

        while (true)
        {
            Combatant next = units
                .Where(unit => unit.Gauge >= threshold)
                .OrderByDescending(unit => unit.Gauge)
                .ThenBy(unit => unit.Name)
                .FirstOrDefault();

            if (next == null)
                break;

            next.Gauge -= threshold;
            TakeAction(next);
        }
    }

    private void TakeAction(Combatant unit)
    {
        Debug.Log($"{unit.Name} acts. Remaining gauge: {unit.Gauge:F1}");
    }
}

이 방식에서 Gauge = 0으로 초기화하지 않고 Gauge -= threshold를 사용하는 이유는 초과분을 보존하기 위해서입니다. 예를 들어 게이지가 10300일 때 행동하면 300이 남습니다. 이를 버리면 프레임 길이나 처리 시점에 따라 실제 행동 빈도가 왜곡될 수 있습니다.

4. 더 정확한 방식: 다음 행동 시각 계산

게이지를 매 프레임 누적하지 않고 각 유닛의 다음 행동 시각을 계산할 수도 있습니다. 현재 전투 시간이 t이고 남은 게이지가 T - G일 때:

nextTime=t+TGSPDnextTime = t + \frac{T - G}{SPD}

가장 작은 nextTime을 가진 유닛이 다음 행동자입니다. 해당 시각으로 전투 시간을 점프한 뒤 그 유닛을 행동시키고 다음 행동 시각을 다시 계산합니다.

public sealed class TimelineUnit
{
    public string Name;
    public float Speed;
    public double NextActionTime;
}

public static TimelineUnit GetNextUnit(List<TimelineUnit> units)
{
    return units
        .OrderBy(unit => unit.NextActionTime)
        .ThenBy(unit => unit.Name)
        .First();
}

public static void ScheduleNextAction(
    TimelineUnit unit,
    double currentTime,
    float threshold)
{
    unit.NextActionTime = currentTime + threshold / unit.Speed;
}

이 모델은 전투가 애니메이션 완료를 기다리는 동안에도 내부 턴 순서를 빠르게 계산하기 좋습니다. 유닛 수가 많다면 NextActionTime을 최소 힙(priority queue)으로 관리해 다음 행동자를 효율적으로 꺼낼 수 있습니다.

5. 행동 지연과 게이지 조작 공식

행동 게이지 방식은 상태 이상과 스킬 효과를 수식으로 자연스럽게 연결할 수 있습니다.

게이지 직접 증감

행동을 당기거나 밀 때는 현재 게이지에 값을 더하거나 뺍니다.

Gclamp(G+ΔG,0,T)G \leftarrow clamp(G + \Delta G, 0, T)
  • +2000: 행동에 가까워짐
  • -1500: 행동이 지연됨

즉시 행동을 허용하지 않으려면 최대값을 T - ε로 제한합니다. 반대로 즉시 행동 스킬이라면 G = T로 설정한 뒤 행동 대기열을 즉시 다시 평가합니다.

속도 버프와 디버프

기본 속도를 SPD_base, 속도 보정률을 r이라 하면:

SPDfinal=SPDbase×(1+r)SPD_{final} = SPD_{base} \times (1 + r)

예를 들어 SPD 120에 25% 버프가 적용되면 최종 SPD는 150입니다. 여러 퍼센트 효과를 합연산으로 처리할지 곱연산으로 처리할지는 게임 규칙으로 정해야 합니다.

합연산:SPDbase×(1+r1+r2)합연산: SPD_{base} \times (1 + r_1 + r_2) 곱연산:SPDbase×(1+r1)×(1+r2)곱연산: SPD_{base} \times (1 + r_1) \times (1 + r_2)

합연산은 예측하기 쉽고 곱연산은 중첩 효과가 더 빠르게 커집니다. 일반적인 RPG에서는 합연산 또는 보정률 상한을 함께 두는 편이 밸런싱하기 편합니다.

6. 선제 행동과 초기 게이지

전투 시작부터 SPD가 높은 유닛이 먼저 행동하게 하려면 초기 게이지를 속도 비례로 설정할 수 있습니다.

Ginitial=T×SPDSPDmaxG_{initial} = T \times \frac{SPD}{SPD_{max}}

다만 이 공식은 최고 속도 유닛의 게이지가 곧바로 임계값에 닿을 수 있습니다. 첫 행동을 즉시 발생시키고 싶지 않다면 다음처럼 여유를 둡니다.

Ginitial=(Tmargin)×SPDSPDmaxG_{initial} = (T - margin) \times \frac{SPD}{SPD_{max}}

초기 게이지는 첫 턴의 체감에만 큰 영향을 주고 충분히 긴 전투의 행동 비율은 결국 최종 SPD가 결정합니다. 따라서 선제권을 강하게 만들지, 장기적인 속도 우위만 만들지는 별도 설계 항목으로 판단해야 합니다.

7. 밸런싱할 때 확인할 값

속도는 공격력처럼 한 번의 수치만 바꾸는 스탯이 아닙니다. 행동 횟수를 통해 피해, 회복, 상태 이상 적용, 자원 회복까지 함께 증가시킬 수 있습니다.

  • 기준 SPD를 정한다. 예: 일반 유닛 100.
  • 비교 구간을 정한다. 예: SPD 80~160.
  • 동일한 전투 시간에서 각 SPD의 예상 행동 횟수를 계산한다.
  • 속도 버프, 추가 턴, 게이지 증가 효과를 함께 적용해 본다.
  • 매우 높은 SPD가 한 번에 연속 행동할 수 있는지 확인한다.

전투 시간 D 동안의 기대 행동 횟수는 단순화하면 다음과 같습니다.

예상행동횟수:D×SPDT예상 행동 횟수: \frac{D \times SPD}{T}

예를 들어 D=300초, T=10000, SPD 100이면 약 3회, SPD 150이면 약 4.5회입니다. 실제 전투에서는 초기 게이지, 행동 중 시간 정지 여부, 게이지 조작 효과 때문에 오차가 생길 수 있으므로 이 값은 밸런스 검토용 기준선으로 사용하면 좋습니다.

마무리

고정 턴 순서는 단순한 게임에 적합하고 Gauge += SPD × dt는 직관적인 실시간 행동 게이지를 제공합니다. 더 복잡한 전투나 많은 유닛을 다룬다면 nextTime = currentTime + remainingGauge / SPD 형태의 타임라인 모델이 안정적입니다.

어떤 공식을 택하든 핵심은 동일합니다. SPD가 행동 빈도에 어떤 비율로 연결되는지 초과 게이지와 동률을 어떻게 처리하는지 게이지 조작이 즉시 행동을 만들 수 있는지를 명확한 규칙으로 고정해야 전투가 예측 가능하고 디버깅하기 쉬워집니다.

#게임 수학#턴제 RPG#Unity#C##게임 밸런싱

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs