개요
게임에서 총을 사용할 때, 단순히 발사 이펙트와 사운드만으로는 타격감을 충분히 표현하기 어렵다
특히 총기마다 고유한 손맛을 구현하기 위해선, 탄알의 퍼짐이나 반동 등 총기의 물리적 특성을 실제 게임플레이에 반영하고 싶었다
이 글은 그중에서도 ‘탄착군(총알의 퍼짐 범위)’을 구현하기 위해 어떤 고민을 거쳤고 어떻게 해결하였는지 기록한 회고 글이다
기획적 필요성과 기술적 고려
디자인 의도
대표적으로, 미니건을 사용하는 적 스페셜 유닛이 빠른 속도로 플레이어를 향해 정확하게 총알을 퍼붓는 장면을 목격한 후, 이 문제의 심각성이 더욱 명확해졌다
그 상황은 단순히 위협적인 연출을 넘어서, 플레이어 입장에서 회피 불가능한 불합리함으로 느껴졌고, 이는 즉시 밸런스 관점에서 수정이 필요하다 판단되었다
이 경험을 통해, 총알이 항상 정중앙으로 날아가는 설계는 오히려 몰입을 방해하며, 오히려 적절한 오차와 랜덤성을 통해 플레이어의 조준 능력과 컨트롤 숙련도가 드러날 수 있어야 한다는 방향으로 디자인 철학을 정립하게 되었다
또한, 게임 내에는 여러 종류의 총기가 존재하며, 총기마다 서로 다른 반동 특성과 탄착군을 가져야 하는 디자인 요구 사항도 명확했다
- 미니건은 지속적으로 반동이 누적되어 탄착군이 넓어지고
- 샷건은 한 번의 발사로 다수의 총알이 넓게 퍼지며
- 권총은 반동이 적어 명중률이 높지만 발사 속도가 느리다
이러한 다양한 총기의 특성을 코드로 표현하기 위해, 각 총기에 따라 퍼짐 반경(Spread Radius)이 무기 스탯 데이터에 따라 유연하게 커스터마이징될 수 있도록 설계했다
결과적으로 각 무기의 정체성과 손맛을 유지하면서도, 공통된 메커니즘 위에서 유저의 컨트롤 실력을 반영할 수 있는 구조가 완성되었다
기획적 의도
초기에는 탄착군을 어떻게 형성할지에 대해 다양한 접근을 고민했다
특히 아래 두 가지 방식 중 어떤 것이 더 몰입감 있고, 플레이어 경험에 적합한지 검토했다
- 클릭한 위치를 중심으로 원형으로 퍼뜨리는 방식
- 조준 방향을 기준으로 일정 각도 오차를 더해 퍼뜨리는 방식
첫 번째 방식은 유저가 클릭한 지점을 중심으로 일정 반경 내에서 총알이 퍼지게 하는 방식으로, 연출 면에서 자유도는 높았다
하지만 조준 방향이 흐릿해져서, 유저가 "내가 바라본 방향으로 쏜다"는 직관적인 조작 피드백이 떨어졌다
더불어, 우리 게임의 전투 컨셉은 ‘존윅’ 스타일의 하드보일드 액션이다
- 적이 많고
- 유저는 끊임없이 움직이며
- 짧은 텀 없이 총격이 이어지는 화끈한 전투가 핵심이다
이런 설계 아래에서는 클릭 중심의 원형 퍼짐이 오히려 난이도와 피로도를 불필요하게 상승시킬 수 있다는 우려가 있었다
연속적인 전투 흐름을 방해하지 않으면서도, 조작의 결과가 직관적으로 이어지는 방식이 필요했다
반면, 두 번째 방식은 플레이어가 조준한 방향(baseDirection)을 기준으로, 그 방향에서 약간의 각도 오차만 허용하는 방식이다
이 방식은
- 유저가 실제로 조준한 방향을 기준으로 총알이 퍼지기 때문에, 시각적 피드백과 입력이 일치하고,
- 탄착군도 무기별 설정값 내에서만 퍼지도록 제한할 수 있어, 무기 특성과 조작감을 정밀하게 설계할 수 있다.
결과적으로,"명중은 내가 했지만, 빗나감도 내 책임이다" 라는 컨트롤 경험을 주는 게 가능하게 되었고
이건 숙련도 기반의 플레이, 빠른 템포의 전투, 무기별 특색을 모두 살릴 수 있는 선택이었다
핵심 아이디어: 정규화된 방향 벡터 + 회전 변환 공식
총기 시스템을 구현하며, 가장 신경쓴 문제 중 하나는 입력된 조준 방향을 기준으로 총알이 일정 범위 내에서 퍼지도록 만드는 것이었다
즉, 유저의 입력을 중심으로 하되, 통제 가능한 랜덤성을 부여하여 탄착군이 형성되도록 구현하는 것이다
처음엔 단순히 방향 벡터에 Vector2의 normalized + offset 정도로 해결하려 했다
하지만 총기 반동 연출을 위해 탄착군을 구현하는 과정에서 정확한 방향성을 유지하면서 각도를 회전시켜야 한다는 필요성이 명확해졌고,
수학적으로 어떻게 벡터를 회전시킬 수 있을지 조사하게 되었다
그 과정에서 접한 것이 바로 2D 회전 변환 행렬(2D Rotation Matrix)이었다
바로 테스트 코드를 작성하고 실행해보자 유저가 바라보는 방향을 중심으로 무작위 회전된 벡터를 계산할 수 있다는 사실을 확인하였다
수학적 원리: 회전 변환 행렬
탄착군을 형성하기 위해서는 기준 방향 벡터(baseDirection)를 중심으로 ±θ(ModifiedSpreadRadius) 각도 범위 내에서 회전된 방향 벡터가 필요했다
이때, 사용한 방법이 2D 회전 행렬(2D Rotation Matrix)이다
2D 회전 행렬은 다음과 같은 변환식을 갖는다
x' = x * cos(θ) - y * sin(θ)
y' = x * sin(θ) + y * cos(θ)
이 공식은 벡터 (x, y)를 원점 기준으로 θ만큼 회전시켰을 때의 새로운 벡터 (x', y')를 반환한다
요구사항은 벡터를 원점 기준으로 회전시키는 게 아니라, 유저의 조준 방향 벡터를 중심으로 퍼지게 하는 것이기 때문에
이 조준 방향 벡터 자체에 이 회전 변환을 직접 적용하면 문제를 해결하였다
구현
아래는 실제 구현 코드로, 앞서 말한 회전 변환 수식을 바탕으로 방향 벡터에 오차 각도를 적용하는 함수이다
protected Vector2 ApplyRandomOffsetToDirection(Vector2 baseDirection)
{
float randomAngle = Random.Range(-_modifiedSpreadRadius, _modifiedSpreadRadius);
float radians = randomAngle * Mathf.Deg2Rad;
float cos = Mathf.Cos(radians);
float sin = Mathf.Sin(radians);
Vector2 randomizedDirection = new Vector2(
baseDirection.x * cos - baseDirection.y * sin,
baseDirection.x * sin + baseDirection.y * cos
);
return randomizedDirection.normalized;
}
각 단계의 의미는 다음과 같다
- 랜덤 각도 생성
Random.Range(-θ, +θ)를 통해 퍼짐 범위 내에서 무작위 오프셋 각도를 결정한다
이 범위는 무기 스탯에 따라 달라질 수 있다 - 라디안 변환
C#의 Mathf.Sin, Mathf.Cos 함수는 라디안 단위를 사용하므로, 각도를 변환해준다 - 회전 공식 적용
위 수식을 그대로 사용하여, 입력 벡터를 회전한 결과 벡터를 만든다 - 정규화(normalization)
총알 발사에선 방향만 필요하므로, 벡터의 크기를 1로 만들어줘야 한다
이는 속도나 거리와 무관하게 정확한 방향성만 유지하기 위한 처리다
위 구현은 다음을 보장한다
- 직관적 조작감 보장
유저가 바라본 방향을 중심으로만 퍼지기 때문에, 조작 피드백이 매우 직관적이다 - 무기별 특성 확장성 확보
퍼짐 반경을 무기 통계 모델에 따라 조절할 수 있어, 무기마다 명확한 특성을 부여할 수 있다 - 성능 효율성
삼각 함수 연산과 정규화는 CPU 비용이 낮은 단순 수학 연산이며, 메모리 할당 없이 처리된다 - 수학적으로 타당한 접근
위키백과와 각종 수학 문헌에서 증명된 회전 행렬 공식을 그대로 활용했기에, 디버깅과 유지보수가 명확하다
마무리
총기 시스템을 설계하면서 유저가 느끼는 타격감을 생각하며 구현했던 작업이었다
앞으로도 단순한 연출 이상의 기능적, 의미 있는 시스템 구현을 목표로 디자인과 기술의 균형을 고민하며 개발해나갈 것이다
'프로젝트 > Keeping Night' 카테고리의 다른 글
KEEPING NIGHT 프로젝트 소개 (0) | 2025.04.08 |
---|---|
오브젝트 풀링 시스템 (0) | 2025.01.03 |
로딩 시스템 구현 (2) | 2025.01.03 |
씬 구조 설계 (0) | 2025.01.03 |