If you’ve been using On-Screen Keyboard in your Unity project – or you’ve been eyeing it on the Asset Store – the latest update is a big one. Over the past stretch I’ve reworked the asset from the inside out: a real Editor experience, a faster build path, hardened pooling, smarter authoring helpers, and proper packaging.
Here’s what’s new and why it matters.
A real Editor experience
This is where the update shines. Until now, authoring a keyboard meant editing a multi-line text field with no validation, no preview, and no autocomplete for special tokens. Type {spaec} instead of {space} and you’d find out at runtime when the key did nothing.
That’s gone:
Live preview on every layout asset. Open any KeyboardLayout in the Inspector and you see exactly what the parser sees – a grid preview with per-line validity badges. Bad tokens are flagged the moment you
type them.
An “Insert token” helper. No more memorizing {backspace} vs {bksp} vs {return}. Pick from a dropdown of every supported special key.
Theming inspectors that actually preview. KeyboardButtonsDisplay shows column-aligned override rows with sprite previews and color swatches, then mirrors the result in a live keyboard preview underneath.
Validation banners on the manager. KeyboardManagerScript calls out missing layout / display / button-prefab references at edit time – not at play time.
A LayoutChanger inspector that catches dead mappings. Each rule gets a “✓ valid / ⚠ unreachable” badge so you can spot a circular or unreachable language switch before shipping.
Asset import validation. A custom AssetPostprocessor scans every KeyboardLayout on import. A broken asset logs to the Console immediately, not the next time you open the scene.
A faster scaffolding path
Two new entry points cut the time-to-first-keypress to almost zero:
GameObject → UI → On-Screen Keyboard scaffolds a fully wired keyboard + TMP input field under the active Canvas. Drag, click, type – done.
A first-import welcome window opens once on install and points you at the demo scene and documentation. Re-openable from Window → On-Screen Keyboard → Welcome.
And the asset finally ships as a proper UPM package (com.saguiitay.onscreenkeyboard) with runtime + editor .asmdefs and a Demo Scenes sample. Install via Git URL or local path; it stops leaking into your Assembly-CSharp.dll.
Faster, quieter, lighter
A bunch of allocation and rebuild hot-spots got cleaned up:
- The keyboard build is single-pass now (was two-pass).
- The build runs with the manager temporarily deactivated so
SetParent/AddComponentcalls don’t trigger one layout rebuild per row – they batch into one rebuild at the end. - Closure-free
Button.onClick. Pooled buttons used to allocate a fresh closure capturingkeyDetailsevery reuse. Now a shared handler is wired once inKeyButtonScript.Awakeand reads its fields off the component. Pooled reuse is allocation-free. - O(1) lookups everywhere.
LayoutChanger‘s trigger-key match andKeyboardButtonsDisplay‘s per-key override lookup are bothDictionary-backed. - Korean composition on the hot path.
KoreanCombiner.ProcessTextruns on every keystroke when Hangul mode is on. It now reuses a[ThreadStatic] StringBuilderand usesHashSet/Dictionaryfor Jamo
classification. - The
Updatepoll for spacing/padding changes is gated behind#if UNITY_EDITOR– no per-frame overhead in builds.
On a low-end Android device, a 50-key layout switch went from “noticeable hitch” to “imperceptible.”
Hardened pooling
The pool was calling SetParent(null) on every pooled child to “detach” it. That triggers a layout rebuild on the previous parent and puts the object at risk of GC on scene unload (Unity only preserves
detached objects when they’re explicitly marked DontDestroyOnLoad).
The fix is boring and obvious in hindsight: keep pooled objects parented to the manager and just SetActive(false). OnDestroy also null-checks every pool collection so teardown survives a mid-rebuild from
another script.
Bug fixes worth calling out
A few long-standing issues finally got squashed:
- Korean base syllable 가 was excluded from final-consonant combination by an off-by-one. Typing 가 followed by a final consonant produced two characters instead of one composed block. Fixed and covered
by a regression test. SetLayout(currentLayout)was a silent no-op even whenMarkDirty()had been called. The dirty flag now actually triggers the rebuild it was supposed to.- Caps-lock vs momentary shift were conflated.
LayoutChanger‘s revert path didn’t reset the lock flag correctly, soshift,shift,aandshift,a,shiftproduced the wrong output in one of the two modes. Both sequences now match the spec. - Caret marched off into the void after a Hangul composition shortened the text buffer, and the viewport stayed scrolled past the end after backspacing through a long string. Both clamped now.
{}spacer keys rendered as invisible-but-tappable buttons. They now disable text/icon/background and become non-interactable, and the parser tags them with an explicitEMPTY_KEYconstant.- Row order was reversed after some pooled rebuilds. Reported by a user, fixed, and covered.
- Race on layout reassignment.
TextboxLeyboardAdapterScript.ApplyLayoutDirectionnow snapshots the layout reference before readingIsRightToLeft.
API cleanups
A few changes visible to anyone scripting against the asset:
// New: programmatic key dispatch
keyboard.SimulateKeyPress(KeyDetails.Parse("{enter}"));
// New: special-key tokens as named constants — no more magic strings
keyboard.SimulateKeyPress(KeyDetails.Parse(KeyboardConstants.SPACE));
// Renamed: clearer, grammatical, and auto-migrating via [FormerlySerializedAs]
adapter.composeHangulSyllables = true;
Plus tooltips on every Inspector-visible field, a declared KeyPressedEvent subclass so OnKeyPressed renders persistent listeners (you can wire LayoutChanger.OnButtonPressed from the editor instead of from code), and [RequireComponent] declarations so you can’t accidentally remove the layout group the manager depends on.
What does the workflow look like now?
using Assets.OnScreenKeyboard.Scripts;
// Drop-in: scaffold from GameObject menu, then wire one event
keyboard.OnKeyPressed.AddListener(key =>
{
if (key.value == KeyboardConstants.ENTER)
Submit(inputField.text);
});
// Programmatic: simulate any key
keyboard.SimulateKeyPress(KeyDetails.Parse("a"));
// Hot-reload after editing the layout asset at runtime
keyboard.MarkDirty();
keyboard.SetLayout(currentLayout);
Or stay in the Editor: use GameObject → UI → On-Screen Keyboard to scaffold, drop a KeyboardLayout asset onto the manager, theme it with a KeyboardButtonsDisplay, and use the live preview to confirm the layout looks right – all without writing a line of code.