GML NPC's with Jobs

E

Emberex

Guest
I'll explain this the best I can.

I have "humans" to spawn around a main hall and walk around.

If a "lumberjack" building is in existence, they will turn into a new object called the lumberjack. Then walk to the building and stay around it.

My issue is that when I place multiple buildings, they all walk toward the same one. I have tested and found that the buildings are separately becoming occupied, but they still walk toward the first one.

How can I make them walk to the one that they occupied?

(The code that turns them into the lumberjack)
if obj_human_guild.active_lumberjacks < instance_number(obj_build_lumberjack) {
obj_build_lumberjack.vacant = false;
obj_human_guild.active_lumberjacks++;
instance_change(obj_lumberjack, true);
}
(The code that tells whether the build is occupied)
vacant = true;
lumberid = instance_number(obj_build_lumberjack);

let me know if you need something extra
 

Nidoking

Member
obj_build_lumberjack.vacant
This is an object ID with dot notation. You've been told not to do this. Don't do this. Get an instance ID and get or set the variable in that instance. In this case, you probably want to store an instance of obj_build_lumberjack in the obj_lumberjack instance and use that as its target.
 

flyinian

Member
I'll explain this the best I can.

I have "humans" to spawn around a main hall and walk around.

If a "lumberjack" building is in existence, they will turn into a new object called the lumberjack. Then walk to the building and stay around it.

My issue is that when I place multiple buildings, they all walk toward the same one. I have tested and found that the buildings are separately becoming occupied, but they still walk toward the first one.

How can I make them walk to the one that they occupied?

(The code that turns them into the lumberjack)
if obj_human_guild.active_lumberjacks < instance_number(obj_build_lumberjack) {
obj_build_lumberjack.vacant = false;
obj_human_guild.active_lumberjacks++;
instance_change(obj_lumberjack, true);
}
(The code that tells whether the build is occupied)
vacant = true;
lumberid = instance_number(obj_build_lumberjack);

let me know if you need something extra
How about the code that tells the lumberjack what building to walk to?
 

TailBit

Member
obj_build_lumberjack.vacant = false; this changes vacant for all obj_build_lumberjack into false, not just one:

You need to target a single instance:
GML:
home = noone;
// tell all of them
with(obj_build_lumberjack){
    // .. to check if vacant
    if(!vacant){
        // if so set it as the home for this one and stop looking through the rest
        other.home = id;
        break;
    }
}

if home != noone{ // if a home was found
    home.vacant = false;
}
home will now hold the id of the building it should be at, or the special value noone (-4)
 

Yal

🐧 *penguin noises*
GMC Elder
You can use a script like this to get a random vacant building's id.
GML:
///get_random_lumberbuilding()
var myl = ds_list_create(), ret;
with(obj_lumberbuilding){
  if(vacant){
     ds_list_add(myl,id)
  }
}
if(ds_list_size(myl) > 0){
  ds_list_shuffle(myl)
  ret = myl[| 0]
}
else{
  ret = noone
}
ds_list_destroy(myl)
return ret;
 
E

Emberex

Guest
obj_build_lumberjack.vacant = false; this changes vacant for all obj_build_lumberjack into false, not just one:

You need to target a single instance:
GML:
home = noone;
// tell all of them
with(obj_build_lumberjack){
    // .. to check if vacant
    if(!vacant){
        // if so set it as the home for this one and stop looking through the rest
        other.home = id;
        break;
    }
}

if home != noone{ // if a home was found
    home.vacant = false;
}
home will now hold the id of the building it should be at, or the special value noone (-4)
What exactly is the other.home = id; doing?
 
E

Emberex

Guest
How about the code that tells the lumberjack what building to walk to?
if (point_distance(x,y, obj_build_lumberjack.x, obj_build_lumberjack.y)) < 10{
direction = irandom_range(0,359);
image_angle = direction;
speed = 0.7;
alarm[1] = 5 * room_speed;
} else {
move_towards_point(obj_build_lumberjack.x, obj_build_lumberjack.y, 0.7);
image_angle = direction;
alarm[1] = 5 * room_speed;
}


This tells the lumberjack to walk around the building randomly, but if distance away is to far, to go back to the building.
 

Nidoking

Member
obj_build_lumberjack.x
But THIS tells the game to pick a random obj_build_lumberjack instead of using the one you want. If you use the name of an object, like obj_build_lumberjack, with a dot and a variable, you're almost certainly doing something wrong.
 

kburkhart84

Firehammer Games
obj_build_lumberjack.vacant = false; this changes vacant for all obj_build_lumberjack into false, not just one:
This is incorrect actually. It WILL change just one instance of the object's variable....however, you don't know which instance that will be. If there is literally only one instance, then that's it, but it theoretically will be the first created instance(I say theoretically because it isn't documented and shouldn't be counted on). If you DID want to set a variable on all instances of some object, a with() statement with the object name would actually go through ALL instances and run that code.

@Emberex As mentioned by @Nidoking , you REALLY need to get better aqcuainted with the differences between objects and instances, and learn the proper time for each when using variables. It is going to wreck havok all over your projects until you do...but once you do, suddenly everything will make more sense and be easier to get done.
 

Nidoking

Member
This is incorrect actually. It WILL change just one instance of the object's variable....however, you don't know which instance that will be.
No, the original explanation is the correct one. While I think it's a bad design, using dot notation on an object in an assignment will actually assign that value to all instances of that object. In every other situation, such as the function parameter I quoted, it chooses one at random.
 

TailBit

Member
Well, only the human have the variable home, so if I tried to use it in another object then it just define a new one there, so:
Code:
home = noone;
with(obj_b) other.home = id;
if obj_a tells obj_b to do somethin using with
then obj_b can refer back to that spesific obj_a using other.
you can bypass this by making a variable that exist til the end of the code using var
Code:
var a = noone;
with(obj_b) a = id;
home = a;
EDIT: Oh, and id is the unique identifier for each instance, if you store it in variable then you can tell it to go to that variable's x and y move_towards_point(home.x,home.y,3)

But is a example never the less..

if both player and enemy have hp and attack, and when you collide with enemy then
they should deal the damage to eachother in the player collision event, then set
an alarm in player to disable it for 1 second:

Code:
if alarm[0]<0 { // if the alarm is off = -1

    // in a collision event then other refers to the spesific enemy in this case
    other.hp -= attack; // deal dmg to enemy
    if(other.hp <= 0) with(other)instance_destroy(); // check if it is dead

    hp -= other.attack; // and the same with player
    if(hp <= 0) instance_destroy();

    alarm[0] = room_speed; // set alarm to 1 second
}
now .. let me write the same thing another way:
Code:
if alarm[0]<0 { // if the alarm is off = -1
  
    // so here is a confusing bit, player is running the code, but uses "with" to tell
    // the spesific "other enemy" to do some code .. for this enemy, that player
    // will now become other, so it can run the exact same code as you use further below
    with(other){
        hp -= other.attack; // get attack from player
        if(hp <= 0) instance_destroy();
    }
  
    hp -= other.attack; // get attack from enemy
    if(hp <= 0) instance_destroy();

    alarm[0] = room_speed; // set alarm to 1 second
}
 
Last edited:

kburkhart84

Firehammer Games
No, the original explanation is the correct one. While I think it's a bad design, using dot notation on an object in an assignment will actually assign that value to all instances of that object. In every other situation, such as the function parameter I quoted, it chooses one at random.
It seems that as of 2.2.5, we are both right. Either something changed since last I had checked, or I just remembered it incorrectly, but it is dependent on the platform what happens.

obj_ball.speed = 0;

With the above code you are setting the speed of an instance of "obj_ball". However if you have more than one instance of the given object in the room, then it will apply to ALL of them equally - unless you are using one of the JStargets or HTML5, in which case it will affect only one, but you have no way of knowing which one it will affect - so if you need to access all instances of an object, you should be using with, as that is 100% cross platform compatible. In general, this format should only be used when you have a single instance of the object in the room, or (as you will see in the next part) when you have a specific instance ID.
However...we BOTH agree that it is generally bad practice...although I don't think it is an issue if you know what you are doing, and you only have one of that object around. Otherwise, you are better off using the with() statement is this is the correct way and should work equally on all platforms.
 
E

Emberex

Guest
This is incorrect actually. It WILL change just one instance of the object's variable....however, you don't know which instance that will be. If there is literally only one instance, then that's it, but it theoretically will be the first created instance(I say theoretically because it isn't documented and shouldn't be counted on). If you DID want to set a variable on all instances of some object, a with() statement with the object name would actually go through ALL instances and run that code.

@Emberex As mentioned by @Nidoking , you REALLY need to get better aqcuainted with the differences between objects and instances, and learn the proper time for each when using variables. It is going to wreck havok all over your projects until you do...but once you do, suddenly everything will make more sense and be easier to get done.
That is exactly what I need, because I have yet to learn how to make one instance do what I want instead of them all. Any websites or videos that you could refer me to?
 
E

Emberex

Guest
Well, only the human have the variable home, so if I tried to use it in another object then it just define a new one there, so:
Code:
home = noone;
with(obj_b) other.home = id;
if obj_a tells obj_b to do somethin using with
then obj_b can refer back to that spesific obj_a using other.
you can bypass this by making a variable that exist til the end of the code using var
Code:
var a = noone;
with(obj_b) a = id;
home = a;
EDIT: Oh, and id is the unique identifier for each instance, if you store it in variable then you can tell it to go to that variable's x and y move_towards_point(home.x,home.y,3)

But is a example never the less..

if both player and enemy have hp and attack, and when you collide with enemy then
they should deal the damage to eachother in the player collision event, then set
an alarm in player to disable it for 1 second:

Code:
if alarm[0]<0 { // if the alarm is off = -1

    // in a collision event then other refers to the spesific enemy in this case
    other.hp -= attack; // deal dmg to enemy
    if(other.hp <= 0) with(other)instance_destroy(); // check if it is dead

    hp -= other.attack; // and the same with player
    if(hp <= 0) instance_destroy();

    alarm[0] = room_speed; // set alarm to 1 second
}
now .. let me write the same thing another way:
Code:
if alarm[0]<0 { // if the alarm is off = -1
 
    // so here is a confusing bit, player is running the code, but uses "with" to tell
    // the spesific "other enemy" to do some code .. for this enemy, that player
    // will now become other, so it can run the exact same code as you use further below
    with(other){
        hp -= other.attack; // get attack from player
        if(hp <= 0) instance_destroy();
    }
 
    hp -= other.attack; // get attack from enemy
    if(hp <= 0) instance_destroy();

    alarm[0] = room_speed; // set alarm to 1 second
}
Okay, so I can set a specific variable for the instance, I get that now, but, in this case I have where the "Main Hall" spawns the instances. and I plan to up the limit when the player progress'. How can I set a unique var id for each one?
 

kburkhart84

Firehammer Games
You will have to search up a few yourself. The manual explains it as well. I found this youtube playlist that may help as well. There are actually tons of resources out there if you just look for them.

Okay, so I can set a specific variable for the instance, I get that now, but, in this case I have where the "Main Hall" spawns the instances. and I plan to up the limit when the player progress'. How can I set a unique var id for each one?
It depends. You have various spawners in the level or what? If you only have one active at a time, you could put in an array for for each player level, the value you need. Then upon progression, change that instance variable accordingly. The following idea assume you only have one of these "main hall" objects.
Code:
//Main Hall Object Creation
values[0] = 1;
values[1] = 2;
values[2] = 4;
values[3] = 10;
.......
currValue = values[0];
Code:
//script you call when the player levels up
with(objMainHallSpawner)
{
    currValue = values[global.playerLevel];
}
Then, whenever the player levels up, call that script. This does assume you put the player level in that global variable, and change it before calling the script. Note that your main hall spawner would also need to use the currValue variable I put in there to decide how much to spawn...and my numbers are just random and would need to be set to whatever makes sense for your game.
 
E

Emberex

Guest
Okay, so here is my code for the human. This tells them to change into the lumberjack if there is an available instance and if vacant is equal to true. I finally have it where they check the specific id.

if obj_human_guild.active_lumberjacks < instance_number(obj_build_lumberjack) && (obj_build_lumberjack.id.vacant == true){
obj_build_lumberjack.id.vacant = false;
obj_human_guild.active_lumberjacks++;
instance_change(obj_lumberjack, true);
}

Problem now, 1 human will turn into a lumberjack and walk to the instance, I have a draw event showing me if the build is vacant or not. The first build will become not vacant, but the rest will not change because they see the build as not vacant. How do I get them to check for another instance with the vacant == true if they see the first as false?
 

FrostyCat

Redemption Seeker
No, you still haven't been checking a specific ID. You just pulled an instance ID off one arbitrary instance. No different than what you've done before.

You need to read my 2 articles on targeting specific instances:
On top of that, you should learn how to use the with statement in general.

DO NOT write back anymore complaining about code where there is still an object ID followed by a dot and a variable name. It's natural for them to not work. You've been told not to do that half a dozen times now.
 
E

Emberex

Guest
No, you still haven't been checking a specific ID. You just pulled an instance ID off one arbitrary instance. No different than what you've done before.

You need to read my 2 articles on targeting specific instances:
On top of that, you should learn how to use the with statement in general.

DO NOT write back anymore complaining about code where there is still an object ID followed by a dot and a variable name. It's natural for them to not work. You've been told not to do that half a dozen times now.
I have read this post, you put a lot of DO NOTS but I can not find where you actually tell how to find specific instances.

I have random "humans" being created. I have it set where I can click to place "Lumberjack Homes". I can get them to change into lumberjacks and walk toward a build, but they will all walk toward the same build. I haven't figured out how to make them search for another if they see the other is occupied.
 

kburkhart84

Firehammer Games
Each object instance knows its own id...you don't need to do anything special there. What you need to do instead of have a controller object. That controller upon creation(or every few steps if the "guilds" can change, would grab the instance IDs for all instances of those guild objects. Then, you can access that list of instances to know what is available. An easy way to get IDs is to have each one, as it creates itself, send its id to the controller object. You don't technically have to have a controller object, you can use global variables if you want, but I like controller objects because there is always more stuff going on and often things need done each step, etc... Finally, just have your humans check the instances you stored in the list one by one if there is a vacancy, and then if not, move to the next one in the list. Finally, you can have them go into some idle animation if there is no vacancy in any of them, and have them check again later.

I recommend you follow some basic tutorials for some basic games. They don't have to be like the one you are making either. The idea is to get some basic knowledge going, which you can then apply to what you are doing.

I should also mention, if you read those things @FrostyCat has provided, you will see different ways to actually get instance ids. Which ones apply to you will vary of course, but the knowledge is there.
 

FrostyCat

Redemption Seeker
I have read this post, you put a lot of DO NOTS but I can not find where you actually tell how to find specific instances.

I have random "humans" being created. I have it set where I can click to place "Lumberjack Homes". I can get them to change into lumberjacks and walk toward a build, but they will all walk toward the same build. I haven't figured out how to make them search for another if they see the other is occupied.
I have told you where in the article to look (the section under "Finding the right instance"). Have you read that?
Finding the right instance

As long as multiple instances of an object exists, referencing with an object ID alone will cease to work, so you must learn to find the one instance ID you want to work with. Here are some common situations.

An instance's own variables: Type out the variable name as-is, without any dot-prefixes. DO NOT use self. DO NOT use the object ID.

Collision events (the ones from the event selector, NOT collision checks in the Step event): other refers to the colliding instance in this event only. IMPORTANT NOTE: other is always equal to -2 and will lose context outside the event. If you need the actual instance ID later, you need to use other.id instead.

Collision-checking functions: instance_place() and instance_position() are the instance-ID-oriented analogues of place_meeting() and position_meeting(). Functions that start with collision_ but don't end in _list all return instance IDs. Save that instance ID into a variable (e.g. var inst_enemy = instance_place(x, y, obj_enemy);), then use that as the subject to work with (e.g. inst_enemy.hp -= 10;). Note that these functions return noone upon not finding a collision. Always account for this special case whenever you handle collisions this way.

Created or copied instance. instance_create() (GMS 1.x and legacy) / instance_create_layer() and instance_create_depth() (GMS 2.x) and instance_copy() return the ID of the created or copied instance. NEVER use instance_nearest() to establish this relationship --- something else could be closer by.

Subsidiary instance(s). Same as created or copied instance. If the subsidiary instance needs to reference its creator, the creator should assign its instance ID to an instance variable in the subsidiary instance. Conversely, if the creator needs to reference its subsidiary (or subsidiaries), it must store the return value of instance_create() (GMS 1.x and legacy) / instance_create_layer() or instance_create_depth() (GMS 2.x) immediately. NEVER use instance_nearest() to establish or maintain this relationship --- being the closest does NOT imply being the most relevant, especially in close quarters.

If in doubt about whether a function will help you find the right instance, use the Index tab in the Manual to look for its entry. It will tell you whether it returns an instance ID or something else.
In particular, pay attention to this line:
Save that instance ID into a variable (e.g. var inst_enemy = instance_place(x, y, obj_enemy);), then use that as the subject to work with (e.g. inst_enemy.hp -= 10;).
The point here is to stop targeting just obj_build_lumberjack when there are multiple instances of it in the room, and instead target an expression that returns a specific instance ID. What you're doing is like telling me to "shake hand with human" in a room full of people. You can't blame me for shaking hands with the "wrong" person, you failed to be specific this whole time.

If you want to go to the nearest instance of obj_build_lumberjack, you target instance_nearest(obj_build_lumberjack). If you want to go to the one you clicked on, you target instance_position(mouse_x, mouse_y, obj_build_lumberjack). If you want to go to the nearest one that has vacant still set to true, you use the with block pattern for "find the instance with the lowest property value" to find the one with the lowest distance, store the found instance ID in a variable (which the example does in lowest), and target that.
 

woods

Member
"shake hand with human"
best analogy ive seen yet ;o)

with
(which human you want me to shake hands with)


it is difficult for some of us that get muddled up and lost in the weave so easily. there is just soo many options available to create interaction with.
thank you @FrostyCat for this simple and straightforward explanation.
and to everyone else here that is so helpful

ive learned alot just lurking on these boards
sorry if this is considered spam on this thread..
just thought id drop a props and thanks where its needed ;o)
 
E

Emberex

Guest
"shake hand with human"
best analogy ive seen yet ;o)

with
(which human you want me to shake hands with)


it is difficult for some of us that get muddled up and lost in the weave so easily. there is just soo many options available to create interaction with.
thank you @FrostyCat for this simple and straightforward explanation.
and to everyone else here that is so helpful

ive learned alot just lurking on these boards
sorry if this is considered spam on this thread..
just thought id drop a props and thanks where its needed ;o)
Honestly it makes me feel better about my post being here and not wasting space, Thank you for your non-spam post! :D
 
E

Emberex

Guest
I have told you where in the article to look (the section under "Finding the right instance"). Have you read that?

In particular, pay attention to this line:

The point here is to stop targeting just obj_build_lumberjack when there are multiple instances of it in the room, and instead target an expression that returns a specific instance ID. What you're doing is like telling me to "shake hand with human" in a room full of people. You can't blame me for shaking hands with the "wrong" person, you failed to be specific this whole time.

If you want to go to the nearest instance of obj_build_lumberjack, you target instance_nearest(obj_build_lumberjack). If you want to go to the one you clicked on, you target instance_position(mouse_x, mouse_y, obj_build_lumberjack). If you want to go to the nearest one that has vacant still set to true, you use the with block pattern for "find the instance with the lowest property value" to find the one with the lowest distance, store the found instance ID in a variable (which the example does in lowest), and target that.
Okay, I feel like I get everything you're telling me. I also believe I see the difference in objects and instances. Still one thing, even if I say: var lumb = instance_create_layer(x,y,"Instances", obj_lumberjack.

That seems to still place the same variable with them all. How do I make everyone of them spawn with a different variable and call the new ones?
 

FrostyCat

Redemption Seeker
Okay, I feel like I get everything you're telling me. I also believe I see the difference in objects and instances. Still one thing, even if I say: var lumb = instance_create_layer(x,y,"Instances", obj_lumberjack.

That seems to still place the same variable with them all. How do I make everyone of them spawn with a different variable and call the new ones?
You need to then use that stored instance ID to assign the variables. For example:
GML:
var lumb = instance_create_layer(x, y, "Instances", obj_lumberjack);
lumb.variable = irandom(100);
You should also remember that values assigned this way are NOT visible in the object's Create event, only in later events. Create events happen at the time of the call to instance_create_layer() or instance_create_depth().
 
E

Emberex

Guest
You need to then use that stored instance ID to assign the variables. For example:
GML:
var lumb = instance_create_layer(x, y, "Instances", obj_lumberjack);
lumb.variable = irandom(100);
You should also remember that values assigned this way are NOT visible in the object's Create event, only in later events. Create events happen at the time of the call to instance_create_layer() or instance_create_depth().
Thank you, I will work with this and ask if I hit another wall.
 
Top