GMS 2.3+ Collision between gui drawn objects

Simple, maybe stupid, question:
how does collision between gui drawn objects work?
i have to objects: a "static" inventory slot (that only follows the screen as it should) and a drag-and-drop-able inventory item.
i want to check for a collision between those two. both drawn in gui.
so far, if i drag the item unto the slot, nothing happens, like theyre not colliding
and most likely theyre not, since gui drawn objects are located where they are drawn on the screen
but how do i get around to make them collide?
 

Nidoking

Member
You must be using some variables to tell them where to draw on the GUI. You'll have to figure out the relative positions based on that. If you've got rectangular sprites, for example, you might check all four corners of one against the rectangle of the other. Should be fairly simple to write a function to do most of the work for you.
 

Vusur

Member
For something like that, you have to think about collision in a different way. GameMaker takes off a lot of work for collisions in general with all the function and events we have. Why? Because it's something we need often. A concept, that we would have to do ourself, is now simplified.
But what is collision in a naive way? Well, the coordinates of something overlap with something else. We check the coordinates of two different things against each other. As you noticed, the usual way of collision doesn't work with GUI. There is no "real object", it's just drawing. So have to go back a step, think about the "naive way" and do it ourself.

What have you on hand:
  1. The static slot with its x/y coordinates and width/height. You defined that somewhere and it's probably already in a GUI state.
  2. The Drag&Drop element in some ways. Something, that tells you, that you have something in Drag&Drop, probably glued at your mouse.
  3. Your GUI mouse position with device_mouse_*_to_gui
The naive Idea:
  1. Have you a Drag&Drop element? If yes...
  2. Is your mouse within your static slot, if the mouse is your pointer / does this element overlap with the static slot with, in case it's not glued at your mouse? If yes...
  3. Then you have a collision, do your collision stuff... like letting mb_left go and it puts the data into your slot.
The first check reduces the amount of checks in general. You probably won't have an drag&drop element all the time. The second check is the naive collision. This is what tells you, that there is a collision. You can do it with some collision functions (check the manual and find what fits the most) or complete naive with comparing x and y values. The third step is, what happens within your collition.

This is the rough idea.
 
For something like that, you have to think about collision in a different way. GameMaker takes off a lot of work for collisions in general with all the function and events we have. Why? Because it's something we need often. A concept, that we would have to do ourself, is now simplified.
But what is collision in a naive way? Well, the coordinates of something overlap with something else. We check the coordinates of two different things against each other. As you noticed, the usual way of collision doesn't work with GUI. There is no "real object", it's just drawing. So have to go back a step, think about the "naive way" and do it ourself.

What have you on hand:
  1. The static slot with its x/y coordinates and width/height. You defined that somewhere and it's probably already in a GUI state.
  2. The Drag&Drop element in some ways. Something, that tells you, that you have something in Drag&Drop, probably glued at your mouse.
  3. Your GUI mouse position with device_mouse_*_to_gui
The naive Idea:
  1. Have you a Drag&Drop element? If yes...
  2. Is your mouse within your static slot, if the mouse is your pointer / does this element overlap with the static slot with, in case it's not glued at your mouse? If yes...
  3. Then you have a collision, do your collision stuff... like letting mb_left go and it puts the data into your slot.
The first check reduces the amount of checks in general. You probably won't have an drag&drop element all the time. The second check is the naive collision. This is what tells you, that there is a collision. You can do it with some collision functions (check the manual and find what fits the most) or complete naive with comparing x and y values. The third step is, what happens within your collition.

This is the rough idea.
reading this, i wrote my code as follows:

GML:
//step

if global.lastgrabbed = id and position_meeting(mouse_guix,mouse_guiy,obj_inventory_slot1) and mouse_check_button_released(mb_left)
{
    obj_inventory_slot1.sprite_index = spr_add
}}
upon releasing mb_left global.lastgrabbed gets set to the instance id
then i check if the mouse overlaps the slot, if the left mb is released (instance dropped) and if the instance that was dropped = the id of this instance
its gonna change some variables later, the sprite change was just a test for visual purposes
 

chamaeleon

Member
I hope you realize that position_meeting() does not necessarily have anything at all to do with where you draw anything using the Draw GUI event since it deals with room coordinates of the instance vs some specified position. It does not necessarily correspond to the coordinate system used for GUI drawing.
 

Vusur

Member
If it works, sure.

For the first part I usually use a flag. Something like global.dragdrop_exists = true, when I click something, that becomes a drag&drop element. Then the step event is shorten to if( global.dragdrop && /* something else */) {...}. This eliminates the global.lastgrabbed = id and the case, that something fishy is going on. Cross-problems with other parts of the code. But this depends highly on your need.

I'm just concerned about position_meeting(mouse_guix,mouse_guiy,obj_inventory_slot1).

You said, that you draw the slot as a GUI. GUI x/y != object x/y. Does this always match in your case? Because this function matches the coordinates of the instance, not neccesarily the coordinates of your drawing.

What I mean is, if I put an object in my room at x/y = 64/32, then I have an x/y relative to the room.
If I do draw_sprite(spr_theSprite, 0, 64, 32); in the draw event, then this x/y is relative to your screen. Lets say you move 100 pixel to the left with your camera. The draw code is still the same and its still 64/32 relative to your screen. The object x/y stayed at 64/32 relative to the room. And if we map the GUI coords back to room coords, we get actually 164/32 room x/y wich is way more right then the "actual object".

The same is true, if you do [ICODE]draw_sprite(spr_theSprite, 0, x, y);[/ICODE].
object x/y is at x/y relative to the room. GUI x/y is at x/y relative to the screen. Move 100 pixel left: object x/y is at x/y relative to the room. GUI x/y is at x/y relative to the screen. Mapping GUI x/y to room x/y and we are at 100+x/y relative in the room. Now position_meeting won't work.

Edit: My naive way in 2.3+:

A MACRO for some neat and handy coding:

GML:
//my MACRO script
//Those are constants. Values that can be read but never change unless I change them here in the code.
//So use this, only if those numbers are fixed at any time. Gets rid of "magic" numbers
//Not needed, just a different style

#macro GUI_SLOT_PAD_TOP 32
#macro GUI_SLOT_PAD_LEFT 64

//... unrelated stuff
The draw event for the GUI. Usually a inventory controller object that is persistent.. then global wouldn't be needed.
GML:
//GUI draw of my inventory
//slot_sprite is a variable, that contains the sprite
if(global.inventory_visible)
{
   draw_sprite(slot_sprite, 0, GUI_SLOT_PAD_LEFT, GUI_SLOT_PAD_TOP); // we used our macros, 32 and 64 will be used
   //... maybe other stuff
}
A method, that uses rectangle_in_rectangle, because it was mentioned. I do it usually different, but why not.
GML:
//step event of my inventory / inventory_controller

if(global.inventory_visible && global.dragdrop_exists)
{
    //did we relaese the mb_left?
    //I check this before the collision, because this is an easier check with not much happening.
    //also the collision will have two states. We did collide or we did not.
    if(mouse_check_button_released(mb_left))
    {
        //get mouse coords relative to your screen
        var _mouse_x = device_mouse_x_to_gui(0);
        var _mouse_y = device_mouse_y_to_gui(0);

        //get slot coords, entangled because there are a lot of parameters

        //source - mouse as a small box, other stuff is also possible like _mouse_x + sprite_get_width(dragdrop_sprite); and so on.
        var _sx1 = _mouse_x - 1;
        var _sx2 = _mouse_x + 1;
        var _sy1 = _mouse_y - 1;
        var _sy2 = _mouse_y + 1;

        //dest - the slot. Everything is already relative to the screen. Just make a box
        var _dx1 = GUI_SLOT_PAD_LEFT; //  is 32
        var _dx2 = _dx1 + sprite_get_width(slot_sprite);
        var _dy1 = GUI_SLOT_PAD_TOP; // is 64
        var _dy2 = _dy1 + sprite_get_height(slot_sprite);

        //the collision: did we put the mouse box into the slot box
        if(rectangle_in_rectangle(_sx1, _sx2, _sy1, _sy2, _dx1, _dx2, _dy1, _dy2) =! 0)  //this function returns 0 for not touching, 1 for completly withing and 2 for overlapping. I allow completly within and overlapping, so != 0
        {
            //stuff that should happend
            slot_sprite = /* the information stored in dragdrop in any way*/
            global.dragdrop = false;
        }
        else
        {
            //we didn't collide, but usually stuff still has to happen
            //maybe cleanup in case we had information tied to the drag&drop. Maybe it's not needed. Depends what you wanna do
            global.dragdrop = false;
        }

    }

}
If you look what happens within all the vars, you'll see, that everything is relative to the screen. No Camera and no real objects involved. Everything is just basic math.

Edit 2: The reason why I introduced MACROS is the handiness. I use them in two different events. Imagine, I change my mind and want the slot somewhere else. I would have to remember, where I set those "magic numbers" with the same meaning. In draw GUI AND in the step event. They have to match or the collision will behave strange / will not work. With macros, I only need to change it in my script and GML does the rest for me. I don't really care where I used them.
 
Last edited:
I hope you realize that position_meeting() does not necessarily have anything at all to do with where you draw anything using the Draw GUI event since it deals with room coordinates of the instance vs some specified position. It does not necessarily correspond to the coordinate system used for GUI drawing.
doesnt this
GML:
var mouse_guix = device_mouse_x_to_gui(0);
var mouse_guiy = device_mouse_y_to_gui(0);
take care of that?
 

Vusur

Member
doesnt this
GML:
var mouse_guix = device_mouse_x_to_gui(0);
var mouse_guiy = device_mouse_y_to_gui(0);
take care of that?
The answer is maybe. If it's working, then just by coincidence. Imho its a case, where it works due false-positive logic. You still check against the coordinates of the instance and not the GUI element. If the instance coords and the GUI element coords happend to be the same, this will work. A case where it would work, explained with ugly paint:

Example1.PNG

If we move the camera now, this will happen:

Example2.PNG

It still works (I think), because the x/y padding is the same. But technically you are checking the mouse coords within the red box against the instance coords of the green box. Doesn't sound right and is false-positive.

To make it more obviouse here some questions:
  1. What happens, if you move the blue box without moving the green box? Are the coords still matching?
  2. Can you move the green box, without moving the blue box?
For the second question, here is another image of what happend, when the instance coords are tied to GUI coords:

Example3.PNG

I moved the instance in my room to somewhere else. It immediately affects the GUI. In this case, the coords are still matching but is it something we want?

This is why I called it a false-positive logic. It works, because the circumstances are just right. Instances and drawn GUI should be seen and treated as independent things. If you wanna manipulate the instance, then only manipulate the instance. If you wanna manipulate the GUI element, then only manipulate the GUI element. Mixing up both can lead to a bad time.

So if the first (and second image) is your case, it will work. That's why it is a maybe. But I wouldn't call it robust or right.
 
The answer is maybe. If it's working, then just by coincidence. Imho its a case, where it works due false-positive logic. You still check against the coordinates of the instance and not the GUI element. If the instance coords and the GUI element coords happend to be the same, this will work. A case where it would work, explained with ugly paint:


If we move the camera now, this will happen:


It still works (I think), because the x/y padding is the same. But technically you are checking the mouse coords within the red box against the instance coords of the green box. Doesn't sound right and is false-positive.

To make it more obviouse here some questions:
  1. What happens, if you move the blue box without moving the green box? Are the coords still matching?
  2. Can you move the green box, without moving the blue box?
For the second question, here is another image of what happend, when the instance coords are tied to GUI coords:


I moved the instance in my room to somewhere else. It immediately affects the GUI. In this case, the coords are still matching but is it something we want?

This is why I called it a false-positive logic. It works, because the circumstances are just right. Instances and drawn GUI should be seen and treated as independent things. If you wanna manipulate the instance, then only manipulate the instance. If you wanna manipulate the GUI element, then only manipulate the GUI element. Mixing up both can lead to a bad time.

So if the first (and second image) is your case, it will work. That's why it is a maybe. But I wouldn't call it robust or right.
i dont really know how to answer your questions
this is becoming very overwhelming tbh

if i scroll the screen and drag my item into my slot, it still works
so that doenst seem to change
i dont know what happens to the instance itself, as i can not see the actual position?

but i just noticed that i screwed up from the beginning
as soon as i change my screen resolution nothing gets drawn right anymore
and i dont understand why
i got this in my creation code

GML:
var base_w = 1920;
var base_h = 1080;
var aspect = display_get_width() / display_get_height();

    ww = base_w;
    hh = base_w / aspect;
    display_set_gui_maximise((display_get_width() / ww), (display_get_height() / hh), 0, 0);





var base_w = 1920;
var base_h = 1080;
var max_w = display_get_width();
var max_h = display_get_height();
var aspect = display_get_width() / display_get_height();
if (max_w < max_h)
    {
    // portait
     var VIEW_WIDTH = min(base_w, max_w);
    var VIEW_HEIGHT = VIEW_WIDTH / aspect;
    }
else
    {
    // landscape
    var VIEW_HEIGHT = min(base_h, max_h);
    var VIEW_WIDTH = VIEW_HEIGHT * aspect;
    }
camera_set_view_size(view_camera[0], floor(VIEW_WIDTH), floor(VIEW_HEIGHT))
view_wport[0] = max_w;
view_hport[0] = max_h;
surface_resize(application_surface, view_wport[0], view_hport[0]);
and im creating every instance using display_get_gui_width() and "..."_height
still nothing is created where it should according to my view
im thinking about giving the whole thing up man
 
for my initial problem i now did go with rectangle in rectangle

Code:
if rectangle_in_rectangle(obj_item_parent.bbox_left,obj_item_parent.bbox_top,obj_item_parent.bbox_right,obj_item_parent.bbox_bottom,bbox_left,bbox_top,bbox_right,bbox_bottom) = 2
and mouse_check_button_released(mb_left)
{
    global.grabbed_object.x = x
    global.grabbed_object.y = y
    
}
this now takes the item and kinda snaps it to the slots position
and it works regardless of screen scrolling
although i guess it shouldnt since this

Code:
global.grabbed_object.x = x
global.grabbed_object.y = y
is again not based on gui
 
to the other problem: how do i have to create my instances so they are created respectively to my view?
i just dont get it
view_get_wport() doesnt work
camera_get_view_border_x() doenst work
display_get_gui_width() doenst work
camera_get_view_x doenst work

so what the hell is it now that defines the border of what im seeing
 

Nidoking

Member
to the other problem: how do i have to create my instances so they are created respectively to my view?
Haven't you stated that you're drawing your instances using Draw GUI? Then where you create your instances is irrelevant. It's where you draw them that determines what you see.

view_get_wport() doesnt work
camera_get_view_border_x() doenst work
display_get_gui_width() doenst work
camera_get_view_x doenst work
You didn't list camera_get_view_width. Is that among the functions you've tried?
 
Haven't you stated that you're drawing your instances using Draw GUI? Then where you create your instances is irrelevant. It's where you draw them that determines what you see.



You didn't list camera_get_view_width. Is that among the functions you've tried?
i created my objects and had them draw_self() in the draw_gui event
do i have to tell the object in which position from respectively to the view it has to drawn at all times?
isnt that what the gui event does for me? like i create it, and it draws the sprite respectively itself?

You didn't list camera_get_view_width. Is that among the functions you've tried?
tried that
but i see my mistake now
if i hardcode the distance to the screen, its bound to move out of the picture, right?
instance_create_layer(display_get_gui_width()-1920, display_get_gui_height()-240,"hud_bg_layer",obj_btn_bar)
its always substracting 1920 from width
it should probably be like display_get_gui_width() minus the gui width in that case
right?
 

Vusur

Member
I looked into your code and the problem is a misunderstanding how GameMaker Studio works.
There are three things or layers you are using:
  1. The room
  2. The camera
  3. The GUI
The room is everything related to objects and instances. The "deepest layer".
The camera is what you see from the room. The "mid layer" and what the player sees.
The GUI is ontop of the camera/screen. The "top layer".

I highly suggest a deep read-through for atleast:

Camera And View Ports
Draw Event (scroll down for GUI)
maybe something related to the room and objects/instances
and maybe the code I wrote earlier

You are mixing up GUI with what the player sees and where instances are in the room. Because if you create instances based on GUI coords, then it tells me, that you treat the GUI as your room.
If you scroll back and read my edit of my first post, you'll see how I treated GUI independent from the actual instance. Where I create the instance doesn't matter. I can do instance_creat_layer(-1000, -1000, "Instance_Layer", obj_something which would create the instance way outside my room and it still would work.

As soon as you treat the GUI as your "room" and not independet from actual instances, you need smart coords mapping. With resolution changes, this is not a trivial thing.
You really should scrap the idea of creating instances based on GUI_x, *_y, *_height, *_width.

Your starting point is the GUI draw event. First off, don't use draw_self(); here. This is mixing up instance coords and GUI coords again. It uses the x and y from the instance.
Use draw_sprite(sprite_var, image_var, THE_X_IN_YOUR_GUI, THE_Y_IN_YOUR_GUI instead. You are in charge of the x and y coords now for the GUI. YOU tell GML where you want to draw something in your GUI. Store this information somewhere like I did with macros or in variables defined in the create event. If those values are hardcoded or dynamic is a different matter and can be changed on the fly later. Those are now the values, you check your mouse against. It is now completly entangled from instance properties.

To make it a little more clear. Assume THE_X_IN_YOUR_GUI == 5; and you do instance_create_layer(5, 10, "blabla", obj_something. So the instance has x == 5, right? You might think 5 == 5 in this case is the same. It's not. They have the same value, but the meaning behind it is different. They are the same, but not identical. The difference between the same and identical is really important and sometimes confusing.

It might be frustrating right now. I know that from experience. I had the same issue when I started. Don't give up, take a step back from your program and think about it more. Maybe draw some stuff with paint to visualize the stuff you are doing?

If you still wanna go the route with creating stuff related to your GUI, then you will go the hard route. It can work in the end, but it's not trivial. I can't really help more than I did with my code, images and explanation, because this approach defies my own logic and programming style. :(
 
"If you still wanna go the route with creating stuff related to your GUI, then you will go the hard route. It can work in the end, but it's not trivial. I can't really help more than I did with my code, images and explanation, because this approach defies my own logic and programming style. :( "

ok if you say this is not how YOU would logically operate
can you explain the basics of what you would do to achieve the result i'm looking for? like simply the slot and a draggable object overlapping everything else on the screen?
 

Nidoking

Member
Is there a compelling reason that you can't just put the slots and draggable objects in a layer that's in front of all the other layers and draw them normally?
 
Is there a compelling reason that you can't just put the slots and draggable objects in a layer that's in front of all the other layers and draw them normally?
this is for pen&paper
i have a map editor in the "background"
you can create and edit a map and then "play" on it, meaning dragging your player tokens from place to place, visualizing game mechanics like the players fighting etc
now i need to, whereever i am on the map, have the inventory and later the player stats and such at hand. i need to be able to create a shop window on screen where the players currently are, be able to drag the objects from the store to whatever inventory they shall go to
and when i want to create a different map without discarding the one i just created, i switch to a new room and the same inventory, stats etc. need to be there to
that means i have to be able to create the inventory when i need it, place something in there, have it follow the screen and destroy it when i dont need it anymore
it needs to save the data of the objects (like object_id and stats variables) for them to be later recreated
thats what its planned to be like

i already finished coding the whole app without the gui drawing stuff
but decided to start over since
a) my code was too messy because of my learning curve during the coding
b) nothing was drawn in gui and i had objects follow a "camera object" to scroll over they screen and follow the view, which led to the objects lagging behind very noticably

gonna try posting a link to a video so you can an image of what im doing
 
thats the almost finished "old version" of my companion
pls ignore the immense loading time for the item creation
thats a problem for another day:)

in that version the player screens and the map are seperate rooms
and thats what i want to eliminate
i want the map in the back, everything else i need in the gui layer or however in front of the map
its wayyy better for handling fights and weapon drops and such
 

Vusur

Member
Ok, now I'm glad I looked back here and saw your idea. What I'm about to do wouldn't help you in the slightest. o_O
GUI in GameMaker is more about displaying stuff and some simple interactions. There is a reason, why it's called draw GUI event and that there is no step GUI event.

If you want to do complex stuff with GUI, then it will be pain. A lot of convenient stuff that GameMaker does for you is in the instance layer. You would always need to map coords back and forth.

What you wanna do can be done without GUI. You can layer instances. Just assigne a depth to the objects, that should be in front. Depth Manual
Or create different instance layer. Layer and Room editor

The instance should stay in place in your camera? Then this.

GML:
//create event of an item
this_instance_padding_left = 12;
this_instance_padding_right = 33;

//step event of an item

if(is_visible)
{
    x = camera_get_view_x + this_instance_padding_left;
    y = camera_get_view_y + this_instance_padding_top;
}
Now this item will always stay 12 pixel from the left and 33 pixel from the top, even if you move the camera.
No need to deal with GUI. Another plus, it will behave like a normal instance. So your usual collision will work, click events will work with it and so on.

Edit: I went down this rabbit hole too, in the past. A complex inventory system with many clickable elements, drag&drops, buttons, lists and so on. Everything on the GUI. My conclusion back then? What the heck am I doing here. GML has beautiful functionalities for instances and I'm sitting here recreating stuff, that are build in, so that it works the same way with the GUI. I copied the object and instance behavior for the GUI.

In the end, I had to redo everything. I treated objects and instances as object and instances again, glued them on my camera, if needed and only needed a custom function for my clicks, so that the object in front is clickable and nothing in the back.
 
Last edited:
yeah to be honest it all started to seem too complicated to accomplish
im gonna try the padding thing instead of binding object to object
thank all of you for your help and patience
 

HayManMarc

Member
If you're talking about the sprite at the bottom-left, it lags because it's being drawn before the screen moves in the same step. So, the drawing order is wrong. You need to figure out where to put the code so the screen movement happens before the sprite is drawn.

(Unless I have that backwards. I stumble on this a lot, too.)
 

Vusur

Member
What is your instance creation order? In your IDE, Room Editor, select the instance layer that has the camera object and the sprite/bar object, bottom.

This lag is a symptom when the big wooden bar object gets old coords from the last step cycle but the view gets new coords from the current step cycle.

Something like this from the current step cycle view
  • sets x and y of the wooden bar relative to camera view
  • sets new x and y for the camera
  • all steps calculated
  • now draw everything.
What you want
  • set new x and y for the camera
  • set x and y of the wooden bar releative to the camera
  • all steps calculated
  • now draw everything
So swapping their order should do the trick. If not, doing the camera movement of the camera object in STEP_BEGIN event.... I hope (because I also stumble on this a lot)
 
i tried every possible combination of what you said
im giving you some details now
maybe im doing something wrong


room width 1920
room height 1080
viewport widht 1920
viewport height 1080
object following (obj_camera)
hborder25
vborder25
hspeed 20
vspeed 20

here are the objects with only the basic code:

OBJ_CAMERA
GML:
///step event
x = mouse_x
y = mouse_y
OBJ_BTN_BAR
Code:
///create event
xpadding = 0
ypadding = 840

///end step event
x = camera_get_view_x(view_camera[0]) + xpadding;
y = camera_get_view_y(view_camera[0]) + ypadding;

///draw_begin or draw_end
draw_self()
btw really appreciating your effort and sticking with me here
 

Vusur

Member
I have some news for you, I found the problem. Semi good news, it's easy to fix. Bad news, your current camera doesn't work and I didn't found a way to fix it. This was a "what the heck am I doing here"-moment. A different solution is easier than fiddeling a fix together.

This is the problem:
object following (obj_camera)
I try to explain, what went wrong. Your logic is, that you have an object, this object follows your mouse and the camera follows the object. This sounds like a good idea, but it's not. This has nothing to do with your creation order, instance order or whatever contrary to what I thought first. It's a "problem" how GML does this following.

The basic idea of what happened is this:
  1. you move the mouse, they get some x and y coords
  2. obj_camera sets its x and y based on your mouse, it moved
  3. your bar sets its x and y based on your camera
  4. every other step event that you have
  5. GameMaker noticed, that you moved the object that it should follow. If close to the border, it changes the x and y of your camera with different speed depending how close the camera object is.
At this point, all the calcultation based on your camera view are wrong. They used old values from the camera. This appears as lagging behind. They only can catch up the next step cycle and if the camera wasn't moved. That's why it snaps back into position, if you don't move the mouse. Because 5. is something, that GameMaker does, we don't have control over it.
I tried some fixes like storing in obj_camera how much we moved this cycle and use this in the bar object. But because the following is not a simple behaviour, it didn't worked well and got more and more complex. The idea was, before I move the camera, I already know how much the camera will move. Predicting the future basically.

I did a demo project with similar methods like you. A camera object to follow, a bar that should be glued at the bottom and with your step code of the camera. I had the exact same outcome. Lagging behind. Changing orders or begin/end step didn't work.

So what's the solution then? WE take full controll over the camera itself and ignore the following. I also avoided everything that has to do with GUI.

My room properties:
Code:
//room size 4000x4000

Viewport 0 used

//how much I see from the room
camera property width  = 480
camera property height = 360

//how "big" the actual screen is
viewport property width = 960
viewport prooperty heigth = 720
//basically everything is scaled up to x2
no object to follow! THIS IS IMPORTANT
This is my obj_bar. It simulates your wooden bar. Goal is, that it always stays at the bottom of the screen. It got a basic sprite, nothing interesting. This is the only stuff it does in this demo.
When dragged from the Asset Browser into the room, I choosed a random place. It doesn't matter. It's initial positon is even above the room in my case.

Create Event:
GML:
//set the bar position relative to the camera width and height
//untangled for debugging purposes
var _cam_width    = camera_get_view_width(view_camera[0]);
var _cam_heigth = camera_get_view_height(view_camera[0]);
var _spr_heigth = sprite_get_height(sprite_index);
var _spr_width    = sprite_get_width(sprite_index);

bar_padding_x = _cam_width - _spr_width;
bar_padding_y = _cam_heigth - _spr_heigth;
Step Event:
GML:
x = camera_get_view_x(view_camera[0]) + bar_padding_x;
y = camera_get_view_y(view_camera[0]) + bar_padding_y;
No draw event set. draw_self() is not interesting ;)

This is my obj_cameraController that handles the camera. This replaces your obj_camera AND the "object follow" of your room properties. It has no sprite and is dragged somewhere into the room. I put controller objects left of my room. Their position has no meaning.
This object handles all the camera stuff. I simplified as much as I could. Also, I have two versions. The first one is a quick one. It simulates "grabbing" the screen and "dragging" it around. Like holding mb_left in your GameMaker Studio 2 workspace. It's a little scuffed because I copied it from a full version, that contains a different approach.

Create Event:
GML:
//mouse pos variables with initial values, values don't matter
//they get the correct value each step later
mouse_origin_x = 0;
mouse_origin_y = 0;
Step Event:
GML:
//store old coords in case we do nothing, reduces redundancy later on
var _camera_x = camera_get_view_x(view_camera[0]);
var _camera_y = camera_get_view_y(view_camera[0]);

//only change camera, if we "grab" the screen and holding middle mb
if(mouse_check_button(mb_middle))
{
    //calculate the distance the mouse moved: previous step cycle -> current step cycle
    var _delta_x = mouse_x - mouse_origin_x;
    var _delta_y = mouse_y - mouse_origin_y;
 
    //calculate the new cam coords with delta
    //delta is how much the camera has to move
    //minus delta so the camera moves the opposite direction of the mouse movement.
    //it is the typical grab feeling for cameras
    //plus delta would move the camera in the direction which the mouse moves
    //feels weird

    _camera_x = _camera_x - _delta_x;
    _camera_y = _camera_y - _delta_y;
}

//make sure, we don't go outside of the room. This is an addition
_camera_x = clamp( _camera_x, 0, room_width - camera_get_view_width(view_camera[0]));
_camera_y = clamp( _camera_y, 0, room_height - camera_get_view_height(view_camera[0]));

//set new (or old if nothing happens) cam coords
camera_set_view_pos(view_camera[0], _camera_x, _camera_y);

//remember the mouse pos of current step cycle for next step cycle
mouse_origin_x = mouse_x;
mouse_origin_y = mouse_y;
Again, no draw event. This object doesn't even have a sprite.

The second one is a little more complex. It's the same cameraController. This simulates the behaviour of GMLs object following. It looks scary, because there is still a lot of redundancy. I just wanted it to work and didn't care about cleaning the code up. There is probably much to simplify (this is the step I do AFTER the logic works in a real project. I stopped here for the demo).
The basic idea is, defining 4 rectangles for your screen border. If the mouse moves into a rectangle, it moves the camera in this direction. Als as bonus, depending how close you are to the screen border, the faster is the scrolling. So you might see why it's a little more complext than grad&drag. This can also be done with objects. Just glue them to your screen border and check the mouse collision. It would reduce the rectangle definition.

Create Event:
GML:
/define the area, where the mouse triggers the camera movement

//I use structs here. If you don't have GML 2.3+, this won't work like this
//then just do cam_area_top_x1 = 0 and so on
//also, instead of defining structs/variables, you could use objects
//if the mouse collides with that object, then do the moving.

cam_area_top =
    {
        //full length of the screen
        x1 : 0,
        x2 : camera_get_view_width(view_camera[0]),
        //32 pixels from the top
        y1 : 0,
        y2 : 32,
    }
 
cam_area_right =
    {
        //32 pixels from right
        x1 : camera_get_view_width(view_camera[0]) - 32,
        x2 : camera_get_view_width(view_camera[0]),
        //32 pixel above the bar
        y1 : 0,
        y2 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar),
    }
 
cam_area_left =
    {
        //32 pixels from left
        x1 : 0,
        x2 : 32,
        //hight of the screen - bar height
        y1 : 0,
        y2 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar),
    }
 
cam_area_bottom =
    {
        //full screen length
        x1 : 0,
        x2 : camera_get_view_width(view_camera[0]),
        //32 heigh and above the bar
        y1 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar) - 32,
        y2 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar),
    }
Step Event:
GML:
//store old coords in case we do nothing, reduces redundancy later on
var _camera_x = camera_get_view_x(view_camera[0]);
var _camera_y = camera_get_view_y(view_camera[0]);

//the speed of the camera
var _cam_movespeed = 20;
 
//mouse in top area?
with(cam_area_top)
{
    //mouse enters the rectangle
    if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
    {
        #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
         
            //get area heigth; we only care about the height, it's the top area
            var _area_h = y2 - y1;
            //where are we in this box; this is different for all four cases
            var _mouse_pos_y_relative = (_camera_y + y2) - mouse_y;
            //whats the percantage
            var _percentage = _mouse_pos_y_relative / _area_h;
            //increase the speed relative to how close we are to the border
            _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
        #endregion
         
        _camera_y = _camera_y - _cam_movespeed;
    }
}
//mouse in right area?
with(cam_area_right)
{
    //mouse enters the rectangle
    if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
    {
        #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
             
            //we care about the width, right area
            var _area_w = x2 - x1;
            //where are we in the box; different from before
            var _mouse_pos_x_relative = mouse_x - (_camera_x + x1);
            //whats the percentage
            var _percentage = _mouse_pos_x_relative / _area_w;
            //increase the speed relative to how close we are to the border
            _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
        #endregion
        _camera_x = _camera_x + _cam_movespeed;
    }
}
//mouse in left area?
with(cam_area_left)
{
    //mouse enters the rectangle
    if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
    {
        #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
             
            //we care about the width, right area
            var _area_w = x2 - x1;
            //where are we in the box; different from before... again
            var _mouse_pos_x_relative = (_camera_x + x2) - mouse_x;
            //whats the percentage
            var _percentage = _mouse_pos_x_relative / _area_w;
            //increase the speed relative to how close we are to the border
            _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
        #endregion
         
        _camera_x = _camera_x - _cam_movespeed;
    }
}
//mouse in bottom area?
with(cam_area_bottom)
{
     
    if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
    {

         
        #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
         
            //get area heigth; we only care about the height, it's the top area
            var _area_h = y2 - y1;
            //where are we in this box; this is different again
            var _mouse_pos_y_relative = mouse_y - (_camera_y + y1);
            //whats the percantage
            var _percentage = _mouse_pos_y_relative / _area_h;
            //increase the speed relative to how close we are to the border
            _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
        #endregion

        _camera_y = _camera_y + _cam_movespeed;
    }
}

//make sure, we don't go outside of the room. This is an addition
_camera_x = clamp( _camera_x, 0, room_width - camera_get_view_width(view_camera[0]));
_camera_y = clamp( _camera_y, 0, room_height - camera_get_view_height(view_camera[0]));

//set new (or old if nothing happens) cam coords
camera_set_view_pos(view_camera[0], _camera_x, _camera_y);


//remember the mouse pos of current step cycle for next step cycle
mouse_origin_x = mouse_x;
mouse_origin_y = mouse_y;

You saw, there are many, many line of codes but it's a lot of redundancy. If you use this code, make sure to use your own coords for the creation event. Those rectangle are tailored around my layout. Just copying it won't work.
The last step I do for you is putting both version together. This shows us how powerfull a camera becomes, if we take controll over it.

Create Event:
GML:
//First option: grab the view

//mouse pos variables with initial values, values don't matter
//they get the correct value each step later
mouse_origin_x = 0;
mouse_origin_y = 0;

//second option: move mouse to the border (easy) but speed up the closer you are to that border (harder)

//define the area, where the mouse triggers the camera movement

//I use structs here. If you don't have GML 2.3+, this won't work like this
//then just do cam_area_top_x1 = 0 and so on
//also, instead of defining structs/variables, you could use objects
//if the mouse collides with that object, then do the moving.

cam_area_top =
    {
        //full length of the screen
        x1 : 0,
        x2 : camera_get_view_width(view_camera[0]),
        //32 pixels from the top
        y1 : 0,
        y2 : 32,
    }
 
cam_area_right =
    {
        //32 pixels from right
        x1 : camera_get_view_width(view_camera[0]) - 32,
        x2 : camera_get_view_width(view_camera[0]),
        //32 pixel above the bar
        y1 : 0,
        y2 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar),
    }
 
cam_area_left =
    {
        //32 pixels from left
        x1 : 0,
        x2 : 32,
        //hight of the screen - bar height
        y1 : 0,
        y2 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar),
    }
 
cam_area_bottom =
    {
        //full screen length
        x1 : 0,
        x2 : camera_get_view_width(view_camera[0]),
        //32 heigh and above the bar
        y1 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar) - 32,
        y2 : camera_get_view_height(view_camera[0]) - sprite_get_height(spr_bar),
    }
Step Event:
GML:
//store old coords in case we do nothing, reduces redundancy later on
var _camera_x = camera_get_view_x(view_camera[0]);
var _camera_y = camera_get_view_y(view_camera[0]);

//first camera behavior
//only change camera, if we "grab" the screen and holding middle mb
if(mouse_check_button(mb_middle))
{
    //calculate the distance the mouse moved: previous step cycle -> current step cycle
    var _delta_x = mouse_x - mouse_origin_x;
    var _delta_y = mouse_y - mouse_origin_y;
 
    //calculate the new cam coords with delta
    //delta is how much the camera has to move
    //minus delta so the camera moves the opposite direction of the mouse movement.
    //it is the typical grab feeling for cameras
    //plus delta would move the camera in the direction which the mouse moves
    //feels weird

    _camera_x = _camera_x - _delta_x;
    _camera_y = _camera_y - _delta_y;

     
}

//second camera behaviour
//change the camera if we get close to the screen border
//it's else, so both won't happen at the same time. grabbing the view while in a rectangle will swap to grab logic
else
{
    //the speed of the camera
    var _cam_movespeed = 20;
 
    //mouse in top area?
    with(cam_area_top)
    {
        //mouse enters the rectangle
        if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
        {
            #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
         
                //get area heigth; we only care about the height, it's the top area
                var _area_h = y2 - y1;
                //where are we in this box; this is different for all four cases
                var _mouse_pos_y_relative = (_camera_y + y2) - mouse_y;
                //whats the percantage
                var _percentage = _mouse_pos_y_relative / _area_h;
                //increase the speed relative to how close we are to the border
                _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
            #endregion
         
            _camera_y = _camera_y - _cam_movespeed;
        }
    }
    //mouse in right area?
    with(cam_area_right)
    {
        //mouse enters the rectangle
        if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
        {
            #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
             
                //we care about the width, right area
                var _area_w = x2 - x1;
                //where are we in the box; different from before
                var _mouse_pos_x_relative = mouse_x - (_camera_x + x1);
                //whats the percentage
                var _percentage = _mouse_pos_x_relative / _area_w;
                //increase the speed relative to how close we are to the border
                _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
            #endregion
            _camera_x = _camera_x + _cam_movespeed;
        }
    }
    //mouse in left area?
    with(cam_area_left)
    {
        //mouse enters the rectangle
        if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
        {
            #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
             
                //we care about the width, right area
                var _area_w = x2 - x1;
                //where are we in the box; different from before... again
                var _mouse_pos_x_relative = (_camera_x + x2) - mouse_x;
                //whats the percentage
                var _percentage = _mouse_pos_x_relative / _area_w;
                //increase the speed relative to how close we are to the border
                _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
            #endregion
         
            _camera_x = _camera_x - _cam_movespeed;
        }
    }
    //mouse in bottom area?
    with(cam_area_bottom)
    {
     
        if( point_in_rectangle(mouse_x, mouse_y, _camera_x + x1, _camera_y + y1, _camera_x + x2, _camera_y + y2 ))
        {

         
            #region //you can ignore this region, if you want to. This is only a method to simulate the follow behaviour of GML.
         
                //get area heigth; we only care about the height, it's the top area
                var _area_h = y2 - y1;
                //where are we in this box; this is different again
                var _mouse_pos_y_relative = mouse_y - (_camera_y + y1);
                //whats the percantage
                var _percentage = _mouse_pos_y_relative / _area_h;
                //increase the speed relative to how close we are to the border
                _cam_movespeed = clamp ( round(_cam_movespeed*_percentage), 1, 20);
         
            #endregion

            _camera_y = _camera_y + _cam_movespeed;
        }
    }
}

//make sure, we don't go outside of the room. This is an addition
_camera_x = clamp( _camera_x, 0, room_width - camera_get_view_width(view_camera[0]));
_camera_y = clamp( _camera_y, 0, room_height - camera_get_view_height(view_camera[0]));

//set new (or old if nothing happens) cam coords
camera_set_view_pos(view_camera[0], _camera_x, _camera_y);


//remember the mouse pos of current step cycle for next step cycle
mouse_origin_x = mouse_x;
mouse_origin_y = mouse_y;

What is the behaviour now? If you middle click somewhere on the screen, you grab the view and can drag it around. If you are near the screen border, it will move the camera similar to the following solution of GameMaker. You can grad the view, while it's moving and it will stop. It's pretty robust so far imho. Instance or creation order doesn't matter with this. I had a test, where different keys created new instances and thos instances where glued to the view. It worked flawless.
The only thing, that is still a problem, is the grabbing itself. It is to simple. Imagine you want to click an object on your screen with the middle mb. It would trigger the camera movement, if you slightly move your mouse. You can code around it. Having the middle mb as your camera signal is an easy one, but might fail, if other objects also have a middle_mb reaction. Calculate the delta movement and only if its big enough, it triggers the camera. Good for people with jittery hands. Disable the camera movement with middle mb while your mouse is ontop of a clickable object. And so on and so on. This is for you to figure out.

Hope this solves your problem.
 
Last edited:

Vusur

Member
Here some material how it looks like and behave.

This is the room view in my IDE. The white rectangle is my view later. You can see, that the bar is even outside of my view AND even outside of the room. It doesn't matter in my case and code.
The "?" is the camera controller. Also outside of the room and stays there. There is nothing else in my room.

RoomLayout.PNG

Here is a short (and bad cropped) video. I didn't bother with setting up my screen capture, so please don't hurt me ;).

You see, that the bar is glued on the bottom. It doesn't matter how hard I shake the screen, it stays there. The red boxes visulize the defined areas in the second version. After a while, a blue circle appears. This is an different instance, that gets created, when I press "SHIFT". Just as a test, that the order doesn't matter. It is also glued at my screen and no lag. :)

 
Last edited:
wow thaank you
im gonna implement that and try it
i think ill go with the screen grab alone as im gonna position a lot of buttons very near the bottom of the screen
again thank you very much for all the effort
if it actually works im gonna tip you 5euro via paypal
 
interestingly it didnt work when i copied the code in my project
but it works fine in a fresh project
so im starting from there
thank you very much
dm me your paypal:)
 

Vusur

Member
interestingly it didnt work when i copied the code in my project
Oh no! I hoped it would just work. There are probably some cross-interference. Changing a system, while other parts depend on it has always some trouble.
But if you are ok with a fresh project, then I'm happy. Losing progress feels bad for sure. :/

dm me your paypal:)
I really, really appreciate the thought. For real. :)
But I don't take money for something like this. Not in a community forum where I decided to help. This is just an hobby and fun for me. It's not like I blurt out solutions nor I'm a professionel game dev. Actually I learned a lot from this. So it is already a win-win situation.
If you wanna pay me back, then stick with your game and idea and once it becomes polished, show it to me^^

cheers!
 
I have a follow up question
:)
i used this code in my creation code to scale and keep the aspect ratio of my applictation:
GML:
var base_w = 1920;
var base_h = 1080;
var aspect = display_get_width() / display_get_height();

    ww = base_w;
    hh = base_w / aspect;
    display_set_gui_maximise((display_get_width() / ww), (display_get_height() / hh), 0, 0);

var base_w = 1920;
var base_h = 1080;
var max_w = display_get_width();
var max_h = display_get_height();
var aspect = display_get_width() / display_get_height();
if (max_w < max_h)
    {
    // portait
     var VIEW_WIDTH = min(base_w, max_w);
    var VIEW_HEIGHT = VIEW_WIDTH / aspect;
    }
else
    {
    // landscape
    var VIEW_HEIGHT = min(base_h, max_h);
    var VIEW_WIDTH = VIEW_HEIGHT * aspect;
    }
camera_set_view_size(view_camera[0], floor(VIEW_WIDTH), floor(VIEW_HEIGHT))
view_wport[0] = max_w;
view_hport[0] = max_h;
surface_resize(application_surface, view_wport[0], view_hport[0]);
now that my buttons and menus are not drawn in gui, its not working anymore
upon changing the resolution, the objects are not in position
without my creation code, gamemaker keeps the aspect ratio for me but all my sprites are very pixelated
and either way the fonts become pixelated
do i have to live with my sprites looking ****e when changing the resolution?
do i need to create different fonts with different sizes for each resolution or is there a way to properly scale fonts?
 
Top