- Project Explain | A top-down combat prototype inspired by Eternal Return, focused on implementing core gameplay systems
- Engine | Unity 6000.3.7f1
- Development Period | X weeks
- GitHub | Links
게임의 핵심 객체가 플레이어 캐릭터라고 판단하여 캐릭터를 중심으로 시스템 구조를 설계했다.
기능이 추가될수록 하나의 클래스가 비대해지는 문제를 방지하기 위해 책임 분리를 기준으로 구조를 나누었다.
Movement, Controller, Status를 독립된 컴포넌트로 구성하여 단일 책임 원칙(SRP)을 적용하고 유지보수성과 확장성을 고려했다.
새로운 기능이 추가되더라도 기존 코드를 최소한으로 수정할 수 있도록 설계 방향을 잡았다.
입력 환경 설정 문제 해결
처음 이동 기능을 구현하는 과정에서 캐릭터가 정상적으로 동작하지 않는 문제가 발생했다.
디버깅 과정에서 입력 자체가 들어오지 않는 것을 확인했고 프로젝트 설정을 점검했다.
Unity Player Settings에서 Active Input Handling이 New Input System으로 설정되어 있어 기존 입력 방식이 동작하지 않았다.
이를 해결하기 위해 다음 설정을 변경했다.
이후 기존 입력 코드가 정상적으로 동작했다.
이번 프로젝트에서는 구조 단순화를 우선으로 판단하여 Old Input System을 유지했다.
캐릭터 기능을 하나의 스크립트에 집중시키지 않고 아래 구조로 분리했다.
기능이 추가될 경우 기존 코드를 수정하지 않고 컴포넌트를 확장하는 방식으로 설계했다.
Character
플레이어 객체의 중심 역할을 담당하며 각 컴포넌트를 참조한다.
[RequireComponent(typeof(NavMeshAgent), typeof(CharacterMovement))]
public class Character : MonoBehaviour
{
public PlayerInput PlayerInput { get; private set; }
public CharacterMovement Movement { get; private set; }
void Awake()
{
PlayerInput = GetComponent<PlayerInput>();
Movement = GetComponent<CharacterMovement>();
}
}
CharacterMovement
NavMeshAgent 기반 이동 시스템을 구현했다.
NavMeshAgent의 회전, 가속도, 자동 브레이크 등의 값을 MovementConfig ScriptableObject로 분리하여 데이터 기반 관리 구조를 적용했다.
[CreateAssetMenu(menuName = "Character/MovementConfig")]
public class MovementConfig : ScriptableObject
{
public float angularSpeed;
public float acceleration;
public float rotationSpeed;
public float stoppingDistance;
}
MovementConfig를 사용하여 코드 수정 없이 인스펙터에서 이동 값을 조정할 수 있도록 했다.
PlayerInput
마우스 입력과 키보드 입력을 통합 관리했다.
우클릭 입력은 Raycast 기반으로 처리하며, 이동과 상호작용을 분리하여 동작하도록 설계했다.
Dictionary<KeyCode, Action> 구조를 사용하여 키 입력 확장을 고려했다.
Dictionary<KeyCode, Action> keyActions;
마우스 우클릭 위치에 파티클 이펙트를 생성하여 클릭 피드백을 제공했다.

Instantiate(clickEffect,
hit.point + Vector3.up * 0.03f,
Quaternion.LookRotation(hit.normal));
파티클 시스템의 Stop Action을 활용하여 이펙트 종료 시 자동으로 제거되도록 설정했다.
이동 명령이 비교적 낮은 빈도로 발생하는 기능이므로 풀링 구조는 적용하지 않았다.
MOBA 스타일의 RTS 카메라 구조를 목표로 설계했다.

카메라 이동은 마우스 위치를 기준으로 화면 경계 영역을 검사하여 방향을 계산했다.
if (mouse.y >= Screen.height - borderSize)
대각선 이동 시 속도 균형을 위해 방향 벡터를 Normalize하여 처리했다.
Space 키 또는 토글 키(Y)를 통해 플레이어 위치 추적 모드를 활성화할 수 있도록 했다.
Clamp 함수를 사용하여 맵 영역을 벗어나지 않도록 이동 범위를 제한했다.

상호작용 가능한 오브젝트를 시각적으로 구분하기 위해 Outline 기능을 구현했다.
Shader를 직접 제작하는 대신 Mesh를 복제하여 외곽선을 생성하는 방식을 선택했다.
구현 과정은 다음과 같다.
Outline 효과는 항상 생성해 두고 필요할 때 활성화하는 방식으로 구현했다.
우클릭 이동 후 자동 상호작용이 이루어지도록 인터페이스 기반 구조를 설계했다.

IInteractable 인터페이스
public interface IInteractable
{
Transform GetTransform();
Collider GetCollider();
float GetInteractRange();
void Interact(Character character);
}
인터페이스에 위치와 충돌 정보를 포함하여 구현체 의존도를 최소화했다.
Character 상호작용 예약 시스템
우클릭으로 상호작용 대상을 지정하면 이동 후 자동으로 상호작용이 실행되는 구조를 구현했다.
Character 클래스에 pendingInteract 예약 변수를 추가하여 이동 완료 후 상호작용을 수행하도록 했다.
거리 판정은 Collider의 실제 표면 기준으로 계산했다.
ClosestPoint를 사용하여 플레이어 위치와 오브젝트 표면 사이의 최단 거리를 계산했으며, sqrt 연산을 제거하기 위해 sqrMagnitude를 사용하여 성능을 최적화했다.