빠른 총알이 벽을 뚫고 지나가는 이유
Esc

    빠른 총알이 벽을 뚫고 지나가는 이유

    –Unity기초

    총알에 Rigidbody(물체를 물리 엔진에 맡기는 컴포넌트)를 붙이고 빠른 속도를 줬더니, 얇은 벽을 그냥 통과합니다. 천천히 쏘면 잘 막히는데, 빠르게 쏘면 가끔 뚫고 아주 빠르면 거의 늘 뚫습니다. 이렇게 부딪혀야 할 벽이나 바닥을 건너뛰어 지나가는 것을 터널링이라고 부릅니다.

    물리 엔진(유니티에서 중력 · 충돌을 계산하는 부분)은 0.02초마다 그 순간의 자리에서 겹쳤는지를 봅니다. 그 사이에 어디를 지나갔는지는 보지 않습니다. 한 걸음에 가는 거리가 벽 두께보다 크면, 이번 걸음은 벽 앞, 다음 걸음은 벽 뒤에 있어서 어느 순간에도 벽과 겹치지 않습니다. 엔진은 부딪힌 적이 없다고 보고 그대로 보냅니다.

    한 걸음의 거리 = 속도 × 0.02초 입니다. 이것이 벽 두께와 물체 크기를 더한 것보다 크면 뚫릴 수 있습니다. Rigidbody 의 Collision Detection 을 Continuous 계열로 바꾸거나, 이번 걸음에 갈 길을 레이캐스트로 검사해 그 사이를 메웁니다.

    한 걸음에 얼마나 가나

    물리는 0.02초마다 한 걸음씩 계산합니다. 이 한 걸음의 길이가 Time.fixedDeltaTime(기본 0.02초, 1초에 50걸음)이고, 걸음마다 FixedUpdate 함수가 불립니다. 속도는 1초에 가는 거리라 5m/s 면 1초에 5m 를 가고, 한 걸음에 가는 거리는 속도 × 0.02 입니다. 걷는 캐릭터(5m/s)는 5 × 0.02 = 0.1m, 총알(300m/s)은 300 × 0.02 = 6m 입니다.

    두께 0.4m 벽에 크기 0.1m 물체를 보낸다고 합시다. 물체의 가운데가 벽 앞면보다 0.05m(물체 크기의 절반) 앞에서부터 벽 뒷면보다 0.05m 뒤까지에 있으면, 물체 끝이 벽에 걸쳐 겹칩니다. 그 구간의 폭이 0.05 + 0.4 + 0.05 = 0.5m, 곧 벽 두께 + 물체 크기 입니다. 한 걸음이 이 0.5m 안에 떨어지면 막히고, 0.5m 를 통째로 건너뛰면 뚫립니다. 걸음이 어디서 시작했는지는 매번 다르니, 막힐 확률은 대략 0.5 ÷ 한 걸음 입니다.

    무엇 속도 (m/s) 한 걸음에 막힐 확률 (0.4m 벽, 0.1m 물체)
    걷는 캐릭터 5 0.1m 늘 막힘 (걸음이 0.5m 보다 짧음)
    떨어지는 공 (3초 낙하) 30 0.6m 0.5 ÷ 0.6 ≈ 83%
    화살 60 1.2m 0.5 ÷ 1.2 ≈ 42%
    총알 300 6m 0.5 ÷ 6 ≈ 8%

    천천히 쏘면 막히고, 빠르면 가끔, 아주 빠르면 거의 늘 뚫리는 것이 이 표 그대로입니다. 떨어지는 공은 처음엔 느리다가 중력으로 1초에 9.81m/s 씩 빨라지니(3초면 약 30m/s), 높은 곳에서 떨어질수록 바닥 근처에서 한 걸음이 커집니다. 잘 되다가 어느 높이부터 뚫리는 이유입니다.

    벽 앞과 벽 뒤만 보면 부딪힌 적이 없다

    1초에 300m 로 날아가는 총알과, 8m 앞의 두께 0.4m 벽입니다. 한 걸음에 6m 를 갑니다. 걸음마다 그 순간 두 물체가 서로 파고들어 있는지만 보는 것을 겹침 검사라고 하고, 물리 엔진의 기본 충돌 검사입니다.

    자리만 보는 방식에서 막히려면, 엔진이 본 자리 중 하나가 겹치는 구간 안에 있어야 합니다. 이 벽에 크기 0.1m 총알이면 가운데가 7.95m 에서 8.45m 사이인 0.5m 구간입니다. 0m, 6m, 12m 는 모두 이 구간 밖이라 막히지 않았습니다. 한 걸음이 0.5m 보다 길면 이렇게 구간을 통째로 건너뛸 수 있고, 속도로 바꾸면 0.5 ÷ 0.02 = 25m/s 를 넘을 때부터 뚫릴 수 있습니다.

    Rigidbody 의 Collision Detection 바꾸기

    Rigidbody 의 Collision Detection 칸이 걸음 사이를 볼지 정합니다. Continuous 계열을 통틀어 연속 충돌 검사(CCD)라고 부릅니다.

    충돌은 눈에 보이는 메시가 아니라 따로 붙인 콜라이더로 따집니다. 콜라이더는 상자(Box) · 공(Sphere) · 캡슐(Capsule) 같은 충돌용 모양이고, Rigidbody 없이 콜라이더만 붙어 벽 · 바닥처럼 움직이지 않는 것을 정적 콜라이더라고 부릅니다.

    방식은 둘입니다. 스윕은 위 그림처럼 콜라이더 모양을 이번 걸음의 길을 따라 밀어 보며 처음 닿는 곳을 찾습니다. **추측(speculative)**은 다음 걸음에 갈 만큼 검사 범위를 넓혀, 그 안에 있는 것들을 미리 접촉 후보로 넣어 둡니다.

    설정 하는 일 비용
    Discrete (기본) 걸음마다 그 자리만 겹침 검사 가장 쌈
    Continuous 정적 콜라이더(벽 · 바닥)에 대해서만 스윕. 다른 Rigidbody 와는 Discrete 비쌈
    Continuous Dynamic 정적 콜라이더에 더해, Continuous 나 Continuous Dynamic 인 Rigidbody 와도 스윕 가장 비쌈
    Continuous Speculative 정적 콜라이더와 Rigidbody 모두에 추측 방식. 회전도 셈에 넣음 스윕보다 쌈

    어떻게 고르나는 이렇습니다.

    • 빠른 것이 벽 · 바닥에 부딪힌다면 그 빠른 것을 Continuous 로
    • 빠른 것끼리 부딪힌다면 빠른 쪽을 Continuous Dynamic 으로, 부딪힐 상대를 Continuous 로
    • 빠르게 도는 것(돌아가는 문, 휘두르는 막대)이나 Kinematic(힘을 안 받고 코드로만 움직이는 Rigidbody)이면 Continuous Speculative. 스윕은 곧게 미는 것만 검사해서 회전으로 생기는 충돌을 못 잡고, Kinematic 에는 Speculative 만 쓸 수 있습니다

    장단점도 있습니다.

    • 비용. 모든 Rigidbody 를 연속 검사로 바꾸면 물리 비용이 크게 늡니다. 빠른 물체 여럿이 가까이 몰려 있으면 스윕 횟수가 빠르게 늘어납니다. 빠른 것만 바꿉니다
    • Speculative 의 부작용. 미리 넣어 둔 후보 때문에 닿기도 전에 튕기거나 모서리에 걸리는 일(유령 충돌)이 생기고, 후보를 걸음 처음에 한 번만 모으니 그 걸음 동안 새로 생긴 접촉은 놓쳐 여전히 뚫릴 수 있습니다

    레이캐스트로 직접 메우기

    총알처럼 아주 빠르고 많은 것은 Rigidbody 대신 레이캐스트로 처리하는 경우가 많습니다. 레이캐스트는 한 점에서 한 방향으로 정한 길이만큼 보이지 않는 선을 쏘아, 처음 닿는 콜라이더를 찾는 함수입니다. 이번 걸음에 갈 길 전체를 선으로 검사하면 사이를 건너뛰는 일이 없습니다. 위 그림의 Continuous 와 같은 생각을 직접 하는 것입니다.

    public float speed = 300f;
    Vector3 velocity;
    
    void Start()
    {
        velocity = transform.forward * speed;
    }
    
    void FixedUpdate()
    {
        // 이번 걸음에 갈 화살표. 속도 × 시간
        Vector3 move = velocity * Time.fixedDeltaTime;
    
        // 지금 자리에서 이번 걸음의 끝까지 선으로 검사한다
        bool hitSomething = Physics.Raycast(transform.position, move.normalized, out RaycastHit hit, move.magnitude);
    
        if (hitSomething)
        {
            // 닿은 자리에서 멈춤
            transform.position = hit.point;
            OnHit(hit);
            return;
        }
    
        transform.position += move;
    }

    move.normalized 가 쏘는 방향(길이 1 로 줄인 화살표), move.magnitude 가 이번 걸음의 길이(6m)라서, 이번 걸음에 지나갈 거리 안에 있는 것만 잡습니다. 선은 굵기가 없어서 크기가 있는 물체라면 Physics.SphereCast(공을 밀어 보는 검사)를 씁니다. 총알 자신에게 콜라이더가 있으면 자기 자신을 맞히지 않게 레이어 마스크로 거릅니다.

    그 밖의 방법

    방법 효과 치르는 것
    벽과 바닥 콜라이더를 두껍게 위의 0.5m 가 넓어져 뚫리는 속도가 올라감 보이는 벽과 충돌 벽이 달라짐
    최고 속도를 제한 한 걸음이 일정 이하 게임 느낌이 바뀜
    fixedDeltaTime 을 줄임 모든 물체의 한 걸음이 짧아짐 물리 계산이 늘어남. 0.01 이면 두 배
    Continuous 계열 빠른 것만 정확히 그 물체의 검사 비용
    레이캐스트 아주 빠른 것도 정확히 충돌 처리를 직접 해야 함

    정리

    • 물리는 걸음마다 그 순간의 자리만 보고 겹쳤는지 검사합니다. 사이에 지나간 길은 보지 않습니다
    • 한 걸음의 거리 = 속도 × fixedDeltaTime 이 벽 두께 + 물체 크기보다 크면 벽을 통째로 건너뛸 수 있습니다. 0.4m 벽과 0.1m 물체면 25m/s 부터입니다
    • 떨어지는 물체는 점점 빨라져서, 어느 높이부터 바닥을 뚫기 시작합니다
    • Continuous 는 정적 콜라이더에 대해, Continuous Dynamic 은 연속 검사 Rigidbody 에 대해서도 지나간 길을 스윕합니다. Continuous Speculative 는 더 싸고 회전과 Kinematic 에 쓰지만 유령 충돌이 생길 수 있습니다
    • 연속 검사는 비싸니 빠른 것만 바꿉니다
    • 아주 빠른 총알은 이번 걸음의 길이만큼 레이캐스트를 쏘아 처리합니다