Quick answer: GameMaker strings are UTF-8. string_length() returns the number of characters; string_byte_length() returns the number of bytes, which is larger for any non-ASCII text. Use byte length whenever you size a buffer, write a length prefix or enforce a protocol limit, and character functions when you work with visible text.

A player named "Zoë" saves the game and their name loads back as "Zo". A chat message with an emoji arrives truncated. Both are the same bug: a character count used where a byte count was needed.

Characters vs bytes

In UTF-8, ASCII characters take 1 byte, accented Latin letters 2, most CJK characters 3 and emoji 4.

var s = "Zoë";
show_debug_message(string_length(s));        // 3 characters
show_debug_message(string_byte_length(s));   // 4 bytes

Any code that allocates or reads string_length(s) bytes drops the end of non-English text.

Buffers and save files

buffer_write(buf, buffer_string, s) writes the UTF-8 bytes plus a terminating zero byte. Size buffers from the byte length:

var bytes = string_byte_length(s) + 1;   // + null terminator
var buf = buffer_create(bytes, buffer_fixed, 1);
buffer_write(buf, buffer_string, s);

With buffer_grow you do not have to pre-size, but length prefixes still need bytes. For a length-prefixed format:

buffer_write(buf, buffer_u16, string_byte_length(s));
buffer_write(buf, buffer_text, s);       // buffer_text: no null terminator

On the reading side, read exactly that many bytes rather than assuming one byte per character.

Network protocols and limits

If a server limits names to 16 bytes and the client checks string_length(name) <= 16, a 16-character Japanese name (48 bytes) passes the client and is rejected or truncated by the server. Enforce limits in the same unit on both sides. For visible limits (a name box that fits 16 characters), check characters; for storage and protocol limits, check bytes.

Indexing

string_char_at(), string_copy(), string_delete() and string_pos() use 1-based character positions. string_byte_at() and string_set_byte_at() use byte positions. Mixing them on non-ASCII text cuts characters in half and produces invalid UTF-8, which then displays as missing glyphs.

Truncating safely

To trim text to a byte budget without breaking a character, remove whole characters until it fits:

function string_truncate_bytes(s, max_bytes) {
    while (string_byte_length(s) > max_bytes) {
        s = string_delete(s, string_length(s), 1);
    }
    return s;
}

Fonts are a separate issue

If text is complete but shows as boxes, the font does not include those glyphs. Add the ranges in the font editor or use a font with wider coverage; that is a rendering problem, not a length problem.

Testing

Keep a test string such as "Zoë 日本 🎮" and run it through every save, load and network path. Most UTF-8 bugs show up immediately.

Related fixes