GameMaker: keyboard_string, but with a caret

A screenshot of the code example in question, showing a text field with a caret inside.

GameMaker has a built-in keyboard_string variable that lets you do simple text entry, but what if you also want a caret (so that the user can enter/erase text in the middle of the string)? This is a post about that - a pretty reasonable input field.

Originally posted on Mar 16, 2013 (archive), revised in 2023.

The idea

When the user presses keys that have a character associated with them, these characters are added to keyboard_string, generally with a limit of 1024 characters. And pressing backspace removes the last character from the string, surely enough.

So if we were to set keyboard_string to

====

and the user typed abc, it would become

====abc

and if the user pressed Backspace, it would instead become

===

we can use this (with a less-common prefix-character, of course) to figure out the newly entered characters / number of erased ones.

The code

You might replace var name; name = ... by var name = ... if you are using GameMaker: Studio or newer.

Create event:

input_string = ""; // current text
caret_pos = 0; // caret position (0 being before the first letter)

caret_next_move = current_time; // when's the next time you can move the caret left/right
caret_move_rate = 170; // delay between such movements

caret_flash_start = current_time; // when the caret will start flashing
caret_flash_delay = 700; // delay before flashing
caret_flash_rate = 600; // flashing rate

// we figure out the number of erased characters based on how many filler
// characters went missing from the string since we have last checked
fill_char = chr(27); // that's an ESC. It's not very typable as-is
fill_count = 16;
fill_string = string_repeat(fill_char, fill_count);

Step event:

// cursor movement:
var caret_delta; caret_delta = keyboard_check(vk_right) - keyboard_check(vk_left);
if (caret_delta != 0) {
    if (caret_next_move <= current_time) {
        caret_next_move = current_time + caret_move_rate;
        caret_pos = clamp(caret_pos + caret_delta, 0, string_length(input_string));
        caret_flash_start = current_time;
    }
} else caret_next_move = current_time;

// keyboard_shortcuts:
if (keyboard_check_pressed(vk_home)) caret_pos = 0;
if (keyboard_check_pressed(vk_end)) caret_pos = string_length(input_string);
if (keyboard_check_pressed(vk_delete)) {
    input_string = string_delete(input_string, caret_pos + 1, 1);
}
if (keyboard_check(vk_control)) {
    if (keyboard_check_pressed(ord("C"))) clipboard_set_text(input_string);
    if (keyboard_check_pressed(ord("V"))) {
        input_string = clipboard_get_text();
        caret_pos = string_length(input_string);
    }
}

// actual input:
var nstr; nstr = keyboard_string;
if (nstr == "") { // `keyboard_string` is set to `""` when the window loses focus.
    keyboard_string = fill_string;
}
else if (nstr != fill_string) { // new user input!
    // the number of missing "filler" characters is the number of characters
    // that user wants to erase this frame
    var numBksp; numBksp = fill_count - string_count(fill_char, nstr);
    numBksp = min(numBksp, caret_pos); // can't erase what doesn't exist
    if (numBksp > 0) {
        caret_pos -= numBksp;
        input_string = string_delete(input_string, caret_pos + 1, numBksp);
    }
    
    // the rest are the newly typed characters:
    nstr = string_replace_all(nstr, fill_char, "");
    nstr = string_replace_all(nstr, chr(127), ""); // ctrl+bksp types a DEL for some reason
    if (nstr != "") {
        input_string = string_insert(nstr, input_string, caret_pos + 1);
        caret_pos += string_length(nstr);
    }
    caret_flash_start = current_time;
    keyboard_string = fill_string;
}

Draw event:

// we don't want the user to type line breaks (in GM:S or earlier) so we'll replace those:
var draw_string; draw_string = string_replace_all(input_string, "#", "\#");
draw_text(x, y, draw_string);

// draw the caret:
var caret_offset; caret_offset = string_width(string_copy(draw_string, 1, caret_pos));
if (current_time < caret_flash_start + caret_flash_delay
|| (current_time - caret_flash_start) mod (caret_flash_rate * 2) > caret_flash_rate
) {
    draw_line(x + caret_offset, y, x + caret_offset, y + string_height("Q"));
}

Other Things

You might be wondering "But what about text selection? And mouse interaction? And multi-line input? And a dozen other common keyboard shortcuts?" and all of those are valid desires, but also slightly out of scope for this little post.

For example, the chat system that you see in some of my multiplayer mods counts a thousand-something lines of code and it's not even feature-complete!

Downloads

You can download the GameMaker 8.1 project that was used for the screenshot in the beginning of this post. It contains the exact same code that you can see above and a very basic "message log" (a ds_list and a few draw_text calls).

Related posts:

21 thoughts on “GameMaker: keyboard_string, but with a caret

  1. Hey,
    I have been messing around a bit with your work but I a afraid of messing with this piece of code because I don’t understand how it works. I think i am getting close to completing it so that you can change the lines after pressing enter.
    I would appreciate your help.

    // actual input:
    if (keyboard_string != filltext && keyboard_string != “”) {
    var n, l, t;
    t = keyboard_string
    l = string_length(t)
    n = 1
    // find where actual input starts
    while ((n 0 && caret > 0) {
    caret -= c
    text = string_delete(tex, caret[0] + 1, c)
    }
    c = l – n + 1
    if (c > 0) {
    text = string_insert(string_copy(t, n, c), text, caret + 1)
    caret += c
    }
    }

    • Instead of messing with that code, it would probably be a better idea to change the string being edited to that of the new line, and store the current one for display on-screen. There are a few complete text field examples on marketplace if I remember right.

  2. Any updated location for this file? I’m browsing different text input methods, and i ran into this one (by recommendation on the forums)

  3. Do you know a code I can add so if I were to say “game_restart’ in the input box, the game would find that function I added and restart the game? :/

    I like the style! No scripts, just some coding.

  4. Hello,
    Thanks for the input field, really helped me with my project.
    My mother language and the language that will be used in it is Hebrew, and it’s a right-to-left language.
    I added support for the Hebrew characters but the text comes out mirrored.
    Can you help me please?
    Sincerely,
    Ofek

    • שלום אופק!!

      נתקלתי בתגובתך לאתר בנוגע לכתיבת עברית בגיימייקר
      (קלט עברית וכד’)

      ?האם הצלחת לפתור את הבעיה

      אודה לך מאוד אם תשתף אותי בקוד מסוים שמאפשר קלט-פלט בעברית ללא אפקט מראה.

      תודה (:

      שחר

    • For HTML5 it would generally be a wiser decision to write an extension that would place an actual <input> element over the game instead of trying to write a keyboard handler yourself (which will not be able to consider every possible keyboard layout anyway)

  5. Pretty good and short for script but you cannot go up or down nor erase once you have created a new line so multiple lines are not yet supported.

    • Correct, this is currently geared towards single-line fields. Adding multi-line support would be possible, but would require increasing complexity of code quite a bit. Will try to look into that sometime.

  6. Just saw this coming from yoyo forum, I was wondering if you could help a newbie like me to GM and suggest a way to integrate this code into an existing prj. I’d like to ask the user his name when the game finished (i.e. lives = 0) so I can write it up in a file and use it in the leaderboard at startup screen. I was using the get_string() function but I don’t like the ugly window pop-up at all, your solution it’s much more elegant from a look’n’feel point of view.

    I’ve tried to create the object (the same that’s in your project) with the same code, and used a create_instance inside my code in place of the get_string but it just doesn’t do anything… I guess I’m too much of a newbie to GM though.

    any help/suggestion is much appreciated.

    thanks

  7. I was excited to see this text input example because as the previous commenter said it’s something that’s serious lacking from GameMaker. That said, I’m disappointed it doesn’t invoke the virtual keyboard on mobile devices when used with HTML5. That’s not the fault of your example, but rather a continued inefficiency of GameMaker. Nevertheless, thanks for all your hard work!

    • Indeed, while bringing up virtual keyboard is possible, it is completely out of scope of this example.
      It’s not even just about the GameMaker – it’s also about how HTML5 works: system keyboard will not pop up unless the user has tapped an according element (input/textarea). Thus, to bring up the keyboard, you have to trick the user into tapping such an input field. And that, in turn, means that all further event capturing must be done via that element.
      That, in combination with current lack of a proper JS API for interacting with engine, makes achieving of such task fairly problematic.

  8. Thanks, if it wasnt for your sniplet Game Maker would have been uninstalled already. Seems silly something that is used to make HTML5 applications has no form fields.

Leave a Reply to David Svejda Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.