• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Legacy GM Scrolling text / objects out of area

C

Crossoni

Guest
I am trying to do something very simple, but can't seem to grasp how to do it. I have made a rectangle and inside it couple objects. What I would like them to do, is to disappear when I scroll them outside of the rectangle and reappear when I scroll them back in. So far I have tried using surfaces, but if I use surfaces it messes up all my x and y positions, but with little adjustments I can make them work correctly. But for some reason I can't click on my objects anymore. This is my code so far:
Code:
///Object which initializes surface
//Create event
instance_create(x1+100,y-150,obj_show_profiles);

//Draw event
if (surface_exists(surface)) {
        draw_surface(surface,x1,y1);
}
Code:
///Object which uses the surface
//Draw event
if (surface_exists(obj_settings_profiles.surface)) {
    surface_set_target(obj_settings_profiles.surface);
} else {
    obj_settings_profiles.surface = surface_create(200,400);
    surface_set_target(obj_settings_profiles.surface);
}
draw_clear_alpha(c_white,0);
draw_self();
draw_text(x,y,name);
surface_reset_target();
If there is any better way of doing this than using surfaces, please let me know. I heard that something called "blending" could be used too to achieve this, but I have no idea what that blending is.

Here are 2 pictures to represent what I mean:
https://imgur.com/a/IsrGErj
 

NightFrost

Member
You probably should take another approach so you don't need to translate coordinates between screen and surface positions and do more checking on a click whether the button is visible, not visible or partially visible (to cull clicks outside the scroll area).

Instead, create a single object that simulates all the buttons. Give it an array with all the button texts and X1,Y1,X2,Y2 positions starting to count from 0,0. Also give it a variable that tracks how far down it has scrolled. When you draw, go through the list and subtract scroll amount from y-values before drawing to the surface. You can check and skip draw if button is outside the area, if you want to (Y2 < 0 or Y1 > area height). Then draw surface to desired screen position. For button clicks, do point in rectangle check in the simulating object to see if the click was inside the drawn scroll area. If it was, subtract scroll amount and corner y position from mouse y, then compare to button positions in array until you find one where it is between y1 and y2.
 
C

Crossoni

Guest
You probably should take another approach so you don't need to translate coordinates between screen and surface positions and do more checking on a click whether the button is visible, not visible or partially visible (to cull clicks outside the scroll area).

Instead, create a single object that simulates all the buttons. Give it an array with all the button texts and X1,Y1,X2,Y2 positions starting to count from 0,0. Also give it a variable that tracks how far down it has scrolled. When you draw, go through the list and subtract scroll amount from y-values before drawing to the surface. You can check and skip draw if button is outside the area, if you want to (Y2 < 0 or Y1 > area height). Then draw surface to desired screen position. For button clicks, do point in rectangle check in the simulating object to see if the click was inside the drawn scroll area. If it was, subtract scroll amount and corner y position from mouse y, then compare to button positions in array until you find one where it is between y1 and y2.
Thanks for the help, but I have a really hard time understanding what you mean. I can't make an array for them, since I use ds_lists and create the other profiles in game. "For button clicks, do point in rectangle check in the simulating object to see if the click was inside the drawn scroll area. If it was, subtract scroll amount and corner y position from mouse y, then compare to button positions in array until you find one where it is between y1 and y2." Could you demonstrate this with code?

I tried substracting y position to check whether to draw or not (without surfaces since surfaces achieves it already without substracting), but since my sprite is centered it goes half a sprite over the area and then disappears completely. Is there any way to make it disappear smoothly?
 

NightFrost

Member
Here's a basic framework.
Code:
Area_Width = 200; // Width of scroller.
Area_Height = 150; // Height of scroller.
Scroll = 0; // Amount that has been scrolled.
Scroll_Speed = 6; // Scroll pixels per step.
Surface = -1;

// Button information
Buttons[0, 0] = "Text 1"; // Text
Buttons[0, 1] = 10; // X1
Buttons[0, 2] = 10; // Y1
Buttons[0, 3] = 190; // X2
Buttons[0, 4] = 50; // Y2

Buttons[1, 0] = "Text 2";
Buttons[1, 1] = 10;
Buttons[1, 2] = 60;
Buttons[1, 3] = 190;
Buttons[1, 4] = 100;

Buttons[2, 0] = "Text 3";
Buttons[2, 1] = 10;
Buttons[2, 2] = 110;
Buttons[2, 3] = 190;
Buttons[2, 4] = 150;

Buttons[3, 0] = "Text 4";
Buttons[3, 1] = 10;
Buttons[3, 2] = 160;
Buttons[3, 3] = 190;
Buttons[3, 4] = 200;

Buttons[4, 0] = "Text 5";
Buttons[4, 1] = 10;
Buttons[4, 2] = 210;
Buttons[4, 3] = 190;
Buttons[4, 4] = 250;

Clicked = -1; // Button that was clicked, -1 if none.
Scroll_Top = 260 - Area_Height; // Maximum scroll amount.

Code:
// Mouse left clicked.
if(mouse_check_button_pressed(mb_left)){
    // If click happened inside scroll area.
    if(point_in_rectangle(mouse_x, mouse_y, x, y, x + Area_Width, y + Area_Height)){
        // Translate clicked screen position to position on the button list.
        var Click_X = mouse_x - x;
        var Click_Y = mouse_y + Scroll - y;
        // Go through button array.
        for(var i = 0; i < array_height_2d(Buttons); i++){
           // Check if clicked position is inside button bounds.
            if(point_in_rectangle(Click_X, Click_Y, Buttons[i, 1], Buttons[i, 2], Buttons[i, 3], Buttons[i, 4])){
                Clicked = i;
            }
        }
    }
}

else if(mouse_wheel_up()){
   Scroll = max(0, Scroll - Scroll_Speed);
}

else if(mouse_wheel_down()){
   Scroll = min(Scroll_Top, Scroll + Scroll_Speed);
}

Code:
// Recreate surface if necessary.
if(!surface_exists(Surface)){
    Surface = surface_create(Area_Width, Area_Height);
    surface_set_target(Surface);
    draw_clear_alpha(c_black, 0);
    surface_reset_target();
}

surface_set_target(Surface);
// Black background for scroll area.
draw_set_color(c_black);
draw_rectangle(0, 0, Area_Width, Area_Height, false);
for(var i = 0; i < array_height_2d(Buttons); i++){
   // Clicked button is red, otherwise white.
    if(Clicked == i) draw_set_color(c_red);
    else draw_set_color(c_white);
   // Offset button position by scroll amount.
    draw_rectangle(Buttons[i, 1], Buttons[i, 2] - Scroll, Buttons[i, 3], Buttons[i, 4] - Scroll, false);
    draw_set_color(c_black);
    draw_set_halign(fa_center);
   // Offset button's text position by scroll amount.
    draw_text(Buttons[i, 3] / 2, Buttons[i, 2] - Scroll, Buttons[i, 0]);
}
surface_reset_target();

draw_surface(Surface, x, y);

The buttons array is just an auxiliary. You can repopulate it whenever your list of profiles changes, in which case it is best to make it a script instead. The mouseclick here just provides a variable to turn the clicked button red, you can change it to whatever operation is necessary for the click. You can expand the array to help you decide what to do with the click, like add a profile number the button points at. Also, I made the scrollable area only 150px tall as I didn't want to write too many buttons. Change sizes to suit.
 
C

Crossoni

Guest
Here's a basic framework.
Code:
Area_Width = 200; // Width of scroller.
Area_Height = 150; // Height of scroller.
Scroll = 0; // Amount that has been scrolled.
Scroll_Speed = 6; // Scroll pixels per step.
Surface = -1;

// Button information
Buttons[0, 0] = "Text 1"; // Text
Buttons[0, 1] = 10; // X1
Buttons[0, 2] = 10; // Y1
Buttons[0, 3] = 190; // X2
Buttons[0, 4] = 50; // Y2

Buttons[1, 0] = "Text 2";
Buttons[1, 1] = 10;
Buttons[1, 2] = 60;
Buttons[1, 3] = 190;
Buttons[1, 4] = 100;

Buttons[2, 0] = "Text 3";
Buttons[2, 1] = 10;
Buttons[2, 2] = 110;
Buttons[2, 3] = 190;
Buttons[2, 4] = 150;

Buttons[3, 0] = "Text 4";
Buttons[3, 1] = 10;
Buttons[3, 2] = 160;
Buttons[3, 3] = 190;
Buttons[3, 4] = 200;

Buttons[4, 0] = "Text 5";
Buttons[4, 1] = 10;
Buttons[4, 2] = 210;
Buttons[4, 3] = 190;
Buttons[4, 4] = 250;

Clicked = -1; // Button that was clicked, -1 if none.
Scroll_Top = 260 - Area_Height; // Maximum scroll amount.

Code:
// Mouse left clicked.
if(mouse_check_button_pressed(mb_left)){
    // If click happened inside scroll area.
    if(point_in_rectangle(mouse_x, mouse_y, x, y, x + Area_Width, y + Area_Height)){
        // Translate clicked screen position to position on the button list.
        var Click_X = mouse_x - x;
        var Click_Y = mouse_y + Scroll - y;
        // Go through button array.
        for(var i = 0; i < array_height_2d(Buttons); i++){
           // Check if clicked position is inside button bounds.
            if(point_in_rectangle(Click_X, Click_Y, Buttons[i, 1], Buttons[i, 2], Buttons[i, 3], Buttons[i, 4])){
                Clicked = i;
            }
        }
    }
}

else if(mouse_wheel_up()){
   Scroll = max(0, Scroll - Scroll_Speed);
}

else if(mouse_wheel_down()){
   Scroll = min(Scroll_Top, Scroll + Scroll_Speed);
}

Code:
// Recreate surface if necessary.
if(!surface_exists(Surface)){
    Surface = surface_create(Area_Width, Area_Height);
    surface_set_target(Surface);
    draw_clear_alpha(c_black, 0);
    surface_reset_target();
}

surface_set_target(Surface);
// Black background for scroll area.
draw_set_color(c_black);
draw_rectangle(0, 0, Area_Width, Area_Height, false);
for(var i = 0; i < array_height_2d(Buttons); i++){
   // Clicked button is red, otherwise white.
    if(Clicked == i) draw_set_color(c_red);
    else draw_set_color(c_white);
   // Offset button position by scroll amount.
    draw_rectangle(Buttons[i, 1], Buttons[i, 2] - Scroll, Buttons[i, 3], Buttons[i, 4] - Scroll, false);
    draw_set_color(c_black);
    draw_set_halign(fa_center);
   // Offset button's text position by scroll amount.
    draw_text(Buttons[i, 3] / 2, Buttons[i, 2] - Scroll, Buttons[i, 0]);
}
surface_reset_target();

draw_surface(Surface, x, y);

The buttons array is just an auxiliary. You can repopulate it whenever your list of profiles changes, in which case it is best to make it a script instead. The mouseclick here just provides a variable to turn the clicked button red, you can change it to whatever operation is necessary for the click. You can expand the array to help you decide what to do with the click, like add a profile number the button points at. Also, I made the scrollable area only 150px tall as I didn't want to write too many buttons. Change sizes to suit.
Thanks that should get me started, I will look deeper into it this week if I have time and I will report back if I have more questions. Thanks for taking your time!
 
C

Crossoni

Guest
I finally got time to test your idea out and it worked like a charm. I actually used your code in a different project, but now I understand much more better how to create surfaces and how to create clickable text without using objects for every single button. Now I can finally have smooth transitions on my game :) Thank you so much NightFrost, God bless!
 
R

Raphael Caloz

Guest
You could do this in a couple lines of code with a shader. Apply the shader to the surface you are drawing the objects to, and for each of their pixels, if the coordinate is greater than the coordinant of the rectangle, just call the 'discard;' instruction within the fragment shader, and voila! You are done in a few minutes!
 

NightFrost

Member
You could do this in a couple lines of code with a shader. Apply the shader to the surface you are drawing the objects to, and for each of their pixels, if the coordinate is greater than the coordinant of the rectangle, just call the 'discard;' instruction within the fragment shader, and voila! You are done in a few minutes!
If OP knows shaders, yeah this could be done. Just discard the surface draw pieces and set a shader that receives the coordinates. Might be a pretty straightforward operation actually, as rest of the code remains the same; button definition, mousewheel and click catching, and draw loop.
 
C

Crossoni

Guest
You could do this in a couple lines of code with a shader. Apply the shader to the surface you are drawing the objects to, and for each of their pixels, if the coordinate is greater than the coordinant of the rectangle, just call the 'discard;' instruction within the fragment shader, and voila! You are done in a few minutes!
So could I use a shader like that on my main surface and just select a rectangular area where I want my objects to be seen? Or do I still have to use other surfaces?
 
R

Raphael Caloz

Guest
If you are ONLY rendering what is inside the rectangle, then you only need the application surface (default surface engine draws to). If you want to render anything around the rectangle, then you need a second surface, because in the fragment shader you are discarding (not rendering) pixels that are outside the rectangle. Makes sense ?
 
C

Crossoni

Guest
If you are ONLY rendering what is inside the rectangle, then you only need the application surface (default surface engine draws to). If you want to render anything around the rectangle, then you need a second surface, because in the fragment shader you are discarding (not rendering) pixels that are outside the rectangle. Makes sense ?
Ahh so does it only affect the object which is using the shader or does it affect everything in application surface? So can I for example draw inside the shader area in 1 object but all the other objects can simultaneously draw to application surface normally? Does rendering mean everything that the game is drawing at that time?
 

NightFrost

Member
There are commands to control that. You activate a shader with shader_set and deactivate with shader_reset. Only the draws that are executed between those will be affected by the shader you have set, the rest of draw commands are unaffected. Note that shaders are written with their own language, as they are a feature of graphics cards, not GMS itself.
 
C

Crossoni

Guest
There are commands to control that. You activate a shader with shader_set and deactivate with shader_reset. Only the draws that are executed between those will be affected by the shader you have set, the rest of draw commands are unaffected. Note that shaders are written with their own language, as they are a feature of graphics cards, not GMS itself.
Alright, but since I am completely newbie to shaders and surfaces seems to do the trick I will probably then stick to surfaces. If I have time someday I will definitely look into shaders some more. Is there any benefit for using a shader for this versus a surface other than less code?
 
Top