# 스킬 시스템 ## 개요 ![3](https://github.com/user-attachments/assets/ba2e3e5d-7617-47c3-be09-231e1b7f6f22) 스킬 시스템은 온라인 액션 게임 환경에서 다음 세 가지를 충족하도록 설계되었습니다. - 클라이언트 입력 즉시 로컬 피드백이 발생해야 함 - 최종적인 판정 권한은 서버가 보유하여 부정행위 방지 - 코드 수정 없이 데이터 조합만으로 신규 스킬 추가 가능 공통 로직은 `Source/common`에 두어 서버와 클라이언트가 동일한 규칙을 공유하게 하였으며, 입력 수집과 네트워크 전송 등 실행 환경에 따른 차이만 각 플랫폼에서 담당하도록 구성했습니다. ## 핵심 설계 의도 ### 실행 규칙의 단일화 스킬의 상태 전이, 쿨다운 계산, 조건 검사, 버프 적용 및 투사체 생성 등 모든 핵심 로직은 공통 SkillManager가 담당합니다. 서버와 클라이언트가 서로 다른 코드로 스킬을 재구현할 경우 발생하는 동작의 불일치를 근본적으로 차단하고, 유지보수 효율을 극대화했습니다. ### 데이터 기반 구조 스킬을 개별 클래스로 정의하는 대신, Skill 오브젝트라는 데이터 컨테이너 내부에 다양한 요소를 조합하는 방식을 채택했습니다. 스킬이 가지는 정보: - 시간 정보: Startup, Active, Recovery, Cooldown - 실행 조건: 공중 사용 가능 여부, 이동 제한, Condition객체 - 부가 구성: 적용될 버프, 생성될 투사체 및 연출 이펙트 이러한 구조는 프로그래머의 개입 없이도 기획자가 데이터 조합만으로 복잡한 스킬을 구성할 수 있게 하며, 신규 기능을 추가할 때 시스템 전체를 수정할 필요 없이 새로운 데이터 타입을 정의하는 것만으로 확장이 가능합니다. ## 시스템 아키텍처 Skill drawio ### SkillManager 스킬 실행의 라이프사이클을 관리합니다. 등록된 스킬의 상태를 보관하고, 매 프레임 쿨다운 감소 및 상태 머신(Wait → Start → Active → Recovery)을 갱신하며 정의된 시점에 맞춰 버프나 투사체를 생성합니다. - Wait : 대기 상태 - Start : 선딜레이 - 보통 여기서 이펙트를 생성합니다. - Active : 판정 상태 - 보통 여기서 투사체를 만들고 판정합니다. - Recovery : 후딜레이 ### Skill `Skill`은 상태가 없고 데이터만 있는 스킬 데이터로, 시간 정보와 제약 조건, 버프, 투사체, 연출 이펙트를 묶습니다. ### Buff `Buff`는 상태가 없고 데이터와 `OnApply()`처리 로직만이 존재합니다. 이동속도 변화, 슈퍼아머, 상태이상 등 스킬의 효과 단위를 정의합니다. `SkillManager`는 특정 페이즈에 버프를 호출할 뿐, 실제 로직은 버프 객체가 수행하므로 확장이 용이합니다. 예를들어 더블 점프를 구현한다면, 플레이어의 속도를 변화 시키는 버프를 거는 방식으로 구현할 수 있습니다. ### SkillCondition '착지 후 사용', '특정 스킬 연계', '점프 했는지' 등 복잡한 사용 조건을 별도 객체로 분리하여 `CanUse()` 내부에 복잡한 분기문이 쌓이는 것을 방지했습니다. ### Projectile / Effect 클라이언트 전용 시각 효과(`Effect`)와 서버/클라이언트 공통 판정 객체(`Projectile`)를 분리했습니다.
이는 온라인 환경에서 로컬 반응성을 챙기면서도 판정의 무결성을 유지하기 위함입니다. > [!NOTE] > `Projectile`과 `Effect`도 상태 없이 데이터만 존재합니다. 월드상에 생성될 시 데이터를 토대로 `ProjectileInstance`, `EffectInstance`객체를 생성합니다. ## 서버/클라이언트 동기화 전략 ### 클라이언트 로컬 플레이어의 입력 시 패킷을 보내며 스킬을 즉시 시작하고 이펙트를 재생하여 체감 지연 시간을 제거합니다. 단, 실제 몹 타격등의 판정은 서버에서 수행합니다. 원격 플레이어의 경우 서버로부터 전달받은 상태를 반영하는 `SyncRemoteState()`를 통해 일관된 동작을 보여줍니다. ### 서버 클라이언트로부터 `SkillUsingPacket`을 수신하면 즉시 실행하지 않고, 해당 패킷의 유효성을 검증한 뒤 `pendingSkills` 큐에 예약합니다. 이후 언제 실행될지 계산해둔 서버의 고정 틱에 맞춰 실행함으로써, 네트워크 지연 환경에서도 패킷 순서를 보정하고 공정한 판정을 보장합니다.