Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

더 나은 XR 시작 스크립트

:ref:`doc_setting_up_xr`에서는 메인 노드에서 스크립트로 사용한 설정을 초기화하는 스타트업 스크립트를 소개했습니다. 이 스크립트는 특정 인터페이스에 필요한 최소 단계를 수행합니다.

OpenXR을 사용할 때 여기에서 수행해야 할 여러 가지 개선 사항이 있습니다. 이를 위해 우리는 보다 정교한 시작 스크립트를 만들었습니다. 우리의 데모 프로젝트에서 이러한 것들이 사용되는 것을 발견하게 될 것입니다.

또는 XR 도구(XR 툴 소개 참조)를 사용하는 경우 XR 도구와 관련된 일부 기능으로 업데이트된 이 스크립트 버전이 포함되어 있습니다.

아래에서는 데모에 사용된 스크립트에 대해 자세히 설명하고 추가된 부분에 대해 설명하겠습니다.

스크립트에 대한 시그널

우리는 게임에 로직을 추가할 수 있도록 스크립트에 3개의 시그널를 도입합니다.

  • ``focus_lost``는 플레이어가 헤드셋을 벗거나 플레이어가 헤드셋의 메뉴 시스템에 들어갈 때 방출됩니다.

  • ``focus_gained``는 플레이어가 헤드셋을 다시 착용하거나 메뉴 시스템을 종료하고 게임으로 돌아갈 때 발생합니다.

  • ``pose_recentered``는 헤드셋이 플레이어의 위치 재설정을 요청할 때 발생합니다.

우리 게임은 이러한 시그널에 따라 반응해야 합니다.

extends Node3D

signal focus_lost
signal focus_gained
signal pose_recentered

...

우리 스크립트에 대한 변수

스크립트에도 몇 가지 새로운 변수를 도입했습니다.

  • ``maximum_refresh_rate``는 헤드셋에서 지원하는 경우 헤드셋 새로 고침 빈도를 제어합니다.

  • ``xr_interface``는 XR 인터페이스에 대한 참조를 보유하고 있습니다. 이는 이미 존재했지만 이제 XRInterface API에 대한 전체 액세스 권한을 얻기 위해 이를 입력합니다.

  • ``xr_is_focussed``는 게임에 포커스가 있을 때마다 true로 설정됩니다.

...

@export var maximum_refresh_rate : int = 90

var xr_interface : OpenXRInterface
var xr_is_focussed = false

...

기타 함수

다음과 같은 주의 사항이 있습니다:

모바일 또는 포워드+ 렌더러를 사용하는 경우 뷰포트의 ``vrs_mode``를 ``VRS_XR``로 설정합니다. 이를 지원하는 플랫폼에서는 포비티드 렌더링이 활성화됩니다.

호환성 렌더러를 사용하는 경우 OpenXR 포비티드 렌더링 설정이 구성되어 있는지 확인하고 구성되어 있지 않으면 경고를 출력합니다. 자세한 내용은 :ref:`OpenXR 설정 <doc_openxr_settings>`을 참조하세요.

:ref:`XRInterface <class_xrinterface>`에서 방출될 다수의 시그널를 연결합니다. 이러한 시그널를 구현하면서 이에 대한 자세한 내용을 제공하겠습니다.

OpenXR을 성공적으로 초기화할 수 없는 경우에도 애플리케이션을 종료합니다. 이제 이것은 선택이 될 수 있습니다. 혼합 모드 게임을 만드는 경우 성공하면 게임의 VR 모드를 설정하고, 실패하면 게임의 비VR 모드를 설정합니다. 하지만 독립형 헤드셋에서 VR 전용 애플리케이션을 실행할 때는 시스템을 멈추는 것보다 실패하면 종료하는 것이 더 좋습니다.

...

# Called when the node enters the scene tree for the first time.
func _ready():
    xr_interface = XRServer.find_interface("OpenXR")
    if xr_interface and xr_interface.is_initialized():
        print("OpenXR instantiated successfully.")
        var vp : Viewport = get_viewport()

        # Enable XR on our viewport
        vp.use_xr = true

        # Make sure v-sync is off, v-sync is handled by OpenXR
        DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

        # Enable VRS
        if RenderingServer.get_rendering_device():
            vp.vrs_mode = Viewport.VRS_XR
        elif int(ProjectSettings.get_setting("xr/openxr/foveation_level")) == 0:
            push_warning("OpenXR: Recommend setting Foveation level to High in Project Settings")

        # Connect the OpenXR events
        xr_interface.session_begun.connect(_on_openxr_session_begun)
        xr_interface.session_visible.connect(_on_openxr_visible_state)
        xr_interface.session_focussed.connect(_on_openxr_focused_state)
        xr_interface.session_stopping.connect(_on_openxr_stopping)
        xr_interface.pose_recentered.connect(_on_openxr_pose_recentered)
    else:
        # We couldn't start OpenXR.
        print("OpenXR not instantiated!")
        get_tree().quit()

...

세션 시작 시

이 시그널는 세션이 설정될 때 OpenXR에서 방출됩니다. 이는 헤드셋이 모든 설정을 완료했으며 우리로부터 콘텐츠를 수신할 준비가 되었음을 의미합니다. 이때에만 다양한 정보가 제대로 제공됩니다.

여기서 가장 중요한 일은 헤드셋의 새로 고침 빈도를 확인하는 것입니다. 또한 XR 런타임에서 보고된 사용 가능한 새로 고침 빈도를 확인하여 헤드셋을 더 높은 새로 고침 빈도로 설정할지 결정합니다.

마지막으로 물리 업데이트 속도를 헤드셋 업데이트 속도와 일치시킵니다. Godot는 기본적으로 초당 60 업데이트의 물리 업데이트 속도로 실행되는 반면 헤드셋은 최소 72 업데이트, 최신 헤드셋의 경우 초당 최대 144 프레임까지 실행되는 경우가 많습니다. 물리 업데이트 속도가 일치하지 않으면 객체가 움직이지 않고 프레임이 렌더링되므로 끊김 현상이 발생합니다.

...

# Handle OpenXR session ready
func _on_openxr_session_begun() -> void:
    # Get the reported refresh rate
    var current_refresh_rate = xr_interface.get_display_refresh_rate()
    if current_refresh_rate > 0:
        print("OpenXR: Refresh rate reported as ", str(current_refresh_rate))
    else:
        print("OpenXR: No refresh rate given by XR runtime")

    # See if we have a better refresh rate available
    var new_rate = current_refresh_rate
    var available_rates : Array = xr_interface.get_available_display_refresh_rates()
    if available_rates.size() == 0:
        print("OpenXR: Target does not support refresh rate extension")
    elif available_rates.size() == 1:
        # Only one available, so use it
        new_rate = available_rates[0]
    else:
        for rate in available_rates:
            if rate > new_rate and rate <= maximum_refresh_rate:
                new_rate = rate

    # Did we find a better rate?
    if current_refresh_rate != new_rate:
        print("OpenXR: Setting refresh rate to ", str(new_rate))
        xr_interface.set_display_refresh_rate(new_rate)
        current_refresh_rate = new_rate

    # Now match our physics rate
    Engine.physics_ticks_per_second = current_refresh_rate

...

보이는 상태에서

이 시그널는 게임이 표시되지만 초점이 맞춰지지 않을 때 OpenXR에서 방출됩니다. 이것은 OpenXR의 약간 이상한 설명이지만 기본적으로 게임이 막 시작되었고 다음에 초점이 맞춰진 상태로 전환하려고 한다는 것, 사용자가 시스템 메뉴를 열었거나 사용자가 방금 헤드셋을 벗었다는 것을 의미합니다.

이 시그널를 수신하면 초점 상태를 업데이트하고 노드의 프로세스 모드를 비활성화로 변경하여 이 노드 및 해당 자식 노드에 대한 처리를 일시 중지하고 focus_lost 시그널를 내보냅니다.

이 스크립트를 루트 노드에 추가했다면 필요할 때 게임이 자동으로 일시 중지된다는 의미입니다. 아직 연결하지 않은 경우 추가 변경을 수행하는 시그널에 메소드를 연결할 수 있습니다.

참고

사용자가 시스템 메뉴를 열었기 때문에 게임이 보이는 상태에 있는 동안, Godot는 프레임 렌더링을 계속하고 머리 추적이 활성 상태로 유지되므로 게임이 배경에 계속 표시됩니다. 그러나 사용자가 시스템 메뉴를 종료할 때까지 컨트롤러와 손 추적이 비활성화됩니다.

...

# Handle OpenXR visible state
func _on_openxr_visible_state() -> void:
    # We always pass this state at startup,
    # but the second time we get this it means our player took off their headset
    if xr_is_focussed:
        print("OpenXR lost focus")

        xr_is_focussed = false

        # pause our game
        get_tree().paused = true

        emit_signal("focus_lost")

...

집중된 상태에서

이 시그널는 게임에 포커스가 있을 때 OpenXR에서 방출됩니다. 이는 시작이 완료될 때 수행되지만 사용자가 시스템 메뉴를 종료하거나 헤드셋을 다시 착용할 때 발생할 수도 있습니다.

또한 사용자가 헤드셋을 착용하지 않은 동안 게임이 시작되면 사용자가 헤드셋을 착용할 때까지 게임은 '표시' 상태로 유지됩니다.

경고

따라서 표시 모드에서는 게임을 일시 중지 상태로 유지하는 것이 중요합니다. 그렇지 않으면 사용자가 게임과 상호 작용하지 않는 동안 게임이 계속 실행됩니다. 또한 게임이 집중 모드로 돌아가면 갑자기 모든 컨트롤러와 손 추적이 다시 활성화되고 그에 따라 반응하지 않으면 게임이 중단되는 결과를 초래할 수 있습니다. 게임에서 이 동작을 테스트해 보세요!

시그널를 처리하는 동안 포커스 상태를 업데이트하고 노드의 일시 중지를 해제하고 focus_gained 시그널를 내보냅니다.

...

# Handle OpenXR focused state
func _on_openxr_focused_state() -> void:
    print("OpenXR gained focus")
    xr_is_focussed = true

    # unpause our game
    get_tree().paused = false

    emit_signal("focus_gained")

...

시작하기

이 시그널는 중지 상태에 들어갈 때 OpenXR에서 방출됩니다. 이 경우 플랫폼마다 약간의 차이가 있습니다. 일부 플랫폼에서는 게임이 종료될 때만 발생합니다. 그러나 다른 플랫폼에서는 플레이어가 헤드셋을 벗을 때마다 이 소리도 방출됩니다.

현재 이 메서드는 자리 표시자일 뿐입니다.

...

# Handle OpenXR stopping state
func _on_openxr_stopping() -> void:
    # Our session is being stopped.
    print("OpenXR is stopping")

...

포즈를 중심으로 한 상태

이 시그널는 사용자가 뷰를 중앙으로 정렬하도록 요청할 때 OpenXR에서 내보냅니다. 기본적으로 이는 사용자가 현재 앞을 향하고 있음을 게임에 전달하며 플레이어가 가상 세계에서 앞을 향하도록 방향을 변경해야 합니다.

그렇게 하는 것은 게임에 따라 다르므로 게임도 이에 따라 반응해야 합니다.

여기서 우리가 하는 일은 pose_recentered 시그널를 내보내는 것 뿐입니다. 이 시그널에 연결하여 실제 최신 코드를 구현할 수 있습니다. :ref:`center_on_hmd() <class_XRServer_method_center_on_hmd>`을 호출하는 것만으로도 충분할 때가 많습니다.

...

# Handle OpenXR pose recentered signal
func _on_openxr_pose_recentered() -> void:
    # User recentered view, we have to react to this by recentering the view.
    # This is game implementation dependent.
    emit_signal("pose_recentered")

이것으로 스크립트가 끝났습니다. 여러 프로젝트에서 재사용할 수 있도록 작성되었습니다. 기본 노드에 스크립트로 추가하거나(필요한 경우 확장) 이 스크립트에 특정한 자식 노드에 추가하세요.