GML [SOLVED] Get ID of child objects

Hello, everyone! This would probably be obvious for some of you, but I'm having troubles.
I have a dialogue system that uses the collision of the player object with a parent object that have all characters as child objects in order to take different text from each.

GML:
var inst = collision_rectangle(...)
My problem comes with the need of starting dialogues when the player object doesn't exist in the room. I already thought of using the persistent "game object", but I have no idea how could I get the ID of the child objects without using the collision function. Right now I'm using a ridiculous collision_rectangle that checks a huge area in order to make the system work, and it does work well, but I'm sure there's a better way to do it.
 

NightFrost

Member
You're not describing how your dialogue system runs or initiates conversations. However let me explain how the system I wrote works and how I solved this in it.

I have a central obj_dialogue controller object which is in charge of displaying dialogue boxes and branching choices. It is coded as state machine that sits in idle state until a dialogue starter kicks it into action. When player moves next to an NPC and presses action key or button, player code runs a collision check that notices there's an NPC player wants to interact with, and runs dialogue starter script. It sets the dialogue controller's state to dialogue initialization, grabs the NPC's unique identifier which is an enumerator value, as well as its x and y position for aligning the dialogue box. (The dialogue controller then checks if there are any valid dialogue branches available and blah blah that's besides the point here.)

When I need to start a dialogue without player intervention, which pretty much is just for cutscenes, the cutscene script contains the NPC enumerator value and an identifier of the specific dialogue branch that needs to be run. The cutscene controller loops through all NPC instances in the room until it finds the one with correct enumerator, takes its x and y, and calls the same dialogue starter script as player object does, which then sends the dialogue controller the NPC enumerator, x and y, and additionally forces the selection of dialogue branch to what the cutscene dictated.

So what you need to do is to uniquely identify all NPCs so you can find the correct one in the room. I use enumerator but you can use whatever. When your code needs to start dialogue without player, you loop through the NPCs and pick the one with correct identifier. Now you have their instance ID, so you can do whatever is next necessary. If it is the NPC instance that is in charge of displaying dialogue, you switch it to correct state or whatever to make it talk.

If your NPCs are placed in room editor, you can right-click them and add the identifier to their startup code there; if you create them through instance_create_layer you assign the identifier then as you always know which NPC you are instantiating.
 
Thanks for the reply. I didn't get in the details of the dialogue system just because all I'm looking for is an alternative to collision to get the child objects IDs. Each of this object is a container of text, divided in pages, that specify the character who is talking and eventually call scripts. This way I don't need NPC instances to actually exist in the room in order to make the dialogues work.
 

zendraw

Member
i dont get what are you trying to do? you want to get child ids but then when player object is not there? you need to write your system if you want help, otherwise look the manual which collision functions return ids.
i dont see why are you using a big collision rectangle atall...
 

NightFrost

Member
This way I don't need NPC instances to actually exist in the room in order to make the dialogues work.
If you want to read values from child objects, an instance of that object must exist in the room. And to find that instance, you either do that huge collision rectangle, or loop through all instances until you find the correct one (identidied for example with an enumerator value as I showed.) If you want to access dialogue without NPC instances in the room, the data must still be available in somewhere in the scope of the room, like a central repository. All my dialogue is stored in obj_dialogue arrays which is made persistent, making it always available. Although all my use cases have been with an NPC instance in room, it could work without (for example to implement narrator dialogue) since in context of obj_dialogue, all NPCs are just enumerator values which point it to the correct array substructure.
 

curato

Member
I am not sure why collision would be integral to your dialog structure, but if the idea is to bump into a character and have a chat then just have the collision event of the character call a script with it name like if you have dialog for a character named Joe then pass in joe to get the correct dialog then just use some global variables to store the dialog progress/story progress. No inheritance is really required to make a basic dialog system work.
 

Hyomoto

Member
Well, I think the simple answer is that you need a different way to start dialogue. Namely, you need to wrap your result up in some sort of trigger, ie:
GML:
start_dialogue( id );
Then, if you need to start a dialogue from somewhere else, you can do so by calling that object. As for how to get the id of something, well, there are a lot of options, but GMS2 provides a constant for every object that is in the room when it is created, it's usually something like "inst_64FAS99" and you can use that,
GML:
start_dialogue( inst_64FAS99 );
Now, this does not make a case for the merits of this system or any potential pitfalls (ie: hard-linking can be problematic while the game is in development), so a "soft" solution would be to give every object a flag, like a name, you can check for. You can use the with() statement to iterate over a type of object (including child objects). For example, you can use with() to iterate over the objects and look for your flag, if found use that to start your dialogue:
GML:
with ( oPerson ) {
  if ( objectName == "start_dialogue" ) {
    start_dialogue( id );

    break;

  }

}
This is a "safer" method because if there is no object with the proper flag, it will just be ignored altogether (though may could lead to troubleshooting why it isn't firing). However, it is simple because it will check all of the given objects, and allows you to easily add or change starting dialogues since you just need to include this flag on an object to make it fire. However, this also relies on ensuring EVERY object of type oPerson, in the above example, has this variable defined or it will crash. This is generally as easy as defining a variable as part of the object, GMS2 Object Variables even allow you to edit them directly in the room editor, but if you aren't used to this kind of programming pattern you might find it awkward at first.

All of that said, it's probably best to not couple your dialogue system directly to the objects themselves. Rather than the above example, perhaps using the same variable method you instead have the objects call something like:
GML:
if ( player_activates_me ) {
  start_dialogue( myDialogue );

}
What "myDialogue" is will be up to you to decide, but it allows you to get rid of the previous pattern and start dialogue by simply calling something like:
GML:
start_dialogue( DIALOGUE.ROOM_1_START );
Then, your object-initiated dialogue and your room start dialogue is handled in the same way, but don't require intermediate objects to function. After all, the purpose of the NPC or sign in this case is to start a dialogue when the player interacts with it. That isn't the function of the dialogue system: the dialogue system should just handle the opening, closing and user input for the boxes themselves. I imagine you are doing it this way because you are probably attaching some dialogue information to the object itself, but It might be advantageous to store that information somewhere, and just allow it to be looked up. As an example we might have something like:
GML:
// when your game starts, you create some dialogues
dialogueLibrary = ds_map_create();

dialogue_create( "cindy.hello.1", spr_cindy, 0, "Cindy", "Hello!" );
GML:
/// @func dialogue_create( id, portrait_sprite, portrait_index, name, lines... )
/// @params portrait_sprite,portrait_index,name
var _new_dialogue = ds_map_create();

ds_map_add( _new_dialogue, "sprite", argument1 );
ds_map_add( _new_dialogue, "index", argument2 );
ds_map_add( _new_dialogue, "name", argument3 );
ds_map_add( _new_dialogue, "text", argument4 );

ds_map_add( dialogueLibrary, argument0, _new_dialogue );
In this way you create dialogue to be stored in this map somewhere, and you can get at it by providing that "id", in this case "cindy.hello.1"
GML:
start_dialogue( "cindy.hello.1" );
GML:
/// @func start_dialogue( key )
/// @params key
with ( instance_create_layer( 24, 24, "Speech", oDialogueBox ) {
  var _dialogue = Game.dialogueLibrary[? argument0 ];

  // the given key didn't exist in the library
  if ( is_undefined( _dialogue ) ) {
    // provide some generic text or an error message

  } else {
    sprite_index = _dialogue[? "sprite" ];
    image_index = _dialogue[? "index" ];
    speakerName = _dialogue[? "name" ];
    dialogueText = _dialogue[? "text" ];

  }
// return the id of the new dialogue box if successful so you can manipulate it from the calling code if needed
  return id;
}
Some food for thought. I've left out a few things, this is hardly copy-paste, but should illustrate how you can store your text somewhere else and then get at it to create your boxes using a key, instead of relying on an object in your room to hold that information. As a nice little bonus, this behavior also makes it a little easier to proofread your text because you don't have to go looking through different objects to find it, you'll have some library of text you can more easily peruse.
 
Last edited:

GMWolf

aka fel666
Maybe I'm not understanding this right. But seems to me like you are looking for instance_count and instance_find.

Code:
for(var i = 0; i < instance_count(obj_thing); i++)
{
  var inst = instance_find(obj_thing, i);
   //Do something with inst
}
This should loop over all child instances of obj_thing in the room.
 
Code:
for(var i = 0; i < instance_count(obj_thing); i++)
{
  var inst = instance_find(obj_thing, i);
   //Do something with inst
}
That's exactly what I was searching for! Thank you, and thanks to everyone who shared their opinions and suggestions here!
 

Hyomoto

Member
That's exactly what I was searching for! Thank you, and thanks to everyone who shared their opinions and suggestions here!
It hate to point this out, but that pattern is a more convoluted:
GML:
with ( obj_thing ) {
  //Do something with inst
}
If your only requirement is to iterate over a specific type of object, then with() is a better, less complex choice. Many people don't look at it as such, but with is also a loop. Loops in GML are generally very slow, at least comparatively, to the internal loops that the VM does. So, with also ends up being faster because it relies on an "internal" loop rather than a GML-powered one. In short, that for pattern is a strange recommendation considering it has a few negative edge case interactions (try deleting an instance) and relies on a lot of more expensive operations to do the same thing.

On the other hand, it doesn't change the variable scope, but whether that is desirable or ultimately important will be case specific.
 
Last edited:
Top