SOLVED Hi my first post! Having an issue with connecting objects

D

Darkwing

Guest
Hello all,

I am creating my own game from scratch (my second one) and this one is pretty ambitious. I have solved multiple hurdles already but I have one which is stumping me. I have made a way to build a spaceship with individual object parts and then control that ship as if it were a single unit, while remaining individual objects with their own stats like HP, energy drain, weight, etc..., but if an object is destroyed and makes some other objects become disconnected, I want them to actually become disconnected, no longer controllable or having their stats influence the ship controls.

My idea was to give each object the variable "connected" and set it to 1 when created. Then, upon the destroy event of any object, create the object "connection check" which would only serve as a trigger for each object except the cockpit to set its variable "connected" to 0. Then have some script that says if "connected" = 1, check one cell above, below, left, and right and if a solid object exists, set its variable "connected" to 1. Then repeat that last command like 100 times or something so that each repeat it connects all objects outward from the cockpit. But it doesn't work.

I have been scratching my head for days trying to figure out how to make this work and I couldn't find anything on the forums regarding this. I am still kind of a n00b at programming so ELI5 if you got any suggestions on how to do this. I cant give away too many details because this game is gonna be awesome and I don't want anyone ganking my ideas before I can put it all together. I am really slow programmer and my last game took me 10 yrs lol so I have my reasons.

Anyways, thanks for any and all help!
 
Last edited by a moderator:

angelwire

Member
Hello, welcome to the forums!

Here's a quick way to do it:
Keep the code that resets the connected variable of all the parts to to 0, you're on the right track there.

I would use recursion to find all the connected parts.
In all the spaceship part objects (including the cockpit) have a function called "connect_parts()". In that function you check all 4 directions. And if there is a part in that direction with the connected variable still at 0, you set the connected variable to 1. Then tell that object to call the connect_parts() function.

So when a spaceship part is destroyed tell the spaceship cockpit object to call the connect_parts function cockpit_object.connect_parts(). This will create a chain reaction that will find all the connected objects.

GML:
//This function should be in the create event for all spaceship part objects
function connect_parts()
{
    var _right_object = /*get object to the right or undefined if none*/
    if (_right_object != undefined and _right_object.connected == 0)
    {
        _right_object.connected = true;
        _right_object.connect_parts();
    }
   
    //Do the same for left, right, down...
}
Hopefully this helps. I couldn't be too specific because I don't know exactly how your game works.
 
D

Darkwing

Guest
I will give that a try. I have not used the function command yet, as I am really really new to GML but the logic seems sound. Also, quite elegant as it will take less processing time to run. Thanks so much for the time and effort you put into your response, when I get another chance to write I will try it out!

Any other suggestions are welcome as well. Again, thanks!
 

OnLashoc

Member
Whatever you do, don't stop. Don't give up. Keep going, and if you have to focus on something else for a bit, go focus on something else for a bit.

As someone with way more sage advice than I when it comes to GML / GMS, talk to the rubber duck about your problem in depth. Before you know it, you'll be figuring things out and solving your problems without any help. *rIKMaN*

I have in my mind how I would potentially do it, but since I am still kinda new (year 3 for me), I don't want to give you any of my bad habits.

Onnie
 

Rob

Member
I think the "best" answer depends on how you're connecting stuff at the moment.

If each part is only connected to certain others (and knows what they are) then my solution would be for each part to have its own list which contains those connected parts.
Then, when a part is destroyed you can go through that parts list with a loop and make every part in it float away or whatever.

You can keep a separate "ship" list which contains all parts that are still connected to the ship (remembering to remove lost parts from this list too) and the ship list can be used to easily work out stars if needed.
 
D

Darkwing

Guest
Hello, welcome to the forums!

Here's a quick way to do it:
Keep the code that resets the connected variable of all the parts to to 0, you're on the right track there.

I would use recursion to find all the connected parts.
In all the spaceship part objects (including the cockpit) have a function called "connect_parts()". In that function you check all 4 directions. And if there is a part in that direction with the connected variable still at 0, you set the connected variable to 1. Then tell that object to call the connect_parts() function.

So when a spaceship part is destroyed tell the spaceship cockpit object to call the connect_parts function cockpit_object.connect_parts(). This will create a chain reaction that will find all the connected objects.

GML:
//This function should be in the create event for all spaceship part objects
function connect_parts()
{
    var _right_object = /*get object to the right or undefined if none*/
    if (_right_object != undefined and _right_object.connected == 0)
    {
        _right_object.connected = true;
        _right_object.connect_parts();
    }
  
    //Do the same for left, right, down...
}
Hopefully this helps. I couldn't be too specific because I don't know exactly how your game works.
I tried to implement this but ran into a couple of issues. First, it keeps giving me the error "unknown function connect_parts" and secondly, I am having trouble figuring out how to "get object to the right". My game is set up on a 32x32 grid and was using the "check empty" command but I dont know how that translates into your code you provided.

Also, the ship will have a virtually infinite number of user-definable configurations so a list seems improbable. I like the solution above just need some help implementing it.

Thanks again!
 

Gamebot

Member
Is there a way to post the current code? It sounds like a call or return error most likely do to a spelling mistake? You can use the "</> code" under the insert (...down arrow) above your text editor like this:

CREATE:
GML:
// CREATE EVENT CODE //
FUNCTION THIS:
GML:
// MY FUNCTION CODE //
 

angelwire

Member
  1. If you don't already have a parent object for all the ship parts, you should make one called "ship_part_parent". And then set all of the ship part objects to have the ship_part_parent object as their parent. It's very helpful because you can refer to the "ship_part_parent" object instead of needing to check for "ship part A" or "ship part B" or "ship part C" etc... Here's a link to the manual page on parents (scroll down to options): https://docs2.yoyogames.com/source/_build/2_interface/1_editors/objects.html
  2. Remove any connect_parts() code that you've added to the objects.
  3. In the create event of the ship_part_parent object, add the connect_parts() function. I've updated it below. I've changed undefined to noone because instance_position() returns noone if there is just an empty space.
  4. If your ship parts have code in their create events, add "event_inherited();" to the create event. This will make sure any code in the individual ship parts doesn't override the ship_part_parent's code.
  5. You'll want to use instance_position() to check if there is a "ship_part_parent" object to the right of the current instance that is using the script.
  6. If instance_position(...) returns noone then do nothing.
  7. If not, then use connect_parts() with the the instance returned by instance_position()
Here's the connect_parts(...) function:
GML:
function connect_parts()
{
    /*Right*/
    var _instance_right = instance_position(x + 32, y, ship_part_parent);
    if (_instance_right != noone and _instance_right.connected == false)
    {
        _instance_right.connected = true;
        _instance_right.connect_parts();
    }
    /*Right*/

    /*Left*/
    //Left code here
    /*Left*/

   //...up and down

}

One thing you should be aware of, is that you definitely need to check if _instance_right.connected == false. Because if you don't to that, then if two ship parts are together, they'll constantly tell the one next to them that they're connected. This will go on forever. So you only want a ship part to tell their neighbors that they're connected if they're not already connected. And be sure not to forget about adding "event_inherited()".

If you get any more errors, be sure to post again. And it would be helpful if you can include the entire error message so that it has as much information as possible.
 
D

Darkwing

Guest
Thanks!

I should have made mention I am using Game Maker Studio version 1. Turns out, I need to use a script to create the function. (Thanks reddit) So my only issue now is the "get obj to the right" which you have given me a good start on!

I do have a parent part for all the ship parts, but it is also a ship part itself. Can I still use a ship_part_parent and just add that one part as a child or do I need to reorganize? Also, for the

GML:
var _instance_right = instance_position(x + 32, y, ship_part_parent);
where the ship_part_parent is there, what is that argument? I am having trouble finding help for the instance_position command.

Again, thanks! This is really educational and I am learning a lot!
 

angelwire

Member
Ah, GMS 1 is definitely different, sorry about that.

I would recommend reorganizing the parent to only be the ship_part_parent. Having a parent of a parent is just extra complexity that you don't really need.
Instead of going through each ship part to change the parent, you could try: duplicating the parent ship part, renaming the original parent ship part to ship_part_parent, and then rename the duplicated object back to the original ship part name. Then all you need to do is change the parent of the one ship part and the others should be set as the children of the ship_part_parent.

The third argument for instance_position is the object that you check for. It will only return an instance if it is an instance of that object (or in this case, an instance of a child of that object). This is sort of a filter, so the instance_position will ignore things like missiles or asteroid objects. Only ship_part_parent objects or children of that object will be "seen".
 
D

Darkwing

Guest
Thanks so much angelwire!

I think I almost have it working, the only problem is that GMS1 does not seem to accept the

GML:
_instance_right.connect_parts();
because apparently the only thing it recognizes after the dot is a variable. However, I know you can tell another object to call a script because there is a DnD for just that. However, I cannot find the code for it anywhere! So if anyone knows the code for the built in script "Script_execute" DnD so that I can code in the part that says "for _instance_right" like "apply to" or something, please let me know. This seems like it should be easy to find, but I have spent much time looking and not finding.

Again, thank you for all the help! It is much appriciated :)
 

angelwire

Member
No probnlem!
With GMS 2, _instance_right.connect_parts(); valid because functions can belong to instances just like variables can. That's not the case in GMS1. You'll need to make more changes than just creating a script. The script will need to allow an argument to be passed into it. So instead of _instance_right.connect_parts() you would use connect_parts(_instance_right).
So the new script would look more like this:
GML:
/*Right*/
var _part = argument0;
var _instance_right = instance_position(_part.x + 32, _part.y, ship_part_parent);
if (_instance_right != noone)
{
    if (_instance_right.connected == false)
    {
        _instance_right.connected = true;
        connect_parts(_instance_right);
    }
}

/*Left*/
//...
 
D

Darkwing

Guest
No probnlem!
With GMS 2, _instance_right.connect_parts(); valid because functions can belong to instances just like variables can. That's not the case in GMS1. You'll need to make more changes than just creating a script. The script will need to allow an argument to be passed into it. So instead of _instance_right.connect_parts() you would use connect_parts(_instance_right).
So the new script would look more like this:
GML:
/*Right*/
var _part = argument0;
var _instance_right = instance_position(_part.x + 32, _part.y, ship_part_parent);
if (_instance_right != noone)
{
    if (_instance_right.connected == false)
    {
        _instance_right.connected = true;
        connect_parts(_instance_right);
    }
}

/*Left*/
//...
I have tried the code and get the FATAL ERROR for object _ship_part_parent: Variable Generator01._part(100013,-2147483648) not set before reading it. At (line 1) - connect_parts(_part);

I have no idea how to fix this... starting to lose hope...
 
D

Darkwing

Guest
GML:
var _part = argument0;
var _instance_right = instance_position(_part.x+32,_part.y,ShipParent);
if (_instance_right != noone and instance_right.connected == 0)
{
_instance_right.connected = 1;
connect_parts(_instance_right)
}

var _part = argument0;
var _instance_left = instance_position(_part.x-32,_part.y,ShipParent);
if (_instance_left != noone and instance_left.connected == 0)
{
_instance_left.connected = 1;
connect_parts(_instance_left)
}

var _part = argument0;
var _instance_up = instance_position(_part.x,_part.y+32,ShipParent);
if (_instance_up != noone and instance_up.connected == 0)
{
_instance_up.connected = 1;
connect_parts(_instance_up)
}

var _part = argument0;
var _instance_down = instance_position(_part.x,_part.y-32,ShipParent);
if (_instance_down != noone and instance_down.connected == 0)
{
_instance_down.connected = 1;
connect_parts(_instance_down)
}
 

chamaeleon

Member
That does not look like a script that has its first line be connect_parts(_part). You should post the event code that calls this script.
 

angelwire

Member
You're on the right track! You don't need all the var _part = argument0; lines, just the very top one. By the looks of your code, the problem is probably when you first try to connect the parts with "connect_parts". You have to include the first part that you want to start from (probably the cockpit object). If you just use "connect_parts()" then there won't be any arguments. The "_part" variable would set to the argument0 variable which would be empty. So if you try to use "_part.x" when _part is empty, Gamemaker will throw an error because it doesn't know what object's x you're talking about.
 

Director_X

Member
What if you had different invisible connecting objects instead?

connectors.JPG

The invisible connectors always stay in their offset positions marking the attachment locations, regardless of destroyed components or not, and you can always re-assemble parts and pair them to any location you wish, without running heavy loop code.

Hope this helps. :)

PS. Welcome to GM! :)
 
What if you had different invisible connecting objects instead?

View attachment 38639

The invisible connectors always stay in their offset positions marking the attachment locations, regardless of destroyed components or not, and you can always re-assemble parts and pair them to any location you wish, without running heavy loop code.

Hope this helps. :)

PS. Welcome to GM! :)
Running a well designed loop is almost always going to be faster than having a bunch of objects hanging around.
 
D

Darkwing

Guest
You're on the right track! You don't need all the var _part = argument0; lines, just the very top one. By the looks of your code, the problem is probably when you first try to connect the parts with "connect_parts". You have to include the first part that you want to start from (probably the cockpit object). If you just use "connect_parts()" then there won't be any arguments. The "_part" variable would set to the argument0 variable which would be empty. So if you try to use "_part.x" when _part is empty, Gamemaker will throw an error because it doesn't know what object's x you're talking about.
I do not understand how arguments work. Not even sure what they are. I got frustrated and removed mention of _part and arguments and reworked how I call the script. I got rid of the errors and it partially works. New code:


GML:
var _instance_right = instance_position(x+32,y,ShipParent);
if (_instance_right != noone and _instance_right.connected == 0)
{
_instance_right.connected = 1;
connect_parts(_instance_right)
}

var _instance_left = instance_position(x-32,y,ShipParent);
if (_instance_left != noone and _instance_left.connected == 0)
{
_instance_left.connected = 1;
connect_parts(_instance_left)
}

var _instance_up = instance_position(x,y+32,ShipParent);
if (_instance_up != noone and _instance_up.connected == 0)
{
_instance_up.connected = 1;
connect_parts(_instance_up)
}

var _instance_down = instance_position(x,y-32,ShipParent);
if (_instance_down != noone and _instance_down.connected == 0)
{
_instance_down.connected = 1;
connect_parts(_instance_down)
}
It disconnects all parts and reconnects the ones next to the cockpit, but does not chain to the other parts. I feel I am close, probably has to do with the arguments thing, but I dont understand that. I would post the code I use to call the script, but I use DnD for almost all of my code and I can't figure out how to post that. Sorry for being a pain, but I really appreciate the time you have taken to help me.
 
D

Darkwing

Guest
What if you had different invisible connecting objects instead?

View attachment 38639

The invisible connectors always stay in their offset positions marking the attachment locations, regardless of destroyed components or not, and you can always re-assemble parts and pair them to any location you wish, without running heavy loop code.

Hope this helps. :)

PS. Welcome to GM! :)
I am not sure what you mean by invisable connectors, or how this would work. I am open to other options so if you could elaborate a bit I would be interested in your idea.
 

angelwire

Member
By taking out the _part variable, you were always checking next to the cockpit object.

When said connect_parts(_instance_right) then Gamemaker goes back and starts running the script from the top again. Except _instance_right is the new _part. So when it goest to instance_position(_part.x,_part.y, ShipParent) then you're not checking from the cockpit any more.

I thought about writing a huge page on recursion and arguments, but it's actually way easier to work around the arguments.

Try this, but make sure you run the script from inside the cockpit object:
GML:
var _instance_right = instance_position(x+32,y,ShipParent);
if (_instance_right != noone and _instance_right.connected == 0)
{
with(_instance_right) //add the with
    { //add the {
    _instance_right.connected = 1;
    connect_parts(); //You don't need to use arguments any more
    } //add the }
}

and then do the same thing with the other 4 directions.
 
D

Darkwing

Guest
angelwire,

I used the code you provided and somehow, it made no difference to the behavior of the parts... I even removed the "_instance_right.connected = 1;" and replaced it with "connected = 1;" and it functioned the same way... I am very confused and fear something larger is afoot that is gumming up the works. If anyone has any thoughts on what might be wrong, please post. Again, I am working in GMS1

Thanks
 
D

Darkwing

Guest
Update:

angelwire's code was correct. I found the problem on my end. There is a pretty big issue that was making it impossible for the script to work. I cant say what it was, because the issue also happens to be a major part of what makes my game unique. However, I can say that I figured out how to adapt angelwire's code to work within my system. Its a little bulky, but it seems to work fine.

I do have one further issue however, is there any way to provide position data to GML in polar coordinates instead of x and y? The code only works if the ship is facing directly up, down, left, or right, due to only checking x+32 etc... is there a way to say like "32 at direction + 90 degrees" or something like that?

Thanks again, this has all been very helpful and I credit angelwire with providing me the solution.
 

chamaeleon

Member
I do have one further issue however, is there any way to provide position data to GML in polar coordinates instead of x and y? The code only works if the ship is facing directly up, down, left, or right, due to only checking x+32 etc... is there a way to say like "32 at direction + 90 degrees" or something like that?
Just use trigonometric math plus a definition of what the origin point is.
 

Director_X

Member
I am not sure what you mean by invisable connectors, or how this would work. I am open to other options so if you could elaborate a bit I would be interested in your idea.

ANY object's x and y can be used to "jump to" with another object.

Using my image:
1616252983067.png

So your object obj_player has an x and y coordinate whereever it is on the screen at any given time.

Now I create an obj_square. And in its STEP event, I set its x = obj_player.x; and its y = obj_player.y; You can now see that it will automatically "move" wherever obj_player (assuming the origin is in the middle center) moves on the screen.

Obviously, you need to OFFSET the X value so it it on the RIGHT side of the player. so x = obj_player.x + 32; (for example).

Now that you have this obj_square automatically following the obj_player wherever it goes, you can simply turn obj_square to INVISIBLE in its properties. This now becomes an invisible connector.

You can now attach as many objects (via their own STEP events) to this connector using the above code: x = obj_square.x; and y = obj_square.y; (and any offsets you need to adjust for as needed).

Similarly you can add more "connectors" to create more connecting points to make large and elaborate multi-jointed bosses and objects. :)

This may not be the most efficient method, but it is a easy and simple method for newcomers to work with (and not have to learn trigonometry to achieve!).

Hope this helps. :)
 
Last edited:
D

Darkwing

Guest
ANY object's x and y can be used to "jump to" with another object.

Using my image:
View attachment 38951

So your object obj_player has an x and y coordinate whereever it is on the screen at any given time.

Now I create an obj_square. And in its STEP event, I set its x = obj_player.x; and its y = obj_player.y; You can now see that it will automatically "move" wherever obj_player (assuming the origin is in the middle center) moves on the screen.

Obviously, you need to OFFSET the X value so it it on the RIGHT side of the player. so x = obj_player.x + 32; (for example).

Now that you have this obj_square automatically following the obj_player wherever it goes, you can simply turn obj_square to INVISIBLE in its properties. This now becomes an invisible connector.

You can now attach as many objects (via their own STEP events) to this connector using the above code: x = obj_square.x; and y = obj_square.y; (and any offsets you need to adjust for as needed).

Similarly you can add more "connectors" to create more connecting points to make large and elaborate multi-jointed bosses and objects. :)

This may not be the most efficient method, but it is a easy and simple method for newcomers to work with (and not have to learn trigonometry to achieve!).

Hope this helps. :)
Thank you for elaborating, Director_X!

This is interesting, but will not work for my game. This issue has already been solved as well. I appreciate the response and maybe it will help me down the line with something else? Cheers!
 
Top