1. Hey! Guest! The 36th GMC Jam will take place between February 27th, 12:00 UTC - March 2nd, 12:00 UTC. Why not join in! Click here to find out more!
    Dismiss Notice
  2. NOTICE: We will be applying a Xenforo update on Tuesday 25th of February. This means that from approximately 10:00 to 14:00 BST the forums will be offline (or possibly longer). Sorry for the inconvenience! Official Announcement here.

GML with Block Recipe Cards

Discussion in 'Tutorials' started by FrostyCat, Apr 24, 2017.

  1. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,878
    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: Feb 13, 2018
    chirpy, Amon, Dan1 and 30 others like this.
  2. Guest

    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!
     
    Bentley likes this.
  3. gnysek

    gnysek Member

    Joined:
    Jun 20, 2016
    Posts:
    1,385
    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:
    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: Jul 12, 2017
  4. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,878
    I'll change the wording to "first found", that way I don't imply any specific order.

    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.
     
  5. gnysek

    gnysek Member

    Joined:
    Jun 20, 2016
    Posts:
    1,385
    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"
     
  6. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,878
    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?
     
  7. chamaeleon

    chamaeleon Member

    Joined:
    Jun 21, 2016
    Posts:
    1,107
    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>..
     
  8. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,878
    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.
     
    Bentley likes this.
  9. Yal

    Yal GMC Memer GMC Elder

    Joined:
    Jun 20, 2016
    Posts:
    4,214
    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.
     
  10. chamaeleon

    chamaeleon Member

    Joined:
    Jun 21, 2016
    Posts:
    1,107
    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.
     
  11. gnysek

    gnysek Member

    Joined:
    Jun 20, 2016
    Posts:
    1,385
    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.
     
  12. Bentley

    Bentley Member

    Joined:
    Jun 18, 2017
    Posts:
    831
    Extremely helpful.
     
  13. Morne

    Morne Member

    Joined:
    Jun 20, 2016
    Posts:
    36
    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);
       }
    
     
  14. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,878
    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.

    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.
     
  15. Morne

    Morne Member

    Joined:
    Jun 20, 2016
    Posts:
    36
    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:
    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.
     
  16. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,878
    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.

    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.
     

Share This Page