3
\$\begingroup\$

GameMaker and coding noob here, need help on something maybe trivial for some of you.

I'm working on the map part of a game. I have a map with spots on it, each spot is an instance of the same object.

I need to draw lines connecting the current spot and the close ones given a maximum distance (100 pixel radius). Also, I need to remove those lines that cross each other, and to keep track of which lines I already traveled.

I made an object ctr_obj that moves (on mouse click) from one spot to another if they are distant less than 100 pixels.

Create Event code :

smp = instance_nearest(x,y,map_spot)
x = smp.x;
y = smp.y;

Draw Event code :

for (i=0; i<instance_number(map_spot); i+=1)
{
    mapSpot[i] = instance_find(map_spot,i);
    near[i] = instance_nearest(x,y,map_spot)
    if point_distance(mapSpot[i].x,mapSpot[i].y,near[i].x,near[i].y)<100
    {
        draw_line(mapSpot[i].x,mapSpot[i].y,near[i].x,near[i].y)
    }
}

This part works as intended, I got the line drawn between the spot object I'm currently in and the other closest spot.

Ideally, I need to have all the lines of the map drawn all the time, not only when my ctr_obj is on a spot, and to keep track of the already traveled line (by changing its color, for example).

I tried to do something like that directly on the draw event of the map_spot object.

Draw Event code :

for (i=0; i<maxnumber; i+=1)
{
    inst[i]= collision_circle(x,y,100,map_spot,false,false);
    if inst[i] !=noone 
    {
        inst[i].x +=1000000;
    }
}
for (i=0; i<maxnumber; i+=1)
{
    if inst[i] !=noone
    {
        inst[i].x-=1000000;
    }
}
for (i=0; i<maxnumber; i+=1)
{
    draw_set_color(c_gray);
    draw_line(x, y, inst[i].x, inst[i].y)
    if done=1 && inst[i].done=1
    { 
        draw_set_color(c_lime)
        draw_set_alpha(0.8)
        draw_line(x, y, inst[i].x, inst[i].y)
    }
}

It was the only way I found to be able to get the instance_ID of multiple instances of the same object around a point. But as the event run on all map_spot I can't find a way to keep track of everything.

done is a variable to check if I already got on a particular spot. It doesn't really work as intended as if there are three map spots close enough each other I get a green triangle and not just the path used. And some paths don't get draw at all.

as show on the picture here

Can someone give me an hint?

EDIT 1 : So I try the solution, but something is not working. based on the solution provided, this is what I've done :

the script i use :

///scr_find_near(dist,object)

var list = ds_list_create();
with (argument1)
{
if (point_distance(x,y,other.x,other.y) < argument0)
    ds_list_add(list,other.id);
}
return list;

the draw_event code of the map_spot object :

closeList = scr_find_near(300,map_spot);
nb_near= ds_list_size(closeList)-1;

for (i = 0; i < nb_near; i += 1)
{
mp[i] = ds_list_find_value(closeList,i);
draw_set_alpha(1);
draw_set_color(c_gray);
draw_line(x,y,mp[i].x,mp[i].y);
info = string("nb of close point"+string(nb_near));
}

the "nb of close point" is just used as a debug for now (i have an "info" tool-tip for debug on all my objects). i try at first with mp as a "normal" variable, not an array, but actually nothing get draw. i can see that each point got the good number of "nb of close point" with the tool-tip, so the ds_list got the good number of info stored in.

but i don't have any line between the map_spot. I'm pretty sure i missed something obvious at this point, i spend too much time on that and can can't see clearly what i need to do. the script you give me work perfectly, but i think i made a mistake in my way of trying to use it to draw the line...

EDIT 2 I continue my trial and error, with the code below on the map_spot draw_event :

with (map_spot){
if point_distance(x,y,other.x,other.y)<300
{
    draw_set_color(c_gray)
draw_line(x,y,other.x,other.y);
if (done=1 && other.done=1) 
{
 draw_set_color(c_lime);
 draw_line(x,y,other.x,other.y);
 } } }

All the line between close spot are now correctly draw. That's a win. But i got my initial issue with keeping track of witch line have been traveled thru. Obviously, with the code above, all the line between map_spot that have been visited are now green, not just the one i really "used". example

the red number are the order of the map_spot i click on the map. the orange X mark the line that i don't want to be green.

I need to try with a global variable to keep track of the number of movement (i will need to keep track of that for other reason) maybe it can help me draw only the green line i want.

already thx for the help, i'm closer than before to make it work !

EDIT 3 :

I manage to get it to work, not with a DS_grid, i use a simple way (for me at least). I already had an invisible ctr_object that move when i clic on a node where i can go (checking distance) this object "ctr_map_position" had multiple usefull variable for my game map ; the ctr_map_position step event :

if (x != xprevious && y != yprevious) 
    {
    moveCount+=1;
    parSec += 0.1*point_distance(x,y,xprevious,yprevious)
    }

with that i keep track of the number of move(moveCount) and the total distance traveled in space (parSec). at each map_spot, i transfert the current moveCount value to a variable named "place" on the map spot, witch tell me the position of the map spot in the journey. (1,2,3,4 etc..) Finally I draw green line only between map_spot with variable 'place' = n and n+1

everything is now working as i intended to, so thx a lot for the help. obviously a ds_grid or an array like you say may be more optimized but i'm still learning with this project, and optimization is not currently my priority ! (even if i try to do think the best way possible)

\$\endgroup\$
  • \$\begingroup\$ Can you be clearer about what you want to achieve? You need a way to connect points of a mesh you create with mouse clicks, or what? Maybe, you want to get an effect similar to the space cartographer in the Mass Effect series, aboard the Normandy? \$\endgroup\$ – liggiorgio May 30 '16 at 10:45
1
\$\begingroup\$

I'm not sure about what you're trying to achieve, but I can help with your starting question: how to get the ids of nearby instances, given an object_index to look for.

The idea behind is: given a radius r (in your case, 100 pixels), there may be more than one instance which is distant less than r from a reference object. So, we have two properties to find out the instances we are looking for: their object_index, and their distance from us.

// Look for those instance closer than 100 px
list = ds_list_create();
with (map_spot)
{
    if (point_distance(x,y,other.x,other.y) < 100)
        ds_list_add(other.list,id);
}

If you put this code into a script file, let's say scrFindNearby, you can call it as a function from GML like this:

scrFindNearby();

By doing this, the instance variable list will store in a ds_list the ids of those instances closer than 100 pixels to the instance that called the function. At this point, you can do whatever you want with them. Also, you can edit the function above to save both id and distance into two separate ds_lists or even better a ds_grid or, if you aren't familiar with data structures, you can use arrays too.

Hope that helps. :)

EDIT 1

I can help with your problem drawing the right colour for lines. I figured out each map_spot has this variable done, which tells us if the spot has been visited before or not, so my answer is based upon this assumption.

Studying a path or a grid to figure out its properties, this is called graph theory. From now on, I'll call your map_spots nodes, and the lines connecting two spots arcs. In the second picture you uploaded, you created the eight nodes in crescent order, so green arcs are drawn between arcs 1 and 2, 2 and 3, and so on to arcs 7 and 8. This is correct, because you traveled to arc n coming from arc n-1, and the connecting line was crossed.

You were expecting gray lines connecting nodes 2 and 4, and nodes 3 and 8. So, why are they green? Easy: you are drawing lines using the wrong criteria. Lines are drawn gray or green depending on the wrong information. Why? Because you are tracking if a node was visited or not, but you need to know whence it was visited too.

Node (map_spot) labeled '4' was visited from node '3', then a green line is drawn. Being node '4' close enough to node '2', then we can draw a line here too. Node '2' was visited from node '1', so node '2' is marked as visited, and so is node '4'. That means the line connecting '2' and '4' should be green, because they both were visited!

This is correct according to the algorithm logic, but we know it's wrong from our perspective: that line should be gray, because nodes '2' and '4' have been visited, sure, but node '4' wan not visited from node '2', and vice versa.

I suggest you to keep the variable done for your map_spot objects, that will help you to keep track of which spots have been actually visited. In addition, you need a proper data structure to keep track of the paths you traveled through or not; each path (or line) can be identified with a starting point and an ending point, as those points are spots in the map. A two-dimensional array, or even better a ds_grid, are fine to hold this kind of information. This variable should belong to a controller object, or you can make it global if you want to.

For example, if you implement correctly a 2D-array to track the lines statuses, you may get something similar to this (based on the second image in your post):

/* Lines status matrix */
lines[0,0] = 1;     // Node 1 visited from node 1 (self-connected)
lines[0,1] = 1;     // Node 2 visited from node 1
lines[0,2] = -1;    // Node 3 can't be visited from node 1
lines[1,2] = 1;     // Node 3 visited from node 2
lines[1,3] = 0;     // Node 4 not visited from node 2 (as you expected)
...
lines[2,7] = 0;     // Node 8 not visited from node 3 (as you expected)

If this data structure is used, your draw code would look like this:

// Draw Event
with (map_spot)
{
    if (point_distance(x,y,other.x,other.y) < 300)
    {
        draw_set_color(c_gray);
        draw_line(x,y,other.x,other.y);
        if (ctrSpots.lines[index,other.index]==1)
        {
            draw_set_color(c_lime);
            draw_line(x,y,other.x,other.y);
        }
    }
}

Be careful when dealing with 2D-array or ds_grid indexes, you must always check the max number of instances before proceeding with drawing, and the data structure must be always be updated every time you add or remove a spot.

\$\endgroup\$
  • \$\begingroup\$ That looks exactly like what i need. i'll will try that with argument for the radius and the object to use it at other place ! i need to correct my code first, i tryed a lot of think and now it's broken. \$\endgroup\$ – wintermute May 30 '16 at 16:01
  • \$\begingroup\$ is their an error in this line : ds_list_add(other.list,id) it seams it should be something like ds_list_add(list,other.id) no ? cant manage to get it to work for now \$\endgroup\$ – wintermute May 30 '16 at 17:02
  • \$\begingroup\$ I edited my script to fix this error. Now list is an instance variable, you better declare it before calling the new script version. \$\endgroup\$ – liggiorgio May 30 '16 at 17:10
  • \$\begingroup\$ i edited the original message with what i did. the script look like it work, but i think i did something wrong after (in the way i use it to draw the line) \$\endgroup\$ – wintermute May 30 '16 at 23:23
  • \$\begingroup\$ Updated my answer, check it out although you are getting involved with some mid-level programming and I'm not sure you can deal with this stuff smoothly. \$\endgroup\$ – liggiorgio May 31 '16 at 11:00
2
\$\begingroup\$

To answer the question in the title, any of GM's collision-testing functions will only return one instance, so you need to get creative to retrieve more than one.

The best solution I've found (and I use this to ensure that my attacks hit multiple targets if more than one is collided with in a single step) is to disable the collision of instances that you've already dealt with before re-running your test to find the next instance. Be sure to restore collision before leaving this code so that you don't create any unintended consequences later in the step.

To achieve this, do the following:

  • Create a sprite with no image and no collision (let's call it no_mask).
  • On whatever object is running the collision test, create a 2d array.
  • Wherever you're testing collision, create a loop to iterate through potential collisions.
  • After getting a valid instance, store its id and its current mask_index in the array, then change its mask_index to that of the sprite you created that has no image/collision (no_mask). The next collision test you perform won't be able to return this instance because it won't be collidable.
  • Run whatever code you would on or with the instance that was collided with. After all instances are handled, allow the loop to end.
  • Iterate through the array you just built with another loop and restore each instance's mask_index from the array.

If you don't want to use a 2d array, create a mask property on the object you're testing for and use it to restore the mask_index to each instance.

Hope this helps.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.