GameMaker (Fixed)Click + Drag mechanic help

Corey

Member
Hello.
I am having an issue trying to set up a "click + drag" type mechanic for an object. Currently, the object is a placeholder and follows the mouse_x and mouse_y position, and it is set to move_snap on a 32 x 32 grid in a step event (the sprite is 32 x 32 pixels as well, and the origin is middle-center). I want to be able to have it set to when the left mouse button is held down, and the player drags the mouse cursor in a line, or diagonally, to create a "rectangle" of the objects based on the mouse_x and mouse_y position (similar to when you hold left mouse button on Windows desktop, it creates the navy-blue rectangle box to select desktop icons). When the left mouse button is released, I want it to create another object and delete the current placeholder object.

I know it's possible but I can't wrap my head around it. I guess this is what I get for not having coffee this morning. All help is greatly appreciated. Thank you.
 
E

Edwin

Guest
Huh? You mean like drawing? First of all, make sure that your room speed is faster or equal to the response speed of the monitor matrix (for example 60Hz means 60 room speed is okay) because you will have indents in pixels, so to speak.

Try this in your End Step event code:
Code:
// Check pressing the left mouse button
if (mouse_check_button(mb_left)) {
    // Mouse is over the placeholder
    var mouse_over = instance_position(mouse_x, mouse_y, obj_placeholder);
 
    // Create the placeholder if it's not over the mouse
    if (mouse_over == noone) {
        instance_create(round(mouse_x/32)*32-16, round(mouse_y/32)*32-16, obj_placeholder);
    }
}
So you'll create a obj_placeholder which sprite is a 32x32 square image and it's origin is 0X and 0Y. You see the quick maths I made in instance create function so it will create in the center of mouse_x and mouse_y can be said ignoring the obj_placeholder's sprite origin.

By the way, don't be shy and ask anything. I'm glad to be maximum helpful.
 
Last edited:

Corey

Member
@Edwin
Thank you for your quick response. Sorry if I didn't explain it correctly. I guess it would take place in a draw event to create the visual.

For an example I'm basically trying to re-create the click + drag mechanic in Rimworld, where you can create large rectangular areas to set up farming, housing, etc., each area has a hue (farming has green for instance), depending on how far you've moved your mouse. Each of the areas in the large rectangular grid fills up with objects instead of just the mouse x and y origin.

For the logic part: When the left mouse button is pressed it will begin a start point at the current mouse x and y position, and when the left mouse button is released it will create an ending point at the new mouse x and y position, creating a rectangular grid and creating instances inside of the grid on a 32 x 32 scale.
 

Corey

Member
For more context, I have done some tweaking with the obj_placeholder step event. Even though the below code is not working correctly, it will give you an idea of what I'm trying to do. This code creates the new object in the placeholder object.

Code:
//this code is redundant
x = mouse_x;
y = mouse_y;

move_snap(32,32);

if (mouse_check_button_pressed(mb_left))
{
    getpos = instance_position(mouse_x,mouse_y,obj_farmland);
}
if (mouse_check_button(mb_left))
{
    if !position_meeting(mouse_x,mouse_y,obj_farmland)
    {
        instance_create_layer(x,y,"Construct",obj_farmland);
    }
    if (mouse_x > getpos) //this is where I'm messing up
    {
        instance_create_layer(x+32,y,"Construct",obj_farmland);
    }
    if (mouse_y > getpos)
    {
        instance_create_layer(x,y+32,"Construct",obj_farmland);
    }
}
 
Last edited:
T

Taddio

Guest
Didnt try it as Im on phone atm, but seems to me you could just go
x= GRIX_X<<5;
//Same for y
in the step of the object you want to drag to clamp it to a 32*32 grid.
You have to keep track of the grid cell, with
GRID_X = x>>5;
//Same for y
tho.
 

Corey

Member
@Taddio
This is interesting. I am not very experienced with using binary operators. I'll do some tweaking and update my progress soon. Thank you.
 
Last edited:

Corey

Member
@Taddio
Alright, so I've done some coding and it seems to be working flawlessly. I did not use the bitwise operators and ended up using another strategy (even though it is possible to use either or). Thank you @Taddio and @Edwin for assisting me. Below is my code in case others need help with this feature as well. The basics here is when the player clicks the left mouse button, it sets a "starting point" at the mouse_x and mouse_y. While the player holds down the left mouse button and drags, it'll create a preview of the objects in a grid and pastes the confirmed objects in another layer (can be new or existing, this is the permanent placement). When the player releases left mouse button, it resets the initial variables so you can click + drag again. There are currently no limitations to how far the player can drag, which can be optimized later. Eventually I'll set it up to where you can delete the placed objects as well using this same method.

Step Event in placeholder object:
Code:
//Fully optimized - No bugs! =)
//Creation/Delete function working
//obj_farmland_placement is the placeholder
//obj_farmland is the actual object to create
if (count < 0) //just in case
{
   count = 0;
}
if (count > 1)
{
   count = 1;
}

clickedXX = mouse_x;
clickedYY = mouse_y;

var spritex;
var spritey;

//mb_left create grid
if (mouse_check_button_pressed(mb_left)) //mb_left initial click
{
   if (count == 0)
   {
       clickedY = mouse_y;
       clickedX = mouse_x;
       count += 1; //increment count by 1 (count could also equal 1)
       if !layer_exists("Preview")
       {
           layer_create(49900,"Preview");
       }
   }
}
if (mouse_check_button(mb_left) && count == 1) //mb_left held down and count equals 1
{
   canBuildFarm = true;
}
if (canBuildFarm == true)
{
   if layer_exists("Preview") //continue to delete/create Preview layer (to update visual)
   {
       layer_destroy("Preview");
   }
   if !layer_exists("Preview")
   {
       layer_create(49900,"Preview");
   }
   var rows = (clickedYY - clickedY) div 32; //get y cell
   var cols = (clickedXX - clickedX) div 32; //get x cell
   var flagr = 0;
   var flagc = 0;
   if (rows < 0)
   {
       flagr = 1;
   }
   if (cols < 0)
   {
       flagc = 1;
   }
   rows = abs(rows); //rows/cols must be absolute values
   cols = abs(cols);
   var i;
   for (i = 0; i <= rows; i++) //for loop to initialize rows
   {
       if (i > 16) //16 rows is the limit (can be changed)
       {
           break; //stop the for loop if above 16
       }
       spritey = 0;
       if (flagr == 0)
       {
           spritey = (round(clickedY div 32)+i)*32; //create +y
       }
       if (flagr == 1)
       {
           spritey = (round(clickedY div 32)-i)*32; //create -y
       }
       var j;
       for (j = 0; j <= cols; j++) //for loop to initialize columns
       {
           if (j > 16)// 16 columns is the limit
           {
               break;
           }
           spritex = 0;
           if (flagc == 0)
           {
               spritex = (round(clickedX div 32)+j)*32; //create +x
           }
           if (flagc == 1)
           {
               spritex = (round(clickedX div 32)-j)*32; //create -x
           }
           if !position_meeting(spritex,spritey,obj_farmland_placement) //if the position is free
           {
               instance_create_layer(spritex,spritey,"Preview",obj_farmland_placement); //create the "Preview" layer objects
           }
           if (mouse_check_button_released(mb_left)) //mb_left released
           {
               if !position_meeting(spritex,spritey,obj_farmland) //once mb_left is released, if the position is free
               {
                   instance_create_layer(spritex,spritey,"Construct",obj_farmland); //create the permanent "Construct" layer with new object
                                                                                   //the "Preview" layer object will be deleted the next time the mb_left is pressed
               }
           }
       }
   }
}
if (canBuildFarm == false && canDeleteFarm == false) //set placeholder to follow mouse when not clicking + dragging
{
   x = mouse_x-16; //follow the mouse and set x and y to -16 since the origin is at the top left to center on mouse pos
   y = mouse_y-16;
   move_snap(32,32); //snap to 32,32 grid
   if (layer_exists("Preview"))
   {
       layer_destroy("Preview"); //delete the Preview layer to refresh visual
   }
}
//mb_right delete grid function
if (mouse_check_button_pressed(mb_right)) //mb_right initial click
{
   if (count == 0)
   {
       clickedY = mouse_y;
       clickedX = mouse_x;
       count += 1;
       if !layer_exists("Preview")
       {
           layer_create(49900,"Preview"); //delete the Preview layer to refresh visual
       }
   }
}
if (mouse_check_button(mb_right) && count == 1) //mb_right held down
{
   canDeleteFarm = true;
}
if (canDeleteFarm == true)
{
   if layer_exists("Preview")
   {
       layer_destroy("Preview");
   }
   if !layer_exists("Preview")
   {
       layer_create(49900,"Preview");
   }
   var rows = (clickedYY - clickedY) div 32;
   var cols = (clickedXX - clickedX) div 32;
   var flagr = 0;
   var flagc = 0;
   if (rows < 0)
   {
       flagr = 1;
   }
   if (cols < 0)
   {
       flagc = 1;
   }
   rows = abs(rows);
   cols = abs(cols);
   var i;
   for (i = 0; i <= rows; i++)
   {
       if (i > 16)
       {
           break;
       }
       spritey = 0;
       if (flagr == 0)
       {
           spritey = (floor(clickedY div 32)+i)*32;
       }
       if (flagr == 1)
       {
           spritey = (floor(clickedY div 32)-i)*32;
       }
       var j;
       for (j = 0; j <= cols; j++)
       {
           if (j > 16)
           {
               break;
           }
           spritex = 0;
           if (flagc == 0)
           {
               spritex = (floor(clickedX div 32)+j)*32;
           }
           if (flagc == 1)
           {
               spritex = (floor(clickedX div 32)-j)*32;
           }
           if !position_meeting(spritex,spritey,obj_farmland_placement)
           {
               instance_create_layer(spritex,spritey,"Preview",obj_farmland_placement);
           }
           if (mouse_check_button_released(mb_right))
           {
               if instance_exists(obj_farmland) //check if obj_farmland exists before deleting anything
               {
                   with (obj_farmland) //send information to obj_farmland
                   {
                       if position_meeting(x,y,obj_farmland_placement) //if obj_farmland has the same position has obj_farmland_placement
                       {
                           instance_destroy(); //destroy obj_farmland
                       }
                   }
               }
           }
       }
   }
}
if (mouse_check_button_released(mb_right)) //if mb_right/left is released, set boolean conditions to false and execute false boolean functions
{
   canDeleteFarm = false;
   count = 0; //set count back to 0 to reset
}
if (mouse_check_button_released(mb_left))
{
   canBuildFarm = false;
   count = 0;
}
Draw Event in placeholder object:
Code:
//called to set semi-transparent
draw_sprite_ext(spr_farmland_placement,0,x,y,1,1,0,c_white,0.5);
Create Event in placeholder object:
Code:
//call these variables so they don't get repeated
canBuildFarm = false;
count = 0;
clickedX = 0;
clickedY = 0;
clickedXX = 0;
clickedYY = 0;
 
Last edited:
T

Taddio

Guest
No problem, mate!
I guess you found your next bedtime/sh**ter read, as a bonus!
This is the part we were talking about specifically
Binary Division

I said above that binary division was also incredibly useful, - why?

Well, first, let's take a quick look at how you do division, and why it’s going to be so useful. Let's take a simple number – 64, and divide by 32 (which gives 2 by the way, just in case you were wondering!)

64 / 32 = 01000000 >> 5 = 00000010

So there you do, shift the single bit down by 5 (which is the number of shifts required for 32 – look above), gives us 2. But what happens if here are other bits in there? Well let's take a look.

68 / 32 = 01000100 >> 5 = 00000010

So there you go…. It’s exactly the same. The bits we shift down are simply lost. This is actually really useful, because when dividing down if we need the remainder, there’s an even easier way to get it, which I’ll get to in a moment. But first, let's take a practical example. I have an X and Y position, and I want to get the grid cell this falls in, where the grid is 32x32 in size. This method allows is to store objects, collisions, flags – all manner of things, and access them very quickly. So here we go..

var X_index = x>>5;
var Y_index = y>>5;
cell_data = mygrid[# X_index,Y_index];


This is quick, very quick. This avoids the need to do a floating point divide, and then a floor( ) calculation – which all adds up.

And the tech blog page is here.
As you will see, even the basics are a primer for anything "most-common-sized" grid-based stuff!
 

Corey

Member
@Taddio
You're awesome. Thanks so much. I'll consider using binary calculations in the future.

I updated and fixed some errors and edited it in the post above. It still needs some optimization work but it's working as intended.

Edit: Optimized and bug-free! Even though it's optimized, it can still be laggy when creating very large scaled areas (since the code is creating/deleting layers and objects every step). I hope this helps others wanting to implement this feature too.
 
Last edited:
Top