무기 진화 시스템 설계하기 — 조건부 강화와 랜덤 선택지 로직

개발 노트 — 게임 시스템 설계

진화 시스템이 필요했던 이유

뱀파이어 서바이벌류 장르에서 “진화(evolution)”는 핵심 재미 요소 중 하나입니다. 처음엔 평범했던 무기가 특정 조건을 채우면 완전히 다른 형태로 강화되는 순간이, 플레이어에게 성취감을 줍니다.

이 글에서는 이런 진화 시스템을 설계할 때 고려했던 두 가지 축 — **”어떤 조건으로 진화시킬 것인가”**와 **”진화 선택지를 어떻게 뽑을 것인가”**를 정리합니다.

설계 축 1: 무기-패시브 1:1 매칭 구조

가장 먼저 정한 규칙은 “무기 + 전용 패시브 = 진화”라는 1:1 매칭 구조였습니다.

회전도끼(최대 레벨) + 도끼 강화서(보유) → 회전도끼 진화형
단검(최대 레벨)     + 단검 강화서(보유) → 단검 진화형
쌍검(최대 레벨)     + 쌍검 강화서(보유) → 쌍검 진화형

각 무기마다 정확히 하나씩 대응하는 패시브가 있고, “무기 최대 레벨 도달 + 대응 패시브 보유(레벨 무관)”라는 조건을 만족하면 진화 선택지가 등장하는 구조입니다.

이 규칙을 코드로 표현하면 이렇게 정리할 수 있습니다.

public class EvolutionRule
{
    public WeaponType weapon;
    public PassiveType requiredPassive;
    public WeaponType evolvedForm;
}

public bool CanEvolve(Player player, EvolutionRule rule)
{
    bool weaponMaxed = player.GetWeaponLevel(rule.weapon) >= WeaponMaxLevel;
    bool hasPassive = player.HasPassive(rule.requiredPassive);
    return weaponMaxed && hasPassive;
}

왜 1:1 매칭으로 단순화했을까

무기와 패시브를 자유롭게 조합해서 진화시키는 방식(예: 여러 패시브 조합에 따라 다른 결과)도 고려할 수 있지만, 저희는 명확성을 우선했습니다. “이 무기를 진화시키려면 이 패시브만 모으면 된다”는 규칙이 단순할수록, 플레이어가 별도 검색 없이도 게임 안에서 직관적으로 학습할 수 있습니다. 복잡한 조합 시스템은 재미있을 수 있지만, 그만큼 플레이어가 “뭘 해야 할지 모르겠다”는 상태에 빠지기 쉽습니다.

설계 축 2: 레벨업 시 선택지 랜덤 뽑기

무기/패시브/진화 후보 중 일부를 랜덤으로 뽑아 플레이어에게 선택지로 제시하는 로직도 중요한 부분입니다. 여기서 흔히 놓치는 두 가지 함정이 있습니다.

함정 1: 완전 랜덤은 플레이어를 불쾌하게 만든다

단순히 Random.Range()로 아무 무기나 뽑으면, 이미 최대 레벨을 찍은 무기가 다시 선택지로 나오는 등 불필요한 중복이 발생할 수 있습니다. 이를 막으려면 보유 여부와 레벨 상태를 먼저 필터링해야 합니다.

List<Item> candidates = allItems
    .Where(item => !player.IsMaxLevel(item))
    .ToList();

함정 2: 셔플 후보가 부족하면 무한 루프에 빠질 수 있다

후보 목록에서 매번 무작위로 몇 개를 뽑아 보여주는 로직을 짤 때, 후보 수가 요구 개수보다 적은 예외 상황(예: 플레이어가 이미 대부분의 아이템을 최대 레벨로 찍은 경우)을 놓치면 무한 루프에 빠질 위험이 있습니다.

해결책: Fisher-Yates 셔플로 후보를 섞고, 안전장치로 반복 횟수 상한(safety 카운터)을 둡니다.

public List<Item> PickChoices(List<Item> candidates, int count)
{
    // Fisher-Yates 셔플
    for (int i = candidates.Count - 1; i > 0; i--)
    {
        int j = Random.Range(0, i + 1);
        (candidates[i], candidates[j]) = (candidates[j], candidates[i]);
    }

    int safety = 0;
    List<Item> result = new List<Item>();

    while (result.Count < count && safety < candidates.Count * 2)
    {
        if (candidates.Count == 0) break;

        var pick = candidates[result.Count % candidates.Count];
        if (!result.Contains(pick))
        {
            result.Add(pick);
        }
        safety++;
    }

    return result;
}

safety 카운터는 “이 정도 반복했는데도 조건을 못 채우면 그냥 멈춘다”는 방어 로직입니다. 후보가 부족한 극단적인 상황(플레이 후반, 대부분의 아이템을 이미 최대 레벨로 찍은 상태)에서도 게임이 멈추지 않고 안전하게 동작하도록 해줍니다.

설계하면서 발견한 아쉬운 점

시스템을 다 만들고 나서 돌아보니, 개선하면 좋을 지점도 보였습니다.

모든 패시브가 진화 재료로만 쓰인다는 점입니다. 패시브 7종이 전부 특정 무기의 진화 조건으로만 기능하다 보니, “진화와 무관하게 순수 스탯만 올려주는 패시브”가 없습니다. 그 결과 플레이어의 선택이 “이번 판엔 어떤 무기를 진화시킬까”로 어느 정도 정형화되는 경향이 있었습니다.

만약 다시 설계한다면, 진화 재료 역할을 하지 않는 순수 스탯형 패시브(예: 전체 공격력 증가, 이동속도 증가 등)를 몇 종 추가해서, 진화 조합 외에도 다양한 빌드가 나올 수 있게 여지를 두고 싶습니다.

정리

진화 시스템을 설계할 때 고려했던 두 축을 정리하면:

  1. 조건 설계는 단순하게: 무기-패시브 1:1 매칭처럼 플레이어가 직관적으로 학습할 수 있는 규칙을 우선
  2. 선택지 로직은 꼼꼼하게: 랜덤 뽑기 로직에는 필터링 + 셔플 + 안전장치(무한 루프 방지)까지 챙겨야 실제 플레이에서 안전하게 동작

성장/진화 시스템이 있는 게임을 만드신다면, “무엇으로 진화시킬지”라는 기획적 결정만큼이나 “선택지를 어떻게 안전하게 뽑을지”라는 구현 디테일도 함께 신경 쓰시길 추천합니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤