GMS 2.3+ Organizing colours and text

Throughout my code on many of my games/applications, I find my self using the following types of commands:

draw_set_colour();
draw_set_font();
draw_set_halign();
draw_set_valign();

It's starting to get messy. I don't like having to keep setting these in different sections of code. I can't really think of a good way to do it. All I can think of is having a script with a case statement and just call different profiles.
 

obscene

Member
We call these "styles" in the design industry. If you have a few variations that you regularly use, make them styles, give them names and write a script like you said...

switch(style) { case style_normal: etc... case style_header1: etc... }
 
Thought that might be something like the case. Is there anything in the IDE I should know about. I have seen something to do with font groups, sound groups. Does this have anything to do with it?
 

kburkhart84

Firehammer Games
I don't know how much it would help(but it can't hurt at the least). But there is a nice text engine with tons of features made by Juju called Scribble. At the least it has a "starting format" function that lets you set the font, color, and HAlign in a single function call. But it does lots of other stuff too so it may be worth looking at(over making your own similar function).
 

TheouAegis

Member
Orient your project so you're writing all left-aligned text at the same time and all center-aligned text at the same time and all right-aligned text at the same time. Write all text taht's one color at the same time, then all text that's another color at the same time.

Also, draw the text to a surface. The draw_text() functions are already slow, and you're compounding that with all the style changes. By drawing the text to a surface and only updating the surface as needed, you'll cut out a huge swath of code running in the Draw event. Floating text won't work so well, but UI and text boxes will benefit greatly from just rendering to a surface.
 
Thanks for the advice. As for surfaces, I do use these quite a bit. I find them really useful when you want sections shaved off. For example, I have a level editor which you can place objects on but don't want them hanging over the side of the grid. So rather than use lots of fiddly draw sprite part stuff I don't have to code anything.

I did the same with the textbox so that you can scroll multiple lines of text if it doesn't fit in the textbox area. If the text is not in the text field area it just won't display because it's outside the surface area.

So I didn't exactly use it for the reason you suggested but that's an added bonus.

Thank you all.

Edit: Just confirm I am using surface correctly I have the following the example of when I have used a surface.

GML:
surface_set_target(surf_text); // Set drawing to grid surface
draw_clear_alpha(c_white, 0); // Set grid colour

// DRAW TEXTBOX

draw_sprite(textbox_sprite, 0, 0, 0); // Draw textbox

// DRAW TEXT

for (var i = 0; i < max_line_qty; ++i) // Loop through all lines of text
{
    _text_x = horizontal_text_buffer; // Calculate text X position
    _text_y = vertical_text_buffer + (text_string_height + text_line_gap) * i; // Calculate text Y position
   
    _text_y -= text_array_offset * (text_string_height + text_line_gap); // Adjust text height to match text array offset
   
    _current_text = text_array[i] + string(" ") + special_suffix; // Calculate current line of text from the text array

    draw_text_color(_text_x, _text_y, _current_text, text_colour[0], text_colour[1], text_colour[2], text_colour[3], text_alpha); // Draw text
}

surface_reset_target(); // Set drawing back to application service
draw_surface(surf_text, x, y); // Draw textbox surface
It seems to work. But is it?
 
Last edited:

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
Don't know about the text drawing details, but you seem to use surfaces correctly (set target, clear, draw stuff, reset target, draw surface).

As for styles: instead of the switch/case proposed elsewhere, I'd suggest using the amazing powers of GM:S 2.3+ and wrap styles in structs instead. For example, here is how I could handle styles:
GML:
function draw_apply_style(style) {
    if (variable_struct_exists(style, "color"))
        draw_set_color(style.color);
    if (variable_struct_exists(style, "font"))
        draw_set_color(style.font);
    if (variable_struct_exists(style, "halign"))
        draw_set_color(style.halign);
    if (variable_struct_exists(style, "valign"))
        draw_set_color(style.valign);
}

global.styles = {
    default: { font: fnt_Regular, color: c_white, halign: fa_left, valign: fa_top },
    info: { font: fnt_Regular, color: c_aqua },
    warning: { font: fnt_Regular, color: c_orange },
    top_left: { halign: fa_left, valign: fa_top },
    centered: { halign: fa_center, valign: fa_middle },
    title: { font: fnt_Title, color: c_white, halign: fa_center, valign: fa_middle }
};
(alternatively, I could create a constructor for style structs with "apply()" method, but at this point it's more of a matter of preference)

Then, you can apply styles like that:
GML:
// drawing a title with a common "title" style
draw_apply_style(global.styles.title);
draw_text(title_x, title_y, "Movement tutorial");

// mixing the different styles together
draw_apply_style(global.styles.info);
draw_apply_style(global.styles.centered);
draw_text(description_x, description_y, "Use arrow keys to move.");
With this approach (particularly applying only the variables that are defined for the given style) you can setup multiple styles independently, too, as well as easily add more variables to the style.

Hope this helps. ^^
 
So I had a look at these structs. They actually look pretty cool. You can even put functions inside them. As I say I haven't used them before but I will have to be careful when and where I use them because I have a tendency to overuse things inappropriately if I have only just learned about it.

Would you be able to provide an example other than the one I asked about in relation to formatting where these come in handy? and possibly a situation where a struct would hinder or make things more complex than they need to be.
 

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
Structs are great for storing global definitions that aren't going to change throughout the course of the game.
In that regard they resemble enums, but much more powerful, with ability to use strings/numbers/arrays/other structs/functions as their values.

Also, structs are good for keeping track of and manipulating more complex states. For example, in another thread I gave an example of countdown timer struct constructor.

Generally, you will want to avoid creating short-lived structs every step, because the more structs you create, the more will need to be destroyed by garbage collector (an automatic system for freeing memory of structs; it's the opposite of ds_* data structures/surfaces/buffers/etc., which need to be freed manually). And this will have an impact on the game performance.

So while it's useful to have a global struct definitions or long-lived structs managing object states, you don't want to create many short-lived structs every step.
 

kburkhart84

Firehammer Games
Assuming I wanted to keep using the included internal text drawing functions, I too would use structs for the styling. And I would have a function in the struct that sets the style automatically instead of having to call some other function to handle it, so it is all integrated into the same nice neat little package.
 

TheouAegis

Member
Thanks for the advice. As for surfaces, I do use these quite a bit. I find them really useful when you want sections shaved off. For example, I have a level editor which you can place objects on but don't want them hanging over the side of the grid. So rather than use lots of fiddly draw sprite part stuff I don't have to code anything.

I did the same with the textbox so that you can scroll multiple lines of text if it doesn't fit in the textbox area. If the text is not in the text field area it just won't display because it's outside the surface area.

So I didn't exactly use it for the reason you suggested but that's an added bonus.

Thank you all.

Edit: Just confirm I am using surface correctly I have the following the example of when I have used a surface.

GML:
surface_set_target(surf_text); // Set drawing to grid surface
draw_clear_alpha(c_white, 0); // Set grid colour

// DRAW TEXTBOX

draw_sprite(textbox_sprite, 0, 0, 0); // Draw textbox

// DRAW TEXT

for (var i = 0; i < max_line_qty; ++i) // Loop through all lines of text
{
    _text_x = horizontal_text_buffer; // Calculate text X position
    _text_y = vertical_text_buffer + (text_string_height + text_line_gap) * i; // Calculate text Y position

    _text_y -= text_array_offset * (text_string_height + text_line_gap); // Adjust text height to match text array offset

    _current_text = text_array[i] + string(" ") + special_suffix; // Calculate current line of text from the text array

    draw_text_color(_text_x, _text_y, _current_text, text_colour[0], text_colour[1], text_colour[2], text_colour[3], text_alpha); // Draw text
}

surface_reset_target(); // Set drawing back to application service
draw_surface(surf_text, x, y); // Draw textbox surface
It seems to work. But is it?
if that is the actual code you are using, then no it's not right. You should be drawing to the surface only when the information changes. It looks like you are drawing the text every step right before drawing the surface. No, you should be drawing text only when the text changes. If you're simply opening a piece of paper that doesn't have scrolling text, for example, then you should only be drawing to the surface once. If you were doing a dialogue text box, you should only draw to the surface when the text in the text box changes. The only time you should ever be constantly drawing text to a surface is when dealing with smooth-scrolling text (like the Star Wars intro).
 
The plan was to use smooth scrolling text as an option. I was going to have smooth scrolling and one line at a time scrolling.

I'm confused about this surface thing now. I thought the code had to be inside the surface code. If I want to draw text on top of the surface I have to use the draw event. Would you show me what this looks like, please?
 

TheouAegis

Member
You don't have to use the Draw event, it's just optimized for it. And you also can use the Begin Draw event. You can still use the Draw event, though, but the logic for drawing the text should be separate from drawing the surface.

Think of things this way: You go to a protest and want to wave a sign at the rally. The sign is your surface and holding it up is draw_surface(). You write your message on the sign - that's draw_text(). Do you douse your sign with with paint and rewrite your message every second? No, you write it once. You hold up your sign every second.
 
Last edited:
I understand the principle but I'm not sure about GMS. How do I write on the surface without doing it every second as the draw code as I understood it has to be under a draw event. and the code you want on the surface has to be between the surface code. How do I do what you say create a surface and draw on it once.

Do you mean:

DRAW BEGIN

GML:
if(!surface_exists(surf_text)) // Check if surface exists
{
    surf_text = surface_create(textbox_width - textbox_right_buffer, textbox_height - textbox_bottom_buffer); // Create text surface
}

surface_set_target(surf_text); // Set drawing surface to textbox surface
draw_clear_alpha(text_surface_colour, 1); // Clear textbox surface
DRAW

Code:
draw_sprite(textbox_sprite, 0, 0, 0); // Draw textbox
DRAW END

Code:
surface_reset_target(); // Reset text surface
draw_surface(surf_text, x, y); // Draw text surface
 
Last edited:
As long as the surface exists, you don't need to be drawing to it. So you would move the "drawing onto the surface" code into the "does the surface exist" if statement. Then, you simply have the draw_surface() outside of the if statement to draw the surface itself.
 
Like this:-

GML:
// SETUP TEXTBOX SURFACE

if(!surface_exists(surf_text)) // Check if surface exists
{
    surf_text = surface_create(textbox_width - textbox_right_buffer, textbox_height - textbox_bottom_buffer); // Create text surface


surface_set_target(surf_text); // Set drawing surface to textbox surface
draw_clear_alpha(text_surface_colour, 1); // Clear textbox surface

draw_sprite(textbox_sprite, 0, 0, 0); // Draw textbox

// DRAW TEXT

for (var i = 0; i < max_line_qty; ++i) // Loop through all lines of text
{
    var _text_x_position = textbox_left_buffer; // Calculate text string X position
    var _text_y_position = textbox_top_buffer + (i * (char_string_height + text_line_gap)) - text_scroll_value; // Calculate text string Y position
    
    var _text_colour = text_colour[i]; // Get text string colour
    var _text_alpha = text_alpha[i]; // Get text string alpha

    draw_text_color(_text_x_position, _text_y_position, text_string[i], _text_colour, _text_colour, _text_colour, _text_colour, _text_alpha); // Draw text
}

surface_reset_target(); // Reset text surface

draw_text(room_width / 2, room_height / 2, temp_string);
}

draw_surface(surf_text, x, y); // Draw text surface
 

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
I don't know what's the point of drawing temp_string right after surface_reset_target() call. It draws temp_string only in the exact frame the surface doesn't exist, which is not very useful.
Either draw it before surface_reset_target() call - if you want it to be drawn on the surface -or draw it outside the !surface_exists(...) block altogether - if you want it drawn outside of the surface.
Judging by using room_width/2 and room_height/2 coordinates, probably the latter.

Otherwise, it looks mostly fine, but keep in mind that you need to redraw the surface whenever a text scroll changes. So you actually might want to introduce variable like "textbox_changed" and do something like:
GML:
// recreate the surface if it doesn't yet exist
if (!surface_exists(surf_text)) {
    surf_text = surface_create(textbox_width - textbox_right_buffer, textbox_height - textbox_bottom_buffer);
    textbox_changed = true;
}

// redraw the surface if it either was recreated or textbox scrolling changed
if (textbox_changed) {
    surface_set_target(surf_text);
    draw_clear_alpha(text_surface_colour, 1);
    // do rest of the drawing here
    surface_reset_target();
}

// draw whatever temp_string is supposed to be
// if you did all the previous code in Begin Draw event, you might want to move it to regular Draw event instead
draw_text(room_width / 2, room_height / 2, temp_string);
Then, in whichever code changes textbox scrollbar position, you set "textbox_changed" to true as well, forcing the textbox redraw.
 
Or the other option for text scrolling is to draw the entire text to the surface and then simply draw a part of the surface with draw_surface_part() and manipulate the coords to show the correct part of the surface in correlation to the scroll amount.
 

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
Or the other option for text scrolling is to draw the entire text to the surface and then simply draw a part of the surface with draw_surface_part() and manipulate the coords to show the correct part of the surface in correlation to the scroll amount.
This option is valid, too, though then you need to keep in mind that:
- textbox background should be drawn outside of the surface, because it's a part that remains fixed no matter the scrolling
- surface itself should be cleared with 0-alpha background, because it'll be drawn on top of the textbox background
- surface height must adapt itself to the height of text
- you must make sure you don't ever use text so long/tall that it exceeds acceptable surface_height (I think there's a limit on that, maybe depending on the max texture page size of the device, if I'm not mistaken)

Keeping these in mind (especially the last point), I personally would stick to the current solution until it becomes too inefficient (possible for larger text). And if it does, I'd first try to optimise it the text-drawing loop, going from first to last visible line rather than draw the entire text.
 
Ye your definitely right for the last point, just for the sake of efficiency and good practice. However, I don't think in a gaming context that anyone would ever want that much text.

Why do I need to make my surface the same size as the text? I have set to it to be the size of the textbox. Well, all of the area inside the textbox border. (I have now moved this outside the surface).

Thanks for all your help and suggestions guys. I think I'm making some good progress now. A few things starting to click.

Edit: I popped my surface update code into a script now works great.
 
Last edited:

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
Why do I need to make my surface the same size as the text? I have set to it to be the size of the textbox. Well, all of the area inside the textbox border. (I have now moved this outside the surface).
Basically, there are two approaches:

1. Your original approach (more or less) - draw only the visible part of the text to the surface, and draw entire such surface on top of the textbox.
The surface needs to be redrawn every time it stops existing *or* when you scroll the text, changing the visible text.
In this case, you want the surface to be as wide and tall as the visible area (i.e. textbox size); when you scroll outside the visible area, you just redraw the surface.

2. Draw the entire text on the surface, then draw scrolled part of the surface to the textbox area.
The surface needs to be redrawn every time it stops existing. Scrolling the text only affects which part of the surface we ultimately draw, but surface itself doesn't need redrawing.
In this approach, you need the surface to be as tall as the entire text; when you scroll, you change the coordinates of the drawn part, but otherwise the text is already drawn.

Each approach has its benefits and drawbacks.
I personally would pick option 1, because you already have something like this working, the surface size is fixed and - if you optimise the text-drawing to only draw the visible lines - it has less cost of a single redrawing.
Option 2 on the other hand results in fewer redrawings and doesn't require to mix textbox scrolling with surface redrawing management.
 

TheouAegis

Member
Why do I need to make my surface the same size as the text?
They are referring to when you do a long text scroll using the surface for just the text (the paper sprite would be drawn normally, not on the surface). That way the entire block of text would be drawn at the very beginning of the text scroll and, assuming the surface doesn't corrupt, never again.
 
This is ultimately the conclusion I have come to now. I actually used both implementations. I re-wrote the code to use the logic I am using now. I branched it off because I wasn't sure which way to go. I kind of had to figure out both methods manually over the last few weeks or so just by brute force logical discovery. It's good to know that I cam to the correct conclusion.

I'm glad I asked because with both methods I 'unwittingly' was updating the surface data ever step. I think that was the main issue. I understood this before in the sense that I understand the logical principles of the surface and I thought I was only updating once. But I wasn't.

I have said this a couple of times before. 'Older systems slow down and as such indicate a problem'. The way I was doing it before was hideous but I did not know because It's so fast you can't tell. So I was thinking it was only updating the surface only when needed but it wasn't. I had no way of knowing this.

It would be nice if Gamemaker could punish you somehow for these discrepancies but I guess we come back into the realm of typed languages at that point.

Edit: if I wanted to scroll the text left and right as well as up and down, how would I achieve this without drawing lots of text.

The only thing I can think of is to draw each letter individually (Other benefits would be rainbow-colored lettering). The draw-back being I would have to do all of the 'kerning' myself. (If I wanted to ensure I would never run out of surface area)

I put kerning in quotes because I am not 100% on its meaning. I assume this is the space between each letter or 'character'. (Which Gamemaker does for you unless you draw each letter individually).
 
Last edited:
It's going quite well now. It would be nice to have it checked over in a couple of days to make sure I'm still doing everything logically. That would be really helpful.
 

TheouAegis

Member
For scrolling left and right, if you did the draw_surface_part() method, you'd just adjust your left argument. You can test easily enough if rendering outside the surface bounds is allowed, just set left to a negative value, which should give the illusion of the text shifting to the right. If left is less than 0, you'd probably want to add it to width so the surface gets clipped on the right. You could probably just add left to width all the time without much issue, but then you'd just be drawing empty pixels for no good reason.

When you use a draw_****_part() function, the origin of what you draw is defined by arguments x and y, meaning draw_sprite_part(), for example, will completely ignore the default origin of the sprite you are drawing. What this means is anything you draw with left or top greater than 0 will result in the appearance of what you draw to be "scrolling". Make a picture frame, hold it over a poster in front of you, then slowly walk to the right. It will appear the poster is scrolling, when in reality it's not. That's what's going on with draw_****_part(). When you're working with colored text, rendering the text to a surface only as-needed becomes even more important, as that's a LOT more draw_text() and draw_set_color() calls you'll be adding to the CPU's workload.

If you were just drawing the text as-needed, then you'd modify the x argument in draw_text().
 
I have used draw sprite part functions on other projects before but opted for the surface because it's basically zero code compared to cutting everything. As far as my textbox goes I think I'm going to implement it the way I did the first time around. I'm using the surface correctly now (At least in terms of not writing to it every step).

I'm going to draw as many lines that will fit in the textbox then offset the array by the number of lines required. Doing this would mean I can't have smooth scrolling.

I think I'm on track now. I'm just torn between those two options. If I use the smooth scrolling I don't really mind just limiting it to something like 1000's. I'm not writing a word processor after all. I don't want to be at risk of over-engineering it and basically wasting time. Ah decisions.

Can I ask when you code something do you just code exactly what you need no more, no less or do you use some foresight about features that you don't need now but may in the future.
 
Last edited:

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
I'm going to draw as many lines that will fit in the textbox then offset the array by the number of lines required. Doing this would mean I can't have smooth scrolling.
Both approaches I mentioned should perfectly allow smooth scrolling, provided the scrolling overhead isn't so large that it slows down FPS below room_speed.

The "visible text on surface, draw entire surface" approach (the original approach) adds a small extra overhead to scrolling, but makes for shorter redrawing overall.
The "entire text on surface, draw part of surface" approach has nearly no scrolling overhead, but can be heavier when you need to redraw a surface because it stopped existing.

As I see it, the overhead of redrawing visible text on scrolling should be so negligible that "visible text on surface, draw entire surface" won't significantly affect FPS, especially if you limit it to visible lines only.
For example, if you know that each line height is 16px, the visible text area height is 80px and that you have a scrolling offset of 38px, then:
- the first line you want to draw is max(0, floor(38px / 16px)) = max(0, 2) = 2nd line (with 0th and 1st lines not drawn)
- the last line you want to draw is min(max_lines - 1, ceil((38px + 80px) / 16px) = min(max_lines - 1, ceil(118px / 16px)) = min(max_lines - 1, 8)

This should make the drawing optimised to the point where you won't need to worry about redrawing cost, making the scrolling smooth.

Can I ask when you code something do you just code exactly what you need no more, no less or do you use some foresight about features that you don't need now but may in the future.
I usually try to apply some minimum amount of foresight. Obviously, I try to code no less than what I need, but I may end up writing a more general code that's able to do more than what I immediately need.
I generally don't write extra code for features not needed at the moment, but rather try to write code that'll make adding these extra features easier. On the other hand, I generally try to write the original code in a parameterised way (e.g. using "visible_lines" parameter, or maybe calculating min and max line based on line height and visible area height), as opposed to putting 5s everywhere because I expect no more than 5 lines at the moment.
See also: Inventor's paradox.

Don't know what exactly do you mean by "features that you don't need now but may in the future". Is it about the horizontal scrolling?
Personally, I wouldn't add the extra code to handle horizontal scrolling unless I knew I need it, on the grounds that:
- I could easily adapt a properly written vertical-scrolling-only code to horizontal scrolling
- I will likely avoid the need for horizontal scrolling by design, because trying to see/read a whole that's both horizontally and vertically scrolled is extra annoying, as opposed to seeing a whole that needs vertical scrolling only
- simply put: just because I'm able to write horizontal scrolling doesn't mean I have to

Mind, I refer to horizontal scrolling in text boxes specifically, and text is generally better wrapped than horizontally scrolled. Note that text is usually linear in nature as well, i.e. you read a single sequence of words in a specific order, instead of jumping sideways. Then again, you have something like source code which benefits from no word-wrapping, but even then the code is easier to read when your lines are broken down in a way that no horizontal scrolling is needed.
If I was working with something like a level map instead, I would allow horizontal and vertical scrolling alike, because there's no reason to arbitrarily limit one or other dimension of a grid-like entity like level map.
 

TheouAegis

Member
It's just something you develop a sense for... As you write your code, try not to use hard numbers. We have enumerators for a reason. In my Gyruss project, I had some hard-coded values based on tangible stuff like how many sprites I plan to use for something. The thought crossed my mind, "Hey, what if you want to add more sprites for smoother effects?" So then I'd create some enumerators and add or multiply some values by those enumerators. I don't do it too often because even multiplying by 1 or adding 0 takes up a bit of CPU time, but it makes expanding my project easier, since I just have to update the enums instead of changing the whole project. When you aren't actually typing up code, jot down notes about what you want in your project, so as you go along those ideas will be on your mind as you type up code, then you will more naturally code around the ability to build around those ideas.

I personally also use a lot of buffers for storing calculations. Like, instead of lengthdir_x() when I only have 256 possible angles, which can be further reduced to 64 angles, it's faster just to precalculate lengthdir_x() for those 64 angles and then save those values in a buffer or array for use throughout the project.

Sometimes I will also write two or more bits of code and just comment one out whenever an idea for an expansion comes to mind. That way I don't slow down my program by calling subroutines I may ultimately not need. If i ever decide I want those expanded ideas, I just shift the comments around.
 
Thanks again for your reply. I'm very much getting an echo of my thought processes with the things you guys have suggested. So I'm going to take that as a positive. It seems that my thought processes are sound I just get tripped up sometimes. With the example of the surface when I discovered that I was writing to it constantly. It wasn't that I wanted or intend for it to do that I just had no way of knowing because my PC is too fast to notice.

I hadn't heard of the inventor's paradox before but that describes my experience fairly well.

It just aches my bones knowing I may have done something wrong. If I hadn't have asked about the surface thing it would have carried on working and I would have been none the wiser. That terrifies me.

Thanks again.
 
Last edited:
Top