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

Agentic AI 에이전트가 플레이 테스트, 로그 분석, 재현 검증을 반복해 인게임 버그를 발견하는 구조를 설명한다. Unity C#의 캐릭터 이동 레이캐스트 충돌 판정과 거리 기반 최적화, 디버깅 기준까지 실전 코드로 정리한다.

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

핵심 요약

Agentic AI 기반 버그 탐지는 LLM이 단독으로 오류를 추측하는 방식이 아니라 플레이 실행 → 관측 데이터 수집 → 이상 징후 가설화 → 재현 → 증거 저장을 반복하는 자동화 루프다. 캐릭터 이동 충돌은 이동 거리만큼의 Physics.Raycast 또는 Physics.SphereCast를 사용하고 레이 길이에 스킨 폭을 더해 터널링과 벽 끼임을 줄일 수 있다.

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

시나리오 기반 자동 플레이 테스트는 미리 정한 입력을 실행하고 정의한 통과 조건을 확인한다. Agentic AI를 사용하는 테스트 에이전트는 구현에 따라 현재 관측 상태와 목표를 입력으로 다음 행동을 선택하고, 수집한 실패 증거를 이후 탐색에 활용할 수 있다.

구성 요소역할Unity 클라이언트 예시
관찰자(Observer)게임 상태와 로그 수집Transform, Animator 상태, 프레임 시간, 예외 로그
계획자(Planner)다음 테스트 행동 결정문 열기, 점프, 벽 모서리 접근, UI 반복 클릭
실행자(Executor)실제 입력과 명령 수행Input System 입력, 테스트 전용 콘솔 명령
검증자(Verifier)기대 결과와 실제 결과 비교위치 변화량, 씬 전환, HP, UI 표시 여부
리포터(Reporter)재현 가능한 버그 증거 저장시드, 입력 시퀀스, 로그, 스크린샷

핵심은 AI가 QA 판단을 완전히 대체하는 데 있지 않다. 사람이 작성한 불변 조건과 관측 가능한 텔레메트리를 바탕으로 사람이 놓치기 쉬운 상태 조합을 빠르게 탐색하게 만드는 데 있다.

flowchart LR
    A[테스트 목표] --> B[에이전트 행동 선택]
    B --> C[게임 플레이 실행]
    C --> D[상태·로그·스크린샷 수집]
    D --> E{불변 조건 위반?}
    E -- 아니오 --> B
    E -- 예 --> F[동일 시드로 재현]
    F --> G[버그 리포트 저장]

Agentic AI가 인게임 버그를 어떻게 재현하고 검증할까?

1. 먼저 기계가 판정할 수 있는 불변 조건을 만든다

에이전트에게 “이동이 자연스러운지 확인하라”라고만 지시하면 결과가 주관적이다. 대신 클라이언트 코드에서 확인 가능한 조건을 정의한다.

  • 캐릭터의 최종 위치가 충돌체 내부에 있으면 안 된다.
  • 사망 상태에서는 이동 입력이 위치를 변경하면 안 된다.
  • 상호작용 버튼은 한 프레임에 두 번 실행되면 안 된다.
  • 씬 로딩 후 10초 안에 플레이어가 활성화되어야 한다.
  • NullReferenceException, MissingReferenceException이 발생하면 테스트를 실패 처리한다.

이 조건은 AI 프롬프트보다 먼저 코드와 테스트 훅으로 고정하는 편이 좋다. AI는 조건을 발견하는 도구가 아니라 조건을 위반할 만한 플레이 경로를 넓게 탐색하는 도구로 사용한다.

2. 행동과 관측 데이터를 같은 시드로 기록한다

버그 리포트에는 자연어 설명만 남기지 말고 재실행 가능한 입력 기록을 포함해야 한다. 절차 생성, 랜덤 드롭, AI 행동 선택처럼 난수가 개입하는 시스템은 시드도 함께 저장한다.

using System;

[Serializable]
public struct PlayTrace
{
    public int randomSeed;
    public float time;
    public string action;
    public Vector3 playerPosition;
    public string sceneName;
}

에이전트가 실패를 탐지하면 최근 입력 20~60초, 난수 시드, 플레이어 위치, 예외 로그를 하나의 증거 묶음으로 저장한다. 이 정보가 있어야 개발자가 “가끔 재현된다”는 보고 대신 동일 조건에서 문제를 확인할 수 있다.

에이전트가 입력 기록과 게임 상태 로그를 수집해 버그 리포트로 만드는 화면

3. LLM 판단은 결정권이 아니라 우선순위에 사용한다

LLM은 로그의 의미를 분류하거나 다음 탐색 경로를 제안하는 데 유용하다. 하지만 충돌 여부, 퀘스트 완료 여부, UI 활성화 여부처럼 결정적인 판정은 C# 코드의 명시적 규칙으로 처리해야 한다.

작업권장 주체이유
예외와 성능 임계치 판정코드결정적이고 비용이 낮음
최근 로그 요약LLM긴 로그에서 맥락을 빠르게 압축 가능
다음 플레이 경로 선택Agentic AI상태 조합 탐색에 유리
최종 버그 확정재현 코드와 개발자오탐 방지와 원인 분석 필요

캐릭터 이동 충돌에 Raycast를 왜 사용해야 할까?

캐릭터를 transform.position += direction * speed * deltaTime처럼 직접 옮기면 한 프레임의 이동량이 얇은 벽 두께보다 클 때 벽을 통과할 수 있다. 이동하기 전에 현재 위치에서 이동 방향으로 캐스팅해 장애물까지 거리를 확인하면 도착 위치가 아니라 이동 경로를 검사할 수 있다.

프레임당 이동 거리 d는 다음과 같다.

d=v×Δtd = v \times \Delta t

여기서 v는 초당 이동 속도이고 Δt는 프레임 간 시간이다. 레이캐스트 길이는 이동 거리보다 약간 길게 잡는다.

L=d+sL = d + s

L은 캐스팅 길이, s는 벽과 캐릭터가 맞닿아 떨리는 현상을 줄이기 위한 스킨 폭이다. 예시 출발값으로 0.01f~0.05f 같은 작은 값을 시험한 뒤 프로젝트의 월드 단위와 캐릭터 크기에 맞춰 조정한다. 이 범위는 Unity 표준값이 아니다.

Unity C#에서 이동 충돌 Raycast는 어떻게 구현할까?

다음 예시는 CharacterController 없이 간단한 캡슐형 플레이어를 이동시키는 경우다. 벽 충돌 레이어만 검사하고 이동량이 거의 0이면 Physics 쿼리를 생략한다.

이동 코드는 legacy Input Manager의 Input.GetAxisRaw를 사용한다. 프로젝트가 new Input System만 활성화했다면 입력 부분을 해당 Input System의 액션으로 바꾼다.

using UnityEngine;

public sealed class RaycastMover : MonoBehaviour
{
    [SerializeField] float speed = 5f;
    [SerializeField] float skinWidth = 0.02f;
    [SerializeField] Vector3 sphereCenterLocalOffset;
    [SerializeField] LayerMask obstacleMask;

    void Update()
    {
        Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        Vector3 direction = new Vector3(input.x, 0f, input.y).normalized;
        Vector3 displacement = direction * (speed * Time.deltaTime);

        if (displacement.sqrMagnitude < 0.000001f)
            return;

        float distance = displacement.magnitude;
        if (Physics.Raycast(transform.position, direction, out RaycastHit hit,
                distance + skinWidth, obstacleMask, QueryTriggerInteraction.Ignore))
        {
            distance = Mathf.Max(0f, hit.distance - skinWidth);
        }

        transform.position += direction * distance;
    }
}

이 코드는 충돌 직전까지만 이동시킨다. 레이캐스트 시작점이 장애물 콜라이더 안에 이미 들어가 있으면 Unity가 그 콜라이더를 감지하지 못하므로, 이 예시는 시작 겹침을 복구하지 않는다. 또한 레이는 두께가 없어 플레이어가 반경을 가진 캡슐이나 구체라면 모서리에서 통과 오차가 생길 수 있다. 구형 근사에 적합한 캐릭터라면 Physics.SphereCast로 반지름을 포함할 수 있다. 사람형 캡슐은 CapsuleCast 또는 CharacterController의 캡슐 형상을 검토한다.

다음 코드는 위 Update의 Physics.Raycast 판정과 최종 위치 적용 줄을 대체하는 예시다. origin은 구체의 중심이므로 플레이어 피벗과 충돌 형상의 중심이 다르면 오프셋을 적용한다. 예시의 반지름 0.35f와 sphereCenterLocalOffset은 실제 충돌 형상과 Transform 크기에 맞춰 설정한다. 아래 변수는 앞선 Update에서 계산한 direction, distance와 컴포넌트 필드 skinWidth, obstacleMask를 사용한다.

Vector3 sphereOrigin = transform.TransformPoint(sphereCenterLocalOffset);
float allowedDistance = distance;
if (Physics.SphereCast(
    sphereOrigin,
    0.35f,
    direction,
    out RaycastHit hit,
    distance + skinWidth,
    obstacleMask,
    QueryTriggerInteraction.Ignore))
{
    allowedDistance = Mathf.Max(0f, hit.distance - skinWidth);
}

transform.position += direction * allowedDistance;

캐릭터 이동 방향의 SphereCast가 벽 앞에서 이동 가능 거리를 제한하는 모습

Raycast와 SphereCast 중 무엇을 선택해야 할까?

방식장점주의점적합한 경우
Physics.Raycast단일 선분 경로를 간단히 검사캐릭터 폭을 반영하지 못함총알, 시선, 작은 점 기반 이동
Physics.SphereCast구체 반경을 스윕에 반영구체가 시작 위치에서 이미 콜라이더와 겹치면 그 콜라이더를 감지하지 못함구형 또는 구체 근사 이동
Physics.CapsuleCast사람형 캡슐에 가까움파라미터와 경사 처리 복잡도 증가직접 구현하는 3D 캐릭터 모터
CharacterController.Move경사와 접촉 처리 제공엔진 규칙에 맞춰야 함표준 3D 플레이어 이동

이미 CharacterController를 쓰고 있다면 별도 레이캐스트 이동기를 새로 만들 필요는 없다. CharacterController.Move를 기본으로 두고 벽 넘기나 발판 감지처럼 추가 정보가 필요한 곳에만 캐스팅을 보조적으로 사용하면 된다.

레이캐스트 충돌 연산은 어떻게 최적화할까?

1. 이동하지 않을 때는 캐스팅하지 않는다

입력값과 이동 거리 모두 0이면 쿼리도 필요 없다. 위 예시의 sqrMagnitude 조기 반환은 가장 값싼 최적화다.

2. LayerMask로 검사 대상을 줄인다

Default, Player, UI까지 모두 검사하지 말고 실제 벽과 장애물에 Obstacle 레이어를 지정해 쿼리 대상과 제외 대상을 명확히 한다. 성능 효과는 씬과 설정에 따라 다르므로 Profiler로 확인한다.

3. 할당을 만들지 않는 API를 선택한다

한 번의 이동 판정에는 단일 히트를 반환하는 Physics.Raycast 또는 Physics.SphereCast를 사용할 수 있다. 여러 충돌 결과가 꼭 필요하고 할당이 문제가 될 때는 Physics.RaycastNonAlloc과 미리 할당한 결과 배열을 검토한다. 단일 히트만 필요한 이동 코드에 모든 결과를 반환하는 RaycastAll을 쓰면 불필요한 결과 배열이 생길 수 있다.

4. 실제 병목인지 Profiler로 확인한다

레이캐스트 수가 많다고 항상 병목은 아니다. Physics Profiler에서 쿼리 수와 물리 시간, CPU Usage에서 호출 경로와 GC Alloc을 확인하고 대상 기기에서도 측정한다. 플레이어 한 명의 프레임당 1회 캐스팅보다 수백 개 AI가 매 프레임 여러 번 캐스팅하는 상황이 우선 조사 후보가 될 수 있지만, 실제 비용은 프로파일링으로 판단한다.

자주 묻는 질문 (FAQ)

Raycast 길이에 왜 skinWidth를 더하나요?

이동 거리와 정확히 같은 길이만 검사하면 부동소수점 오차나 접촉 상태 때문에 벽에 살짝 파고들 수 있다. 캐스팅 길이에 스킨 폭을 더하고 실제 이동 거리에서는 다시 빼면 안정적으로 벽 앞에 멈출 수 있다.

Agentic AI가 찾은 버그를 바로 이슈로 등록해도 되나요?

동일 시드와 입력 기록으로 최소 한 번 재현한 뒤 등록하는 것이 좋다. 재현 불가한 AI 추론 결과는 이슈가 아니라 탐색 후보로 분리해야 한다.

Update와 FixedUpdate 중 어디에서 Raycast를 실행해야 하나요?

transform.position으로 직접 이동한다면 렌더 프레임 기준인 Update에서 입력과 함께 처리한다. Rigidbody에 힘이나 속도를 적용한다면 물리 시뮬레이션 주기에 맞춰 FixedUpdate와 Rigidbody API를 사용한다. 두 방식을 같은 오브젝트에 섞지 않는 것이 중요하다.

정리

Agentic AI 버그 자동 탐지는 관측 가능하고 재현 가능한 테스트 루프가 먼저 갖춰져야 효과가 난다. 이동 충돌은 캐릭터가 이동하기 전 경로를 Raycast 또는 SphereCast로 검사하고 이동 거리와 스킨 폭을 분리해 계산하면 작은 코드로도 벽 통과와 접촉 떨림을 줄일 수 있다.

처음에는 플레이어 이동 1개와 명확한 불변 조건 3~5개부터 계측하자. 그 다음 실제 로그와 Profiler 결과가 쌓였을 때만 AI 탐색 범위와 물리 쿼리를 확장하는 편이 유지보수 비용이 낮다.