Custom Caret

Hello,

I’m doing my own terminal, like simple OS (imagine commodore 64 or fallout4 terminal), its for a game, like you can give it simple commands copy/cp list/ls. I’ve done it quite robust and brute but dont need anything special. Basically it gets input from player, char by char and saves it in list of chars then when enter is pressed it goes and recognizes commands and that all works, for displaying that list of chars i throw chars to string and that string to Text.text and update it to screen. Also works fine but i need my own InputField since terminal has it on focus all the time and allows me much more customization.

THE QUESTION
How to make my own caret (thing that flashes when typing), like i would do it with sprite, some white thick sprite like terminals have it but thing is how to deremine on screen position it should be at, basically how to see where at Text.text i am currently and show white sprite there, or maybe you have better suggestion.

Thanks.

What you want is the ‘TextGenerator’ class:

With this you can measure how strings will be displayed.

The UnityEngine.UI.Text has one cached on itself which it uses for calculating its own rendering space:
https://docs.unity3d.com/ScriptReference/UI.Text-cachedTextGenerator.html

What you can do is check the ‘verts’ collection on it for all the vertices. The verts are in quads, groups of 4, so if you wanted to find a character before the end you need to figure that into your math. But since we just want to find the end of the string we can just use the last vert.

Here is an example:

public class zTest01 : MonoBehaviour
{


    public Text TextObject;
    public GameObject Cursor;
    public float CursorDelay = 0.3f;
    public Vector3 CursorOffset;

    private string _msg = "";
    private float _timer = 0f;

    private void Update()
    {
        //update msg
        foreach (char c in Input.inputString)
        {
            if (c == '\b') // has backspace/delete been pressed?
            {
                if (_msg.Length != 0)
                {
                    _msg = _msg.Substring(0, _msg.Length - 1);
                }
            }
            else if ((c == '\n') || (c == '\r')) // enter/return
            {
                _msg += "\r\n";
            }
            else
            {
                _msg += c;
            }
        }

        TextObject.text = _msg;

        //place cursor
        var gen = TextObject.cachedTextGenerator;
        if(gen.verts.Count > 0)
        {
            var v = gen.verts[gen.verts.Count - 1].position;
            v = TextObject.transform.TransformPoint(v);
            Cursor.transform.position = v + CursorOffset;
        }
    
        //do blink
        _timer += Time.deltaTime;
        if(_timer > CursorDelay)
        {
            _timer = 0f;
            Cursor.SetActive(!Cursor.activeSelf);
        }
    }

}

Note that the string appending code is very naive and will generate TONS of garbage… it serves only as an example.

As for the getting the cursor position, that should be fine in and of itself.

Note that I have a ‘CursorOffset’ since not all cursors are the same. You might have big fat ones, thin little ones, etc. Use that value to adjust the cursor relative to the end however you like.

4437916--405931--Typing.gif

1 Like

Just made new project to test your file, its basically what i need, i may implement later moving left right in command line but for now this works perfectly. ill study it a bit and implement in my project. Thank you very much.

I implemented everything fine and it works perfectly, one thing that bothers me

TextGenerator gen = inputText.cachedTextGenerator;          //Get TextGenerator from input
        if (gen.verts.Count > 0)
        {
            Vector3 v = gen.verts[gen.verts.Count - 1].position;
            v = inputText.transform.TransformPoint(v);
            caret.transform.position = v + caretOffset;
        }

this code, after each input from player i make update screen function which is this

inputText.text = "[" + currentIP + "] " + currentDirectory + ": " + CharsToString(command);          //Updates players input
        historyText.text = "";          //Clears old history text

        for (int i = 0; i < history.Count; i++)          //Shows all players commands backwards
        {
            historyText.text += "\n" + history[i];
        }

like here i update that text shown to player, but if i put caret update part here, it always is one step before than current situation, putting it in update function works. maybe drawing of text is done later so it cant fix it self until next frame?

Yeah, the code that pulls the information from ‘cachedTextGenerator’ is in the state that only includes the information before Update was called. This means that it doesn’t contain the information from adding in the new characters yet.

If you put in this line before ‘if(gen.verts.Count > 0)’ you should get what you want:

gen.Populate(_msg, ...);

edit:
err, you need to get the TextGenerationSettings for the second parameter… probably can get that from here:
https://docs.unity3d.com/ScriptReference/UI.Text.GetGenerationSettings.html

Sorry, at work, so can’t help directly.

1 Like

can you please share the project i am very much in need to know how to get this working with the current unity version as the positions are not correct no mater what offset i use thanks

for me, the caret is always set to the level of the letter in front of it. (I use unity 2019)