GameMaker Drag an object along a branching path

P

Pyrokenisis

Guest
(I'm new to these forums, and to GMS2 in general, so if there is anything wrong with this post let me know!)

I've been trying to make a little minigame where you have to shift gears when prompted (like in a car). I want to be able to click and drag the handle along the paths towards the different gears, without it being able to deviate or get stuck if the mouse is a little above or below the allowed path.

Here is a crappy mockup of the idea in MS Paint to give a better idea of what I mean.
upload_2019-8-28_16-28-24.png

What I've tried so far is to create the handle object, with the collision mask only effecting the bottom of it, and created a solid object with a path around it, which kinda works, but feels really janky and the handle keeps getting stuck if it touches the wall.
upload_2019-8-28_16-33-52.png

Here is the code I was using for that:
Code:
// Create
grabbed = false;
collisionSpeed = 2;

// Global Left Released
grabbed = false;

// Left Down
grabbed = true;

// Step
if (grabbed){
    if(place_free(mouse_x + collisionSpeed, y) || place_free(mouse_x - collisionSpeed, y)){
        x = mouse_x
    }
    if(place_free(x, mouse_y + collisionSpeed) || place_free(x, mouse_y - collisionSpeed)){
        y = mouse_y;
    }
}
The problems with this are that the handle can escape the path if I drag the mouse out too far, it gets stuck if I move it too quickly (even with the buffer), I can only grab it from the bottom (where the collision mask is), and the movement is not very smooth or fun (since it is just the direct mouse input with no smoothing or friction). I can only imagine that isn't the best way to approaching this problem, so any solutions (even totally different from the one I was working on) are greatly appreciated. Thanks!
 
S

spoonsinbunnies

Guest
I would make the path its own object then I would do something like
Code:
xdif=x-mouse_x
ydif=y-mouse_y
if abs(xdif)>abs(ydif)
{
if collision_point(x+2*sign(xdif),y,obj_path)
x+=2*sign(xdif)
}
else
{
if collision_point(x,y+2*sign(ydif),obj_path)
y+=2*sign(ydif)
}
keep in mind the collision point will need tweaked so its checking where the bottom of your shifter is right now it checks where the orgin would be when its moving. you may also need to change all of the + to a minus I sometimes write codes like these backwards so instead of pulling to the mouse it might push away on the path currently
 

NightFrost

Member
Consider one design point. What to do at intersections when mouse is not perfectly aligned to the path? Assume that in your first mock-up, the handle has been pulled down from the start position onto the horizontal path. Now the player throws the mouse in one quick motion all the way to the left edge of the screen and slightly up. As you process the new position for the handle, there is the intersection towards "2" on the way. Should the handle A) move left, since mouse position is to the left, or B) move up, since the mouse position is also towards the top? Most user friendly decision is, the handle should move towards whichever quadrant the mouse is at. Since it is towards the left and only slightly up, the handle should move left.

To move the handle, you'll need to check if it has been grabbed. If so, compare handle position to mouse position and calculate the difference. You could decide not to process movement that is smaller than the handle radius so slight mouse movements do not make it twitch, but that's up to your design decision. Check which quadrant the mouse is in (use for example point_direction) and select that as the movement direction. The amount to move is the difference between mouse and handle positions.

Now that you have both direction and distance, you just loop distance amount of times and collision detect one pixel towards movement direction and hange position by one pixel each time there is no collision. Terminate loop as soon as collision is detected. After this is done, your handle is as close to mouse as it can get without leaving the path.

This will move the handle to the mouse in a single step, which might not be visually pleasing when it is pulled across large distances. If you want you could give the handle a maximum speed. When you calculate the distance it needs to move, you cap the movement at max speed, so it will take multiple steps for the handle to arrive. However if you decided to put a "dead zone" to the movement I mentioned above, it can leave the handle's center slightly away from the mouse if the next movement step starts with the handle touching the mouse. You'd also need to decide what should happen when the player lets go of the handle while it is moving. Should it stop immediately, or finish the movement? In the latter case the handle would need to remember the target position as the mouse position can no longer be relied on.

EDIT: actually, I just realized this creates a huge problem when trying to switch to a perpendicular path at T-intersections. It needs pixel-perfect positioning from the player. So, forget about choosing a single direction when the mouse is offset from the handle. The handle should collision check towards both directions that mouse is offset from the handle. However, on every step it should first handle the direction that is in the same quadrant the mouse is in. An example: Assume again that the handle has been pulled down of the starting position and to the left, halfway towards the T-intersection. The player wants to take the handle towards "2" so they pull the mouse left, so it goes across the intersection, and all the way up past the end of the straight section. As the handle moves, it will first move left as it is the only available path, but when it arrives to the T-intersection, it moves up. This is because the mouse is in the top quadrant, so vertical collision check is handled first, and the handle takes a step up. Horizontal check will then result in collision and causes no change in position. Afterwards, the handle keeps moving up as that is the only available direction.
 
Last edited:
Top