Cursor atrapado en FiveM o no se mueve después de cerrar un menú: corrección de enfoque NUI

¿Cursor atrapado en la pantalla o no se mueve después de cerrar un menú? Cómo funciona SetNuiFocus, cerrar con un callback de NUI, manejar ESC en JavaScript y encontrar al culpable.

Cierras un menú, y una de estas cosas sucede:

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

Nada aparece en la consola, porque nada está mal con el código que se ejecuta: simplemente nunca devolvió el enfoque. Aquí está cómo funciona el enfoque, cómo cerrar un menú correctamente y cómo encontrar al culpable.

Cómo funciona el enfoque de NUI

Una NUI es una página web (HTML, CSS y JavaScript) que FiveM dibuja sobre el juego. Mientras tenga enfoque, el mouse y el teclado van a la página en lugar del juego. Un script da y toma el enfoque con un nativo:

lua
SetNuiFocus(hasFocus, hasCursor)
  • SetNuiFocus(true, true): la NUI recibe el teclado y el mouse, y el cursor se muestra.
  • SetNuiFocus(true, false): la NUI recibe el teclado, sin cursor.
  • SetNuiFocus(false, false): el enfoque vuelve al juego y el cursor se oculta.

Si un menú se abre con true, true y ningún código jamás llama a SetNuiFocus(false, false), el juego permanece bloqueado, incluso si la página está oculta.

La solución: siempre cierra con SetNuiFocus(false, false)

Pon el cierre en una función y úsalo para cada forma de cerrar el menú:

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

Haz lo mismo si el menú puede ser cerrado por otro código: cuando el jugador muere, entra en un vehículo, o cuando el recurso se detiene.

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

Este último importa. Si un recurso se detiene mientras su menú está abierto, el enfoque no se devuelve por ti, y el jugador queda atrapado hasta que se reconecte.

Cierra desde la página: RegisterNUICallback y fetch

Un botón de cierre, o una pulsación de tecla dentro de la página, se ejecuta en JavaScript. JavaScript no puede llamar a un nativo, así que envía una solicitud al lado de Lua, que la maneja:

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

En tu página, envía la solicitud con fetch a https://<nombre de recurso>/close. GetParentResourceName() te da el nombre del recurso dentro de una 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)

El callback debe llamar a cb. Si nunca responde, la solicitud en la página permanece abierta, y algunas librerías dejan de esperar. Siempre termina un callback con cb('ok') u otra respuesta.

ESC y otras teclas en JavaScript

Mientras la NUI tiene enfoque, el juego no ve el teclado, así que la tecla ESC nunca llega a tu código Lua. La página tiene que escucharla:

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

Añade la misma verificación para cualquier otra tecla que deba cerrar el menú. Si la página tiene un cuadro de entrada, piensa en lo que la tecla hace allí: ESC en un campo de texto aún debe cerrar el menú, pero una letra no debe.

Cuidado: no confíes únicamente en un botón de cierre. Si un jugador presiona ESC y la página no tiene un listener, el menú de pausa puede abrirse, y el menú permanece en la pantalla sin forma de cerrarlo.

Caminar mientras un menú está abierto

Algunas interfaces, como un HUD con un pequeño panel o un teléfono, deberían permitir que el jugador siga moviéndose. Para eso usa SetNuiFocusKeepInput:

lua
SetNuiFocus(true, true)
SetNuiFocusKeepInput(true)

Con la entrada de mantenimiento, el juego sigue recibiendo el teclado mientras la NUI tiene enfoque. Si lo enciende, apágalo cuando se cierre el menú, porque permanece encendido hasta que lo hagas:

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

Un SetNuiFocusKeepInput(true) restante da síntomas extraños: el cursor se fue pero algunas teclas se comportan de manera extraña o no hacen nada.

Encuentra el recurso que mantiene el enfoque

A menudo el menú atrapado no es tuyo. Cuando no sabes qué recurso tomó el enfoque, encuéntralo por eliminación:

  1. Reproduce el problema: abre el menú, ciérralo, y ve el cursor atrapado.
  2. En la consola del servidor (o la Consola en vivo de txAdmin), escribe restart resource_name para un recurso que tiene un menú: un teléfono, inventario, tienda, menú radial o menú de ropa.
  3. Verifica si el cursor y los controles vuelven o si el problema ya no ocurre.
  4. Repite para el siguiente recurso hasta que el comportamiento cambie.

Cuando un reinicio devuelve el enfoque, ese script lo mantiene. Busca en su código una ruta de cierre que no llame a SetNuiFocus(false, false): un error antes de la línea de cierre, una muerte o un cambio de trabajo que cierre solo el HTML, o un callback que no responda. Un error en la consola en el momento del bug es una pista fuerte: véase how to read a FiveM script error.

Si es un script que compraste, envía a su autor los pasos exactos y el nombre del recurso que encontraste.

Lista de verificación

Síntoma Solución
Cursor permanece después de cerrar Llama a SetNuiFocus(false, false) en la función de cierre
El botón de cierre no hace nada Añade RegisterNUICallback('close', ...) y un fetch a https://<recurso>/close
ESC no cierra el menú Escucha Escape en JavaScript y llama al callback de cierre
Atrapado después de reiniciar un recurso Libera el enfoque en onResourceStop
El personaje no puede moverse después de cerrar Apaga SetNuiFocusKeepInput(false) al cerrar
No sé qué script lo hace Reinicia recursos uno a uno hasta que el cursor regrese
El callback se cuelga Siempre responde con cb('ok')

Respuestas rápidas

¿Por qué mi cursor permanece en la pantalla después de cerrar un menú?

El menú ocultó su HTML, pero nada le dijo al juego que devolviera el enfoque. El script tiene que llamar a SetNuiFocus(false, false) cuando se cierra el menú.

¿Qué hace SetNuiFocusKeepInput?

SetNuiFocusKeepInput(true) permite que el juego reciba entrada de teclado mientras una NUI tiene enfoque, para que el jugador aún pueda caminar. Debe apagarse de nuevo cuando se cierra el menú.

¿Cómo encuentro qué recurso mantiene el enfoque?

Reinicia tus recursos uno a la vez y verifica después de cada uno si el cursor y los controles vuelven. El que sea reiniciado y solucione el problema es el que mantenía el enfoque.

Scripts que evitan este problema

Mic PhoneUn móvil plegable que se abre en tablet y llega al móvil real del jugador.Ver script →Arcade MachinesSiete juegos arcade jugables en máquinas reales, con clasificaciones y apuestas.Ver script →Clothing DesignerDiseña ropa dentro de FiveM — pincel, capas, imágenes e IA — y póntela.Ver script →

Sigue leyendo