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.

Um script de inicialização de XR melhor

In Configurando XR we introduced a startup script that initializes our setup which we used as our script on our main node. This script performs the minimum steps required for any given interface.

Ao usar o OpenXR, há uma série de melhorias que devemos fazer aqui. Para isso, criamos um script de inicialização mais elaborado. Você os encontrará em uso em nossos projetos de demonstração.

Alternativamente, se você estiver usando o XR Tools (veja Introduzindo ferramentas XR), ele contém uma versão deste script atualizada com alguns recursos relacionados ao XR Tools.

Abaixo, detalharemos o script usado em nossas demonstrações e explicaremos as partes que foram adicionadas.

Sinais para o nosso script

Estamos introduzindo 3 sinais em nosso script para que nosso jogo possa adicionar mais lógica:

  • focus_lost é emitido quando o jogador tira o headset ou quando o jogador entra no sistema de menu do headset.

  • focus_gained é emitido quando o jogador coloca o headset de volta ou sai do sistema de menu e retorna ao jogo.

  • pose_recentered é emitido quando o headset solicita que a posição do jogador seja redefinida.

Nosso jogo deve reagir de acordo a esses sinais.

extends Node3D

signal focus_lost
signal focus_gained
signal pose_recentered

...

Variáveis para o nosso script

Também introduzimos algumas novas variáveis em nosso script:

  • maximum_refresh_rate will control the headset's refresh rate if this is supported by the headset.

  • xr_interface mantém uma referência à nossa interface XR; isso já existia, mas agora definimos seu tipo para obter acesso total à nossa API XRInterface.

  • xr_is_focussed será definido como verdadeiro sempre que nosso jogo tiver o foco.

...

@export var maximum_refresh_rate : int = 90

var xr_interface : OpenXRInterface
var xr_is_focussed = false

...

Nossa função ready atualizada

We add a few things to the _ready function.

Se estivermos usando o renderizador mobile ou forward+, definimos o vrs_mode da viewport para VRS_XR. Em plataformas que suportam isso, isso ativará a renderização foveada.

Se estivermos usando o renderizador de compatibilidade, verificamos se as configurações de renderização foveada do OpenXR estão configuradas e, se não estiverem, emitimos um aviso. Veja Configurações do OpenXR para mais detalhes.

Conectamos uma série de sinais que serão emitidos pela XRInterface. Forneceremos mais detalhes sobre esses sinais à medida que os implementarmos.

We also quit our application if we couldn't successfully initialize OpenXR. Now this can be a choice. If you are making a mixed mode game you setup the VR mode of your game on success, and setup the non-VR mode of your game on failure. However, when running a VR only application on a standalone headset, it is nicer to exit on failure than to hang the system.

...

# 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()

...

Ao iniciar a sessão

Este sinal é emitido pelo OpenXR quando nossa sessão é configurada. Isso significa que o headset passou pela configuração de tudo e está pronto para começar a receber conteúdo de nós. Somente neste momento várias informações ficam devidamente disponíveis.

A principal coisa que fazemos aqui é verificar a taxa de atualização do nosso headset. Também verificamos as taxas de atualização disponíveis relatadas pelo tempo de execução do XR para determinar se queremos definir nosso headset para uma taxa de atualização mais alta.

Finalmente, igualamos nossa taxa de atualização de física à taxa de atualização do headset. O Godot roda a uma taxa de atualização de física de 60 atualizações por segundo por padrão, enquanto os headsets rodam a um mínimo de 72 e, para headsets modernos, frequentemente até 144 quadros por segundo. Não igualar a taxa de atualização de física causará trepidações (stuttering), pois os quadros são renderizados sem que os objetos se movam.

...

# 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

...

No estado visível

Este sinal é emitido pelo OpenXR quando nosso jogo se torna visível, mas não está focado. Esta é uma descrição um pouco estranha no OpenXR, mas basicamente significa que nosso jogo acabou de começar e estamos prestes a mudar para o estado focado a seguir, que o usuário abriu um menu do sistema ou que o usuário acabou de tirar o headset.

Ao receber este sinal, atualizaremos nosso estado de foco, mudaremos o modo de processamento do nosso nó para desativado, o que pausará o processamento neste nó e em seus filhos, e emitiremos nosso sinal focus_lost.

Se você adicionou este script ao seu nó raiz, isso significa que seu jogo pausará automaticamente quando necessário. Se não adicionou, você pode conectar um método ao sinal que realiza alterações adicionais.

Nota

Enquanto seu jogo estiver no estado visível porque o usuário abriu um menu do sistema, o Godot continuará renderizando os quadros e o rastreamento da cabeça permanecerá ativo, de modo que seu jogo continuará visível em segundo plano. No entanto, o rastreamento de controles e mãos será desativado até que o usuário saia do menu do sistema.

...

# 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")

...

On focused state

Este sinal é emitido pelo OpenXR quando nosso jogo ganha foco. Isso é feito na conclusão da nossa inicialização, mas também pode ser emitido quando o usuário sai de um menu do sistema ou coloca o headset de volta.

Observe também que quando seu jogo começa enquanto o usuário não está usando o headset, o jogo permanece no estado 'visível' até que o usuário coloque o headset.

Aviso

Portanto, é importante manter seu jogo pausado enquanto estiver no modo visível. Se você não fizer isso, o jogo continuará rodando enquanto o usuário não estiver interagindo com ele. Além disso, quando o jogo retorna ao modo focado, de repente todo o rastreamento de controles e mãos é reativado e pode ter consequências que quebram o jogo se você não reagir a isso adequadamente. Certifique-se de testar esse comportamento em seu jogo!

Ao tratar nosso sinal, atualizaremos o estado de foco, despausaremos nosso nó e emitiremos nosso sinal 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")

...

No estado de parada

Este sinal é emitido pelo OpenXR quando entramos em nosso estado de parada. Existem algumas diferenças entre as plataformas sobre quando isso acontece. Em algumas plataformas, isso só é emitido quando o jogo está sendo fechado. Mas em outras plataformas, isso também será emitido toda vez que o jogador tirar o headset.

Por enquanto, este método é apenas um marcador de posição (placeholder).

...

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

...

Na pose recentralizada

Este sinal é emitido pelo OpenXR quando o usuário solicita que sua visão seja recentralizada. Basicamente, isso comunica ao seu jogo que o usuário agora está olhando para frente e você deve reorientar o jogador para que ele fique voltado para frente no mundo virtual.

Como fazer isso depende do seu jogo, seu jogo precisa reagir de acordo.

Tudo o que fazemos aqui é emitir o sinal pose_recentered. Você pode se conectar a este sinal e implementar o código de recentralização real. Frequentemente, basta chamar 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")

And that finished our script. It was written so that it can be reused over multiple projects. Just add it as the script on your main node (and extend it if needed) or add it on a child node specific for this script.