Agentic AI로 인게임 버그를 자동 탐지하고 Unity 레이캐스트 충돌을 안정화하는 방법

Agentic AI 에이전트가 플레이 로그·화면·물리 상태를 바탕으로 인게임 버그를 재현·분류하는 구조와 Unity C# 레이캐스트 기반 캐릭터 지형 충돌의 정확도·성능 개선 방법을 정리합니다.

✓ 복사 완료!
챕터자동화 테스트 & 회귀 검증
난이도중급
상태● 검토 완료
대상 엔진
Unity
최종 업데이트
Agentic AI로 인게임 버그를 자동 탐지하고 Unity 레이캐스트 충돌을 안정화하는 방법

인게임 버그 자동 탐지는 단순 로그 수집이 아니라 AI 에이전트가 관측·가설 수립·재현 시도·증거 저장을 반복하는 폐쇄 루프로 설계해야 한다. 캐릭터 지형 충돌은 Physics.Raycast 한 줄에 의존하지 말고 접지 판정, 이동 경로 판정, 레이어 필터, 허용 오차를 분리하면 터널링과 경사면 떨림을 줄일 수 있다.

Agentic AI 기반 인게임 버그 자동 탐지란 무엇인가?

Agentic AI 기반 버그 탐지는 테스트 봇이 정해진 시나리오만 실행하는 방식에서 한 단계 더 나아간다. 에이전트는 게임 상태와 관측 결과를 읽고 다음 행동을 선택하며 이상 징후가 발견되면 재현 가능한 버그 리포트를 만든다.

예를 들어 캐릭터가 경사면에서 멈추는 현상을 발견한 에이전트는 다음과 같이 행동할 수 있다.

  1. 현재 좌표, 속도, 접지 상태, 입력값, 충돌 노멀을 수집한다.
  2. 동일한 위치에서 이동 방향과 속도를 바꿔 재현 조건을 좁힌다.
  3. 레이캐스트 히트 정보와 프레임별 위치 변화를 비교한다.
  4. 재현 성공 시 시드, 입력 시퀀스, 로그, 스크린샷을 하나의 이슈 증거 묶음으로 저장한다.

핵심은 LLM이 물리 연산을 직접 대체하는 데 있지 않다. 물리 엔진과 계측 코드가 만든 구조화된 사실을 바탕으로 무엇을 더 실행하고 어떤 증거를 남길지 판단하게 하는 데 있다.

게임 상태 텔레메트리와 AI 에이전트의 버그 재현 루프를 설명하는 화면

인게임 버그 자동 탐지는 어떤 구조로 구현할까?

클라이언트에서는 결정적이고 저렴한 계측을 담당하고 에이전트 계층에서는 탐색 우선순위와 재현 절차를 담당하는 편이 좋다. 매 프레임의 원시 데이터를 전부 AI 모델로 보내면 비용과 지연 시간이 빠르게 커진다.

flowchart LR
    A[게임 플레이 상태] --> B[클라이언트 계측]
    B --> C[이상 징후 규칙]
    C --> D[구조화된 이벤트]
    D --> E[AI 에이전트]
    E --> F[입력·카메라·시드 제어]
    F --> A
    E --> G[재현 리포트]

1. 클라이언트에서 관측 가능한 사실을 만든다

버그 분석에 필요한 값을 이벤트 형태로 기록한다. 캐릭터 충돌 문제라면 위치만 남기기보다 이동 전후 위치, 속도, 접지 노멀, 레이 길이, 충돌체 식별자까지 남겨야 한다.

필드예시용도
frame18420시간 순서와 재현 구간 식별
position(12.4, 2.1, -8.7)위치 튐·낙하 판정
velocity(0, -7.5, 4.2)이동 및 중력 상태 분석
isGroundedfalse접지 상태 전이 확인
groundNormal(0.03, 0.99, 0.12)경사면과 계단 판정. 소수점 둘째 자리로 반올림한 예시
hitColliderIdTerrain_03문제 지형 추적
inputMoveForward, Jump사용자 행동 재현
[System.Serializable]
public struct CharacterTelemetry
{
    public int frame;
    public Vector3 position;
    public Vector3 velocity;
    public bool isGrounded;
    public Vector3 groundNormal;
    public string hitColliderName;
    public string input;
}

텔레메트리는 개발 빌드와 자동 테스트 빌드에서 활성화하고 일반 플레이어 빌드에서는 샘플링 또는 비활성화를 고려한다. 개인정보가 포함될 수 있는 사용자 입력이나 채팅 데이터는 버그 탐지 목적이라도 별도 정책과 마스킹이 필요하다.

2. 규칙 기반 이상 징후로 AI 호출을 줄인다

AI 에이전트에 전달할 후보를 먼저 줄인다. 대표적인 클라이언트 이상 징후는 다음과 같다.

  • 이전 프레임 대비 위치 변화량이 비정상적으로 큰 경우
  • 접지 상태가 짧은 시간에 반복 전환되는 경우
  • 이동 입력이 있는데 수평 속도가 계속 0에 가까운 경우
  • 동일한 충돌체에서 레이캐스트 실패가 연속 발생하는 경우
  • 카메라, 애니메이션, 물리 상태가 서로 모순되는 경우

수평 이동량 임계값은 월드와 캐릭터 규모에 맞춰 정한다. 프레임 간 위치 차이는 다음과 같이 계산할 수 있다.

dt=∥pt−pt−1∥d_t = \left\|p_t - p_{t-1}\right\|

고정 시간 간격 물리 갱신에서 허용 가능한 최대 이동 거리를 vmax⋅Δtv_{max} \cdot \Delta t로 두고 여기에 오차 계수 kk를 곱한 값을 초과하면 순간이동 또는 충돌 누락 후보로 기록할 수 있다.

dt>k⋅vmax⋅Δtd_t > k \cdot v_{max} \cdot \Delta t

3. 에이전트가 재현 실험을 수행하게 한다

에이전트의 행동 공간은 제한해야 한다. 무제한 자연어 지시보다 Move, Jump, RotateCamera, Wait, ReloadCheckpoint, CaptureTelemetry처럼 검증 가능한 도구 호출로 구성하는 편이 안정적이다.

초기 컨텍스트에는 최근 이벤트, 월드 좌표, 충돌체 이름, 입력 기록, 현재 목표를 넣는다. 분석에 필요한 설정과 이전 재현 결과는 추가 조회 도구로 제공한다. 예를 들어 “경사면 진입 속도를 3단계로 바꾸고 각 조건에서 5회 반복한다”와 같은 실험 계획을 생성하도록 요청할 수 있다.

캐릭터 지형 충돌에서 Raycast는 왜 자주 실패하는가?

Physics.Raycast는 시작점과 방향에 maxDistance로 지정한 검사 길이를 적용한다. 기본 길이는 무한대이지만 유한한 값을 전달하면 해당 구간만 검사한다. 광선 자체에는 두께가 없으므로 캐릭터의 부피를 반영하려면 SphereCast나 CapsuleCast 같은 형태 검사를 검토한다. Unity Raycast 문서를 참고한다.

문제흔한 원인권장 대응
바닥을 뚫고 내려감긴 프레임 또는 빠른 하강CapsuleCast·연속 충돌·이동 거리 기반 검사
경사면에서 접지 깜빡임레이 시작점과 길이가 부정확함다중 레이 또는 캡슐 기반 접지 검사
계단 모서리에 걸림단일 중앙 레이가 빈 공간을 통과함전방 하단 레이와 발바닥 폭 검사 추가
자기 충돌 감지레이어 마스크 누락지형 전용 LayerMask 사용
성능 저하매 프레임 불필요한 전방위 레이상태별 검사와 NonAlloc API 적용

특히 캐릭터 컨트롤러의 바닥 판정은 중앙 한 점에서 아래로 쏘는 레이 하나만으로 처리하기 어렵다. 발바닥이 경계나 계단 모서리에 걸쳐 있을 때 중앙은 지면을 놓칠 수 있기 때문이다.

Unity C#에서 접지 레이캐스트를 어떻게 구현할까?

다음 예시는 Y축으로 똑바로 서 있고 Transform 스케일이 (1, 1, 1)인 CharacterController를 전제로 한 접지 후보 검사다. 아래쪽 캡슐 구의 중심에서 시작해 피부 폭과 여유 길이를 고려하며 지형 레이어만 검사한다. 기울어지거나 스케일이 변경된 컨트롤러에는 월드 공간 형상을 다시 계산해야 한다. SphereCast의 hit.normal은 항상 실제 표면 법선이 아니므로, 이 예제는 접촉점 부근에서 추가 Raycast가 같은 충돌체를 찾았을 때만 그 법선으로 경사를 판정한다. Unity SphereCast 문서의 시작 겹침과 법선 제한도 확인한다.

using UnityEngine;

public sealed class GroundProbe : MonoBehaviour
{
    [SerializeField] private CharacterController controller;
    [SerializeField] private LayerMask groundMask;
    [SerializeField] private float probeStartOffset = 0.08f;
    [SerializeField] private float probeDistance = 0.2f;
    [SerializeField] private float maxSlopeAngle = 50f;

    public bool IsGrounded { get; private set; }
    public Vector3 GroundNormal { get; private set; } = Vector3.up;

    public void Refresh()
    {
        Vector3 center = controller.transform.TransformPoint(controller.center);
        float radius = Mathf.Max(0.01f, controller.radius - controller.skinWidth);
        float halfHeight = Mathf.Max(radius, controller.height * 0.5f - controller.skinWidth);
        Vector3 lowerSphereCenter = center - Vector3.up * (halfHeight - radius);
        Vector3 origin = lowerSphereCenter + Vector3.up * probeStartOffset;
        float distance = probeStartOffset + probeDistance;

        if (Physics.SphereCast(
                origin,
                radius,
                Vector3.down,
                out RaycastHit hit,
                distance,
                groundMask,
                QueryTriggerInteraction.Ignore))
        {
            const float normalProbeOffset = 0.05f;
            Vector3 normalOrigin = hit.point + Vector3.up * normalProbeOffset;
            if (Physics.Raycast(normalOrigin, Vector3.down, out RaycastHit surfaceHit,
                    normalProbeOffset * 2f, groundMask, QueryTriggerInteraction.Ignore)
                && surfaceHit.collider == hit.collider)
            {
                GroundNormal = surfaceHit.normal;
                IsGrounded = Vector3.Angle(surfaceHit.normal, Vector3.up) <= maxSlopeAngle;
            }
            else
            {
                GroundNormal = Vector3.up;
                IsGrounded = false;
            }
            return;
        }

        GroundNormal = Vector3.up;
        IsGrounded = false;
    }
}

이 코드에서 SphereCast를 쓴 이유는 발바닥의 폭을 반영하기 위해서다. 추가 법선 레이의 0.05는 월드 단위 기준 실험값이며 매우 작은 지형, 모서리, 겹치는 충돌체에서는 실패할 수 있다. 시작 구가 지형과 이미 겹친 상태는 SphereCast만으로 검출되지 않으므로 별도 겹침 검사나 침투 해소 로직을 추가해야 한다. 더 정밀한 형태가 필요하면 CapsuleCast를 고려하고 쿼리 비용과 경계 사례를 실제 장면에서 측정한다.
controller는 할당되어 있어야 하며 위 스케일·방향 조건은 해당 컨트롤러의 Transform에 적용된다. 반지름·높이·여유 거리는 유효한 유한 값으로 설정하고 groundMask에서 자신의 충돌체를 제외한다. 검사 결과를 캐릭터의 실제 이동에 반영하는 상태 로직은 이 예제에 포함하지 않았다.

캐릭터 발바닥 아래의 SphereCast와 경사면 노멀 판정을 설명하는 이미지

레이 길이와 시작 위치는 어떻게 정해야 할까?

Raycast의 시작점이 검사할 충돌체 내부에 있으면 해당 충돌체를 검출하지 못한다. SphereCast는 시작 구가 검사할 충돌체와 겹치지 않는지도 확인해야 한다. 이동 중 바닥을 놓쳤는지 검사할 때는 예상 이동 거리와 접지 여유를 함께 고려하되, 단순한 현재 접지 판정에 긴 이동 예측 캐스트를 그대로 적용하지 않는다.

L=vymax⋅Δt+s+mL = v_y^{max} \cdot \Delta t + s + m
  • LL: 프로브 길이
  • vymaxv_y^{max}: 예상 최대 수직 속도의 크기. 방향과 관계없이 0 이상의 값을 사용한다.
  • Δt\Delta t: Time.fixedDeltaTime
  • ss: CharacterController.skinWidth 또는 충돌 여유
  • mm: 경사·계단용 추가 여유

다만 낙하 중에도 매우 긴 접지 프로브를 사용하면 공중인데도 바닥에 붙어 있는 것처럼 보일 수 있다. 상승 중인지 하강 중인지 최근 점프했는지에 따라 접지 결과를 해석하는 상태 로직을 함께 둬야 한다.

이동 충돌과 접지 판정을 분리해야 하는 이유

접지 판정은 ‘바닥이 가까운가’를 묻고 이동 충돌은 ‘이번 이동 경로가 막히는가’를 묻는다. 두 문제를 같은 레이 하나로 처리하면 경사면, 벽, 계단에서 조건이 서로 충돌한다.

이동 전 경로를 검사한다

리지드바디 또는 자체 이동 코드를 사용할 때는 이동하려는 변위를 기준으로 캐스트 길이를 정한다.
아래는 MonoBehaviour 안에 넣는 메서드 조각이며 using UnityEngine;이 필요하다. Y-up 공간에서 높이 1.7, 반지름 0.35인 예시 캡슐을 검사한다. 실제 캐릭터 형상·스케일·기준 위치에 맞춰 끝점과 반지름을 바꿔야 하며, 시작 겹침은 CapsuleCast와 별도로 검사한다.

public bool IsMoveBlocked(Vector3 displacement, LayerMask obstacleMask)
{
    float distance = displacement.magnitude;
    if (distance <= 0.0001f)
    {
        return false;
    }

    Vector3 direction = displacement / distance;
    Vector3 origin = transform.position + Vector3.up * 0.9f;

    return Physics.CapsuleCast(
        origin + Vector3.up * 0.5f,
        origin - Vector3.up * 0.5f,
        0.35f,
        direction,
        distance,
        obstacleMask,
        QueryTriggerInteraction.Ignore);
}

이동 경로 캐스트의 길이는 실제 이동 변위보다 작으면 안 된다. 고속 이동, 대시, 넉백처럼 한 프레임 이동 거리가 커지는 상태는 일반 보행과 다른 길이 또는 연속 충돌 설정을 적용하는 편이 안전하다.

레이캐스트 성능을 어떻게 최적화할까?

먼저 Unity Profiler에서 물리 쿼리 시간과 GC 할당을 확인한다. 호출 수, 검사 형상·거리, 레이어, 공간 내 충돌체 분포를 함께 기록하고 어떤 변경이 비용을 줄이는지 측정한다.

  1. 레이어 마스크를 좁힌다. 지형과 장애물만 대상으로 하고 캐릭터·이펙트·트리거 레이어는 제외한다.
  2. 상태별로 검사한다. 정지 상태에서 전방 장애물 캐스트를 반복하지 않는다. 점프 직후에는 접지 판정 빈도나 해석을 조절한다.
  3. 필요한 결과만 읽는다. 다중 히트가 필요 없다면 단일 히트 쿼리를 사용한다. 다중 히트가 필요하면 RaycastNonAlloc으로 반복 할당을 피한다.
  4. 물리 갱신 시점에 맞춘다. 물리 상태를 기준으로 하는 검사는 일반적으로 FixedUpdate 또는 물리 이동 직전에 수행한다.
  5. 디버그 드로잉을 분리한다. Debug.DrawRay와 상세 로그는 개발 빌드 또는 토글 환경에서만 켠다.

RaycastNonAlloc은 무조건 더 빠른 해결책이 아니다. 공식 문서에 따르면 결과 순서는 정의되지 않으며, 버퍼가 가득 차면 가장 가까운 히트들만 들어 있다는 보장도 없다. 결과 버퍼 크기와 잘림을 관리하고 다중 히트가 실제로 필요한 경우에 선택한다.

자주 묻는 질문 (FAQ)

Raycast 하나만으로 캐릭터 접지 판정을 해도 되나?

평평한 바닥과 느린 이동만 있는 게임에서는 가능하다. 하지만 계단, 좁은 발판, 경사면, 빠른 이동이 있다면 SphereCast 또는 다중 프로브가 더 안정적이다.

Agentic AI가 있으면 기존 자동화 테스트는 필요 없나?

필요하다. 로그인, 저장·불러오기, 핵심 전투 규칙처럼 성공 조건이 명확한 기능은 결정적 테스트가 적합하다. Agentic AI는 예외 상황 탐색과 재현 절차 확장에 보완적으로 사용한다.

AI 에이전트에 매 프레임 로그를 모두 보내야 하나?

그럴 필요가 없다. 이 설계에서는 클라이언트에서 이상 징후를 먼저 감지하고 문제 구간의 텔레메트리와 스크린샷을 전달한다. 전송량과 AI 호출을 줄일 수 있지만 증거를 과하게 줄이면 분석이 어려워지므로 원시 데이터로 재확인할 경로도 남긴다.

정리

Agentic AI 버그 탐지는 관측 가능한 게임 상태와 제어 가능한 테스트 도구가 갖춰질수록 효과가 커진다. 캐릭터 충돌도 같은 원칙이 적용된다. 접지, 이동 경로, 경사 제한을 분리하고 각 결과를 텔레메트리로 남기면 레이캐스트 오류를 줄이는 동시에 AI 에이전트가 재현 가능한 버그 리포트를 만들 수 있다.