Soluções Definitivas para o Cursor do InputField que não Pisca no Unity Android

O Enigma do Cursor Estático em Dispositivos Android Modernos

Muitos desenvolvedores Unity enfrentam um problema crítico ao lançar projetos para Android: o cursor do InputField (Caret) simplesmente para de piscar. O comportamento é frustrante porque funciona perfeitamente no editor, mas falha em dispositivos Android 12 ou superiores, especialmente naqueles equipados com chipsets Snapdragon recentes. Alterar o CaretBlinkRate ou chamar ActivateInputField() via script raramente resolve o problema.

A causa raiz não é uma configuração de parâmetro, mas sim um conflito na pipeline de renderização e no sistema de entrada. Em versões recentes do Android, o framework de método de entrada (IMF) e os teclados customizados de fabricantes como Xiaomi, Samsung e Huawei frequentemente interceptam a lógica de desenho do cursor, invalidando o CaretRenderer padrão do Unity.

Como o Cursor do Unity Realmente Funciona

Diferente do que se imagina, o cursor do Unity não é controlado por um simples temporizador de visibilidade. Ele depende do sistema de marcação "dirty" do Canvas e do retorno de chamada OnFillVBO do componente de texto. Para que o cursor apareça e pisque, quatro condições devem ser atendidas simultaneamente:

  • Foco Ativo: O InputField deve completar sua cadeia de eventos OnSelect.
  • Racyaster do Canvas: O componente não pode estar bloqueado por outros elementos UI ou fora do plano de visão da câmera.
  • Fontes Válidas: Se o Atlas de Fonte Dinâmica do Unity 2021.3+ falhar ao carregar, o cursor não será renderizado.
  • Compatibilidade de Material: O uso de Shaders customizados (como no URP) sem a devida atualização do material do cursor pode torná-lo invisível.

Em dispositivos de alta performance, o driver da GPU pode descartar atualizações de vértices se o SetVerticesDirty() for chamado em intervalos muito curtos (abaixo de 16ms), o que explica por que aumentar a taxa de intermitência às vezes piora o problema.

Estratégia 1: Forçar a Redesenho dos Vértices (C#)

Esta solução é ideal para projetos de pequeno e médio porte. Ela ignora o sistema de flags do Unity e força a atualização dos vértices do cursor no LateUpdate, garantindo que a GPU receba os dados de renderização independentemente das otimizações do driver.

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(InputField))]
public class CursorBlinkEnforcer : MonoBehaviour
{
    private InputField _field;
    private CanvasRenderer _cursorRenderer;
    private RectTransform _cursorTransform;
    private float _timer;
    private bool _isVisible = true;

    void Start()
    {
        _field = GetComponent<InputField>();
        Transform t = transform.Find("Text Input Field/Caret");
        if (t != null)
        {
            _cursorTransform = t.GetComponent<RectTransform>();
            _cursorRenderer = t.GetComponent<CanvasRenderer>();
        }
    }

    void LateUpdate()
    {
        if (!_field.isFocused || _cursorRenderer == null) return;

        _timer += Time.unscaledDeltaTime;
        if (_timer >= _field.caretBlinkRate)
        {
            _isVisible = !_isVisible;
            _timer = 0;
        }

        if (_isVisible)
        {
            UpdateCursorGeometry();
        }
        else
        {
            _cursorRenderer.Clear();
        }
    }

    private void UpdateCursorGeometry()
    {
        Vector2 size = _cursorTransform.sizeDelta;
        Vector3[] corners = new Vector3[4];
        corners[0] = new Vector3(-size.x * 0.5f, -size.y * 0.5f, 0);
        corners[1] = new Vector3(size.x * 0.5f, -size.y * 0.5f, 0);
        corners[2] = new Vector3(size.x * 0.5f, size.y * 0.5f, 0);
        corners[3] = new Vector3(-size.x * 0.5f, size.y * 0.5f, 0);

        Color32[] colors = new Color32[4];
        Color caretColor = _field.caretColor;
        for (int i = 0; i < 4; i++) colors[i] = caretColor;

        _cursorRenderer.SetVertices(corners, colors);
    }
}

Estratégia 2: Sincronização via JNI (Android Nativo)

Para aplicações que exigem precisão absoluta (como apps financeiros), é possível monitorar o estado do cursor diretamente na camada Java do Android e notificar o Unity. Isso contorna o problema de "劫持" (sequestro) do cursor por parte do teclado do sistema.

No lado Java (Android Studio / Plugin):

package com.tech.utils;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.CursorAnchorInfo;
import android.content.Context;
import com.unity3d.player.UnityPlayer;

public class NativeInputHandler {
    public static void requestCursorUpdate(Context context) {
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        CursorAnchorInfo info = imm.getLastCursorAnchorInfo();
        if (info != null) {
            String data = info.getInsertionMarkerX() + ":" + info.getInsertionMarkerY();
            UnityPlayer.UnitySendMessage("InputManager", "OnNativeCursorUpdate", data);
        }
    }
}

Estratégia 3: Transição para TextMeshPro InputField

A solução mais robusta a longo prazo é substituir o InputField legante pelo TMP_InputField. O TextMeshPro não utiliza o CaretRenderer padrão do Unity; em vez disso, ele desenha o cursor como um glifo individual ou um elemento de malha SDF (Signed Distance Field). Isso torna o cursor imune a falhas de sincronização do sistema de entrada do Android.

Vantagens do TMP_InputField:

  • Controle total sobre a renderização através de m_CaretRenderer (que funciona como um SpriteRenderer interno).
  • Melhor suporte para diferentes resoluções e densidades de pixels sem serrilhamento.
  • Independência das permissões de Buffer de Quadro do Android.

Guia de Diagnóstico Rápido

Se o cursor ainda não estiver visível, verifique os seguintes pontos:

  1. Escala do Canvas: Garanta que o Reference Pixels Per Unit não esteja configurado com um valor que torne o cursor menor que 1 pixel.
  2. Material URP: Se estiver usando a Universal Render Pipeline, o cursor deve usar o material Universal Render Pipeline/UI/Default.
  3. Z-Order: Verifique se o cursor não está sendo desenhado atrás do fundo do InputField devido a uma alteração na hierarquia de Z ou na ordem de renderização dos elementos do Canvas.

Tags: Unity android-development ui-design CSharp rendering

Publicado em 6-20 05:08