GML with Block Recipe Cards

FrostyCat

Redemption Seeker
with Block Recipe Cards

GM Version: GMS 2.x, GMS 1.x, GM Legacy
Target Platform: All
Download: N/A
Links: N/A
Summary: A quick reference for a range of common use cases for the with block.

Summary
The with block is GML's most powerful means for manipulating instances, but mainstream GML education tends to omit all but its most trivial use cases. This guide aims to document a number of common and useful patterns as a reference.

Definitions:
A property is an expression from the perspective of a called instance. Examples:
  • x
  • sprite_width*sprite_height
  • distance_to_object(other)
A condition is a property with a Boolean value (true or false). Examples:
  • visible
  • id != other.id
  • object_is_ancestor(object_index, other.object_index)
A calling instance is an instance executing a with block.
A called instance is an instance being targeted by some iteration of a with block.

Looping Patterns

Looping patterns use with blocks to allow the calling instance to apply statements to one or more called instances. This is the form most commonly taught in other resources.

Run statements for all instances (Basic form)
Code:
with (object_or_instance_or_all) {
  /* statements */
}

For all other instances except self (Not-me loop)
Code:
with (object_or_all) {
  if (id != other.id) {
    /* statements */
  }
}

For the first instance found that satisfies a condition
Code:
with (object_or_all) {
  if (condition) {
    /* statements */
    break;
  }
}
Note: If there are multiple instances satisfying the condition, which one would be selected is dependent on the program state and the runner's implementation. Avoid assuming any implicit ordering.

Alias an instance as other for a group of statements
Code:
with (instance_or_object) {
  with (other) {
    /* statements */
  }
}
Common functions to use as a source of instance IDs for this pattern include instance_place(), instance_position() and collision_*() functions.

For all instances that satisfy a condition (General form, with-if filter)
Code:
with (object_or_all) {
  if (condition) {
    /* statements */
  }
}

For all instances that satisfy any of the conditions (Set union / Whitelist)
Code:
with (object_or_all) {
  if (condition1 || condition2 || /* ... || */ conditionn) {
    /* statements */
  }
}
Tip: Arrange the conditions in order of descending likeliness to be true for optimal performance.

For all instances that don't satisfy a condition (Set subtraction / Blacklist)
Code:
with (object_or_all) {
  if (!condition) {
    /* statements */
  }
}

For all instances that satisfy all of the conditions (Set Intersection)
Code:
with (object_or_all) {
  if (condition1 && condition2 && /* ... && */ conditionn) {
    /* statements */
  }
}
Tip: Arrange the conditions in order of descending likeliness to be false for optimal performance.

Polling Patterns
Polling patterns use with blocks to help the calling instance make a decision about a group of called instances, terminating as soon as there is enough information to produce an output. These may be combined with looping patterns.

If all instances satisfy a condition
Code:
var all_ok;
all_ok = true;
with (object_or_all) {
  if (!condition) {
    all_ok = false;
    break;
  }
}
if (all_ok) {
  /* statements */
}

If at least one instance satisfies a condition
Code:
var some_ok;
some_ok = false;
with (object_or_all) {
  if (condition) {
    some_ok = true;
    break;
  }
}
if (some_ok) {
  /* statements */
}

Aggregating Patterns

Aggregating patterns use with blocks to produce an output that describes a group of called instances. These may be combined with looping patterns.

Count the number of instances satisfying a condition
Code:
var n_ok;
n_ok = 0;
with (object_or_all) {
  if (condition) {
    n_ok += 1;
  }
}
/* Use n_ok here */

Collect all instances that satisfy a condition
Code:
var i, insts_ok;
i = 0;
with (object_or_all) {
  if (condition) {
    insts_ok[i] = id;
    i += 1;
  }
}
/* Use insts_ok here */

Collect all instances with property sorted in ascending order
Code:
var i, lim, pq, insts_sorted;
pq = ds_priority_create();
with (object_or_all) {
  ds_priority_add(pq, id, property);
}
lim = ds_priority_size(pq);
for (i = 0; i < lim; i += 1) {
  insts_sorted[i] = ds_priority_delete_min(pq);
}
ds_priority_destroy(pq);
/* Use insts_sorted here */

Collect all instances with property sorted in descending order
Code:
var i, lim, pq, insts_sorted;
pq = ds_priority_create();
with (object_or_all) {
  ds_priority_add(pq, id, property);
}
lim = ds_priority_size(pq);
for (i = 0; i < lim; i += 1) {
  insts_sorted[i] = ds_priority_delete_max(pq);
}
ds_priority_destroy(pq);
/* Use insts_sorted here */

Find the instance with lowest property value
Code:
var val, lowest, lowest_val;
lowest = noone;
lowest_val = -1;
with (object_or_all) {
  val = property;
  if (lowest == noone || val < lowest_val) {
    lowest = id;
    lowest_val = val;
  }
}
if (lowest != noone) {
  /* Use lowest here */
}

Find the instance with highest property value
Code:
var val, highest, highest_val;
highest = noone;
highest_val = -1;
with (object_or_all) {
  val = property;
  if (highest == noone || val > highest_val) {
    highest = id;
    highest_val = val;
  }
}
if (highest != noone) {
  /* Use highest here */
}

Last update (2018-02-13): Changed initial value of highest_val and lowest_val in the highest/lowest property value pattern to discourage a common misinterpretation.
 
Last edited:
G

Guest

Guest
My initial reaction to this was, "meh--read the manual"--but this is my third time in two months dipping back into here for a sanity check. Thanks!
 

gnysek

Member
For the first instance
Rather "For one random instance". There's no "first", as instance order may change because of creating new ones, changing layers, or deactivating. It is usually the first one created, but not always.

Edit:
Find the instance with highest property value
In this example:

Code:
with (instance_create_depth(0, 0, 0, obj_smething)) { value = -4; }
with (instance_create_depth(0, 0, 0, obj_smething)) { value = -10; }
Code won't be executed, as -10 < -4, and noone == -4. Compiled code looks like this:

Code:
var val, highest, highest_val;
highest = -4;
highest_val = -4;
with (object_or_all) {
  val = property;
  if (highest == -4|| val > highest_val) {
   highest = id;
   highest_val = val;
  }
}
if (highest != -4) {
  /* Use highest here */
}
 
Last edited:

FrostyCat

Redemption Seeker
Rather "For one random instance". There's no "first", as instance order may change because of creating new ones, changing layers, or deactivating. It is usually the first one created, but not always.
I'll change the wording to "first found", that way I don't imply any specific order.

In this example:

Code:
with (instance_create_depth(0, 0, 0, obj_smething)) { value = -4; }
with (instance_create_depth(0, 0, 0, obj_smething)) { value = -10; }
Code won't be executed, as -10 < -4, and noone == -4. Compiled code looks like this:

Code:
var val, highest, highest_val;
highest = -4;
highest_val = -4;
with (object_or_all) {
  val = property;
  if (highest == -4|| val > highest_val) {
   highest = id;
   highest_val = val;
  }
}
if (highest != -4) {
  /* Use highest here */
}
I don't get what you mean by this, all you've done is expanding noone in my code to -4 without disproving anything.
  • If the -4 instance comes first, the first pass would accept it because highest is set to noone, then the second pass would ignore the -10 instance because highest is no longer noone and -10 is not higher than -4.
  • If the -10 instance comes first, the first pass would accept it because highest is set to noone, then the second pass would overwrite it with the -4 instance because -4 is higher than -10.
The code is working properly by selecting the one with a value of -4 instead of -10, regardless of which order the instances are evaluated. I think you're mixing up the roles of highest and highest_val.
 

gnysek

Member
If the -4 instance comes first, the first pass would accept it because highest is set to noone, then the second pass would ignore the -10 instance because highest is no longer noone and -10 is not higher than -4.
But, "noone" == -4 in Game Maker :)

Code:
self == -1;
other == -2;
all == -3;
noone == -4;
global == -5;
So, after game is compiled, all "noone" are replaced to -4.
Like:
Code:
var i = -1;
instance_exists(i); //returns true, cause instance -1 == "self"
 

FrostyCat

Redemption Seeker
But, "noone" == -4 in Game Maker :)

Code:
self == -1;
other == -2;
all == -3;
noone == -4;
global == -5;
So, after game is compiled, all "noone" are replaced to -4.
Like:
Code:
var i = -1;
instance_exists(i); //returns true, cause instance -1 == "self"
Once again, what are you trying to prove about my code other than noone being equal to -4, which I've known for more than a decade?
 

chamaeleon

Member
I think the only problem this particular code snippet has is that if the property of interest for all instances matched in the with statement is less than -4 (the value noone assigned to highest_val), none of them will evaluate "val > highest_val" as true, so highest will never be set to some instance id, and your if statement will not execute for one of the instances.

As these are sample codes, clearly nothing stops an intrepid game developer from changing highest_val = noone to highest_val = <some value less than anything my program will ever generate for this property>..
 

FrostyCat

Redemption Seeker
I think the only problem this particular code snippet has is that if the property of interest for all instances matched in the with statement is less than -4 (the value noone assigned to highest_val), none of them will evaluate "val > highest_val" as true, so highest will never be set to some instance id, and your if statement will not execute for one of the instances.

As these are sample codes, clearly nothing stops an intrepid game developer from changing highest_val = noone to highest_val = <some value less than anything my program will ever generate for this property>..
That won't happen because the first iteration of the with block would always take the ID and property value from the first instance encountered. That is what the first half of highest == noone || val > highest_val is supposed to enable. highest stores the ID of the best instance found thus far, while highest_val stores the property value --- I get the feeling that neither you nor gnysek is seeing this distinction.

This still looks more like improper conclusions drawn from bad tracing than an actual problem with the example.
 

Yal

šŸ§ *penguin noises*
GMC Elder
Code won't be executed, as -10 < -4, and noone == -4. Compiled code looks like this:
There's separate variables for the property value and the instance ID, you've interpreted the code as there being a single variable used for both.
 

chamaeleon

Member
That won't happen because the first iteration of the with block would always take the ID and property value from the first instance encountered. That is what the first half of highest == noone || val > highest_val is supposed to enable. highest stores the ID of the best instance found thus far, while highest_val stores the property value --- I get the feeling that neither you nor gnysek is seeing this distinction.

This still looks more like improper conclusions drawn from bad tracing than an actual problem with the example.
You're absolutely right. I was trying to hard to find a problem. I looked too much at the value management of highest_val, I forgot to look at the first part that will be true if no instance has been looked at yet. My conclusion is invalid.
 

gnysek

Member
OK, my fault, seems that indeed I didn't noticed the fact, that at start first OR is ALWAYS executed (I was thinking about next iterations), and changing noone to any other number < 100000 won't change anything in fact. Sorry about this then.
 
M

Morne

Guest
Wow, great examples or code snippets to test. Took me a while to learn multiple use cases of WITH()...never seen the last lowest and highest property value checks, still trying to understand how that will work. How do you compare a 'noone' instance_id with a "property" like 'sprite_width'? Or what I mean is why do you have to rewrite variables with same names for different uses, yes its less code but could be confusing. Why not rather be more verbose for example:
"Instance with highest priority" change to...
Code:
var val, highest, highest_val, highest_instance_id;
highest = noone;
highest_val = 4;
with (object_enemy) {
  val = sprite_width;
  if (highest == noone || val > highest_val) {
   highest_instance_id = id;
   highest_val = val;
  }
}
if (highest_instance_id != noone) {
  /* Use highest object specific instance id here */
}




Also, I feel this list needs to also mention the function call:
instance_find(obj,n)

And its explanation in Help file will help some of the WITH() explanations to track instance_id's...snippit as from help file below:
Code:
var i;
for (i = 0; i < instance_number(obj_Enemy); i += 1)
   {
   enemy[i] = instance_find(obj_Enemy,i);
   }
 

FrostyCat

Redemption Seeker
Took me a while to learn multiple use cases of WITH()...never seen the last lowest and highest property value checks, still trying to understand how that will work. How do you compare a 'noone' instance_id with a "property" like 'sprite_width'? Or what I mean is why do you have to rewrite variables with same names for different uses, yes its less code but could be confusing. Why not rather be more verbose for example:
"Instance with highest priority" change to...
Code:
var val, highest, highest_val, highest_instance_id;
highest = noone;
highest_val = 4;
with (object_enemy) {
  val = sprite_width;
  if (highest == noone || val > highest_val) {
   highest_instance_id = id;
   highest_val = val;
  }
}
if (highest_instance_id != noone) {
  /* Use highest object specific instance id here */
}
Like chameleon and gnysek, you too are mixing up the purpose of highest (holds noone or instance ID) and highest_val (holds property values after at least one instance is encountered) in my code. Nowhere in my code did I attempt to compare instance IDs or noone with a property value.

By the way, you didn't even test your code. There is an obvious problem with it that would cause it to always pick the last instance encountered. I'll give you a moment to read it over and recant your interpretation.

Also, I feel this list needs to also mention the function call:
instance_find(obj,n)

And its explanation in Help file will help some of the WITH() explanations to track instance_id's...snippit as from help file below:
Code:
var i;
for (i = 0; i < instance_number(obj_Enemy); i += 1)
   {
   enemy[i] = instance_find(obj_Enemy,i);
   }
If you've been following my posts, you should know that I absolutely despise instance_find() being used in this form. Coverage of this nonsense has no place coming out of my mouth, other than proof of how hard it sucks.
 
M

Morne

Guest
I did not test it, apologies. I took your code and re-wrote it to try and make more sense of how it would process, to start a dialog with you.

I do understand this:
Like chameleon and
gnysek, you too are mixing up the purpose of highest (holds noone or instance ID) and highest_val (holds property values after at least one instance is encountered) in my code. Nowhere in my code did I attempt to compare instance IDs or noone with a property value.
I do not like how you defined
highest_val = noone;
(which you define to hold a 'noone' and therefore I mistakenly assumed also for purpose of an 'instance ID'),
it would make more sense to be if you defined it as
highest_val = -1;

I like to keep 'noone' for when I plan to work with instance ID. That is just me.

BTW, I've no formal programming education, just self taught so I may have wrong assumptions...always willing to learn.

I wasn't aware of the slow quadratic processing cost of instance_find, and it helped me when I made an RTS and had to iterate through many instances and make lists of objUnitEnemy and objUnitFriendly... you provide a new solution using with().

Also I'm not sure if "highest_val = 4;" will always work unless calling instance is included in the called instance definition. So I will have to test how your code works if I use it in object_control and run it for object_enemy. Don't think it will work, without directly referring to object_control.highest_val. I also forgot to define
highest_instance_id = noone at top of script.

I don't have to recant, I'd like to discuss it rather and see how I can learn from you to match my own style. I'm not trying to offend you.
 

FrostyCat

Redemption Seeker
I do understand this:
I do not like how you defined
highest_val = noone;
(which you define to hold a 'noone' and therefore I mistakenly assumed also for purpose of an 'instance ID'),
it would make more sense to be if you defined it as
highest_val = -1;
Fine. Any value in that position would have worked, but if by changing it to -1 I'd stop getting misguided comments, so be it.

Also I'm not sure if "highest_val = 4;" will always work unless calling instance is included in the called instance definition. So I will have to test how your code works if I use it in object_control and run it for object_enemy. Don't think it will work, without directly referring to object_control.highest_val. I also forgot to define
highest_instance_id = noone at top of script.
It always works because it is in local scope (with var), not instance scope (no var). Why don't you read up on the implications of local variables inside a with block before saying it probably doesn't work.
The Manual entry on local variables said:
One other thing about var declared variables should be noted... As they are unique to the event that runs them, they can be used in any other instances through code too! This means that we can use these variables to set and change things in other instances using the "with()" construct (there is a section on this in the GML Overview section of the manual). The actual code itself would look something like this:

Code:
var num = instance_number(obj_Enemy);
with (obj_Enemy)
   {
   if num>10 instance_destroy();
   }
The above code works because the var declared variable is local to the event (or script) it is contained in, not the instance, nor the game world, and so can be used in any function in any object as long as it is in the same code block.
 
Top