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
InputFielddeve completar sua cadeia de eventosOnSelect. - 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:
- Escala do Canvas: Garanta que o
Reference Pixels Per Unitnão esteja configurado com um valor que torne o cursor menor que 1 pixel. - Material URP: Se estiver usando a Universal Render Pipeline, o cursor deve usar o material
Universal Render Pipeline/UI/Default. - 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.