Cursor FiveM preso ou não pode se mover após fechar um menu: correção de foco NUI

Cursor preso na tela ou não pode se mover após fechar um menu? Como SetNuiFocus funciona, fechando com um retorno de chamada NUI, tratando ESC em JavaScript e encontrando o culpado.

Você fecha um menu, e uma destas coisas acontece:

text
the mouse cursor stays on screen
the character will not move or look around
the keyboard does nothing until you reconnect

Nada aparece no console, porque nada está errado com o código que funciona: ele apenas nunca devolveu o foco. Aqui está como o foco funciona, como fechar um menu corretamente e como encontrar o culpado.

Como o foco NUI funciona

Uma NUI é uma página web (HTML, CSS e JavaScript) que FiveM desenha sobre o jogo. Enquanto tem foco, mouse e teclado vão para a página em vez do jogo. Um script dá e tira foco com um nativo:

lua
SetNuiFocus(hasFocus, hasCursor)
  • SetNuiFocus(true, true): a NUI recebe o teclado e o mouse, e o cursor aparece.
  • SetNuiFocus(true, false): a NUI recebe o teclado, sem cursor.
  • SetNuiFocus(false, false): o foco volta para o jogo e o cursor se esconde.

Se um menu abre com true, true e nenhum código jamais chama SetNuiFocus(false, false), o jogo fica travado, mesmo se a página estiver oculta.

A correção: sempre feche com SetNuiFocus(false, false)

Coloque o fechamento em uma função e use-a para cada maneira de fechar o menu:

lua
local open = false

local function openMenu()
    open = true
    SetNuiFocus(true, true)
    SendNUIMessage({ action = 'open' })
end

local function closeMenu()
    open = false
    SetNuiFocus(false, false)
    SendNUIMessage({ action = 'close' })
end

Faça o mesmo se o menu pode ser fechado por outro código: quando o jogador morre, entra em um veículo, ou quando o recurso para.

lua
AddEventHandler('onResourceStop', function(resource)
    if resource == GetCurrentResourceName() and open then
        SetNuiFocus(false, false)
    end
end)

Este último importa. Se um recurso para enquanto seu menu está aberto, o foco não é devolvido para você, e o jogador fica preso até se reconectar.

Feche da página: RegisterNUICallback e fetch

Um botão de fechamento, ou um pressionamento de tecla dentro da página, funciona em JavaScript. JavaScript não pode chamar um nativo, então envia uma solicitação para o lado Lua, que a manipula:

lua
RegisterNUICallback('close', function(data, cb)
    closeMenu()
    cb('ok')
end)

Em sua página, envie a solicitação com fetch para https://<resource name>/close. GetParentResourceName() lhe dá o nome do recurso dentro de uma página NUI:

js
function closeMenu() {
  fetch(`https://${GetParentResourceName()}/close`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json; charset=UTF-8' },
    body: JSON.stringify({}),
  })
}

document.getElementById('close-button').addEventListener('click', closeMenu)

O retorno de chamada deve chamar cb. Se nunca responder, a solicitação na página fica aberta, e algumas bibliotecas param de esperar por ela. Sempre termine um retorno de chamada com cb('ok') ou outra resposta.

ESC e outras teclas em JavaScript

Enquanto a NUI tem foco, o jogo não vê o teclado, então a tecla ESC nunca chega ao seu código Lua. A página tem que escutá-la:

js
window.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    closeMenu()
  }
})

Adicione a mesma verificação para qualquer outra tecla que deve fechar o menu. Se a página tem uma caixa de entrada, pense no que a tecla faz lá: ESC em um campo de texto ainda deve fechar o menu, mas uma letra não deve.

Atenção: não confie apenas em um botão de fechamento. Se um jogador pressiona ESC e a página não tem um listener, o menu de pausa pode abrir, e o menu fica na tela sem nenhuma maneira de fechá-lo.

Caminhar enquanto um menu está aberto

Algumas interfaces, como um HUD com um pequeno painel ou um telefone, devem deixar o jogador continuar se movendo. Para isso use SetNuiFocusKeepInput:

lua
SetNuiFocus(true, true)
SetNuiFocusKeepInput(true)

Com entrada mantida, o jogo ainda recebe o teclado enquanto a NUI tem foco. Se você o ativar, desligue quando o menu fecha, porque fica ligado até você fazer:

lua
local function closeMenu()
    SetNuiFocusKeepInput(false)
    SetNuiFocus(false, false)
end

Um SetNuiFocusKeepInput(true) restante dá sintomas estranhos: o cursor desaparece mas algumas teclas se comportam de forma estranha ou não fazem nada.

Encontre o recurso que segura o foco

Frequentemente o menu preso não é seu. Quando você não sabe qual recurso pegou o foco, encontre-o por eliminação:

  1. Reproduza o problema: abra o menu, feche-o, e veja o cursor preso.
  2. No console do servidor (ou no Console ao Vivo do txAdmin), digite restart resource_name para um recurso que tem um menu: um telefone, inventário, loja, menu radial ou menu de roupas.
  3. Verifique se o cursor e os controles voltam ou se o problema não acontece mais.
  4. Repita para o próximo recurso até que o comportamento mude.

Quando uma reinicialização devolve o foco, esse script o segura. Procure em seu código por um caminho de fechamento que não chame SetNuiFocus(false, false): um erro antes da linha de fechamento, uma morte ou uma mudança de trabalho que fecha apenas o HTML, ou um retorno de chamada que não responde. Um erro no console no momento do bug é uma dica forte: veja como ler um erro de script FiveM.

Se é um script que você comprou, envie ao seu autor os passos exatos e o nome do recurso que você encontrou.

Lista de verificação

Sintoma Solução
Cursor fica após fechar Chame SetNuiFocus(false, false) na função de fechamento
Botão de fechamento não faz nada Adicione RegisterNUICallback('close', ...) e um fetch para https://<resource>/close
ESC não fecha o menu Escute Escape em JavaScript e chame o retorno de chamada de fechamento
Preso após reinicialização de recurso Libere o foco em onResourceStop
Personagem não pode se mover após fechar Desligue SetNuiFocusKeepInput(false) ao fechar
Não sabe qual script faz isso Reinicie recursos um por um até que o cursor volte
Retorno de chamada trava Sempre responda com cb('ok')

Respostas rápidas

Por que meu cursor fica na tela após eu fechar um menu?

O menu ocultou seu HTML, mas nada disse ao jogo para devolver o foco. O script tem que chamar SetNuiFocus(false, false) quando o menu fecha.

O que SetNuiFocusKeepInput faz?

SetNuiFocusKeepInput(true) permite que o jogo receba entrada de teclado enquanto uma NUI tem foco, para que o jogador ainda possa caminhar. Deve ser desligado novamente quando o menu fecha.

Como encontro qual recurso mantém o foco?

Reinicie seus recursos um de cada vez e verifique após cada um se o cursor e os controles voltam. Aquele cuja reinicialização o corrige é aquele que mantém o foco.

Scripts sem esse problema

Mic PhoneUm celular dobrável que abre em tablet e chega ao celular de verdade do jogador.Ver script →Arcade MachinesSete jogos de fliperama jogáveis em máquinas de verdade, com rankings e apostas.Ver script →Clothing DesignerCrie roupas dentro do FiveM — pincel, camadas, importação de imagens e IA — e vista na hora.Ver script →

Continue lendo