스킬 입력 및 실행 진입 구조

목차

1. 시스템 요구 사항

스킬 시스템을 설계하면서 가장 먼저 고민한 것은 스킬을 어떻게 실행할 것인가보다, 스킬을 실행하면 안 되는 상황에서 로직이 어디까지 관여하고 있는가였다.

일반적인 스킬 입력 구조에서는 키 입력이 들어오면 곧바로 스킬별 조건 검사로 진입하게 된다.

하지만 이 방식은 무기가 없는 상태나 전투가 불가능한 맵에서도 불필요하게 스킬 로직이 평가되고, 시스템의 전제 조건이 코드 곳곳에 흩어질 가능성이 높다.

무기가 없는 상태이거나 마을이나 학교 같은 비전투 지역, 이미 다른 스킬 애니메이션이 재생 중인 상황에서는 스킬 실행이 불가능한 상태가 아니라 스킬 로직 자체가 개입할 이유가 없는 상태로 정의했다.

따라서 스킬 시스템은 '입력 → 조건 검사 → 실행' 의 흐름이 아니라, 환경과 상태를 먼저 필터링한 뒤에만 스킬 로직이 의미를 갖도록 하는 구조가 필요했다.

2. 설계  목표

- 전투 불가능한 상황에서는 스킬 로직이 아예 평가되지 않도록 할 것

- 스킬 입력 처리를 단일 진입점으로 통제할 것

- 입력 코드만 보아도 전체 스킬 맵핑 구조가 드러나도록 할 것

- 실행 조건 판단과 실제 스킬 동작을 명확히 분리할 것

3. 흐름도

이 흐름에서 중요한 점은, 상단 조건을 통과하지 못하면 하단 로직은 아예 실행되지 않는다는 점이다.

즉, 스킬 입력 처리는 조건 분기가 아니라 진입 필터로 동작한다.

이 구조를 통해 스킬 시스템은 입력 처리가 아니라 상태 기반 실행 시스템으로 동작한다.

4. 구현

4.1. 스킬 식별 기준 정의
private enum SkillID
{
    FireBall,
    Explosion,
    Blaze,
    Meteor
}

스킬은 애니메이션 이름이나 이펙트 이름이 아니라, 명확한 식별자(SkillID)를 기준으로 정의한다.

이 구조를 사용하면 스킬은 이펙트나 애니메이션 이전에 하나의 행동 단위로 먼저 존재하게 된다.

SkillID는 이후 애니메이션 파라미터, 쿨타임, 데이터 테이블, 스킬 실행 로직과 연결되는 모든 기준점이 된다.

이 방식은 문자열 하드코딩에 의존하는 구조보다 훨씬 안전하며, 스킬 개수가 늘어날수록 그 차이가 크게 드러난다.

4.2. 스킬 입력 단일 진입점 및 SkillID 기반 선택
public void HandleSkill()
{
    if (EquipWeaponAtHand.instance == null ||
        !EquipWeaponAtHand.instance.HasWeapon())
        return;

    if (gameManager.CurrentMap == GameManager.Map.Village ||
        gameManager.CurrentMap == GameManager.Map.MagicSchool)
        return;

    if (Input.GetKey(KeyCode.F) &&
        !anim.GetBool(SkillAnimHash.Explosion) &&
        !anim.GetBool(SkillAnimHash.Blaze) &&
        !anim.GetBool(SkillAnimHash.Meteor) &&
        playerManager.PlayerSkills[0].IsAcquisition)
    {
        if (fireball != null && fireball.TryStartCast(playerManager))
        {
            StartSkill(SkillID.FireBall);
            fireball.UseSkill();
        }
    }

    // 이하 동일 패턴
}

HandleSkill은 스킬 시스템 전체에서 스킬 입력을 처리하는 단일 진입 함수다.

이 함수는 어떤 스킬을 실행할 것인가를 결정하기 전에, 지금 스킬을 논의할 상황인가를 먼저 판단한다.

무기 장착 여부와 맵 상태는 스킬의 종류와 상관없이 항상 먼저 만족되어야 하는 전제 조건이기 때문에 함수의 최상단에서 return 기반으로 차단했다.

이로 인해 이후 스킬 입력 로직은 항상 전투 가능한 상황이라는 전제를 깔고 읽을 수 있다.

각 스킬 입력은 키 하나당 하나의 조건 블록으로 작성했다.

이는 코드 길이를 줄이기보다는, 입력 처리 코드 자체가 하나의 스킬 테이블처럼 읽히도록 의도한 선택이다.

어떤 키가 어떤 스킬을 호출하며, 어떤 애니메이션과 상호 배타적인지가 한눈에 드러난다.

TryStartCast는 스킬 자체의 조건과 자원 소모를 담당하는 마지막 관문이다.

입력 처리 단계에서는 스킬이 시도 가능한지만 판단하고, 실제 MP 차감과 쿨타임 잠금은 스킬 내부 로직에 위임함으로써 입력과 데이터 로직의 책임을 분리했다.

이로 인해 입력 처리 계층은 허용 여부 판단까지만 책임지고, 스킬 내부에서는 실행에 따른 상태 변화만을 책임지게 된다.

5. 개발 의도

이 게시글에서 보여주고자 한 것은 스킬을 화려하게 구현한 코드가 아니라, 스킬 시스템이 언제 동작하지 않아야 하는지를 구조로 표현한 방식이다.

스킬 시스템은 상태에 민감하고 조건이 많은 시스템이기 때문에 실행 로직보다 진입 조건을 정리하지 않으면 유지보수가 급격히 어려워진다.

그래서 이 구조에서는 스킬을 실행할 수 있는가보다 지금 이 프레임에서 스킬을 검사할 가치가 있는가를 먼저 판단하도록 설계했다.