with

As indicated in the section Addressing Variables in Other Instances, it is possible to read and change the value of variables in instances and structs other than the one currently executing any given code. However, in a number of cases you want to do a lot more than just change a single variable within those other instances, and may want to perform more complex code actions that require multiple functions and lines of code. For example, imagine that you want to move all the instances of a ball object in your game 8 pixels down. You may think that this is achieved simply by the following piece of code:

obj_ball.y = obj_ball.y + 8;

But this is not correct, as the right-hand side of the assignment gets the value of the y-coordinate of the first ball and adds 8 to it. Next this new value is set as the y-coordinate of all balls, so the result is that all balls get the same y-coordinate, and even if you use the following:

obj_ball.y += 8;

it will have exactly the same effect because it is simply an abbreviation of the first statement. So how do we achieve something like this? This is why the with statement exists in GML.

Syntax

The with statement takes the form:

with (<expression>)
{
    <statement>;
    <statement>;
    ...
}

The expression can take a number of different inputs:

This will then change the scope of the code within the curly brackets { } from the instance, struct or function that executes the with to the instance (or instances or struct) given in the expression.

Once the expression has set the scope of the with, the statement will then be executed for each of the indicated instances or structs, as if it is the current (i.e. self). So, returning to our original problem, to move all instances of the ball object 8 pixels down you would type:

with (obj_ball)
{
    y += 8;
}

If you want to execute multiple statements, just include them in the curly brackets, the same as you would around any other code block. So for example, to move all the balls in our example to a random position and give them a random speed and direction, you would use:

with (obj_ball)
{
    x = random(room_width);
    y = random(room_height);
    speed = 1 + random(2);
    direction = random(360);
}

 

NOTE Instances in a room are created in a certain order, and their Create events are also executed as they are created one-by-one. This means that you must be careful when reading variables from other instances in the Create event, as that other instance may not have run its Create event yet!

For example: let's say ObjectA is created before ObjectB, and you have the following code in those objects' Create events:

ObjectA Create - myValue = objectB.myValue;
ObjectB Create - myValue = 10;

ObjectA is created first and its Create event runs, which then crashes the game:

"Variable objectB.myValue(100003, -2147483648) not set before reading it."

That's simply because ObjectB has not even been created yet, so any variables initialised in its Create event do not yet exist. This is why you must take caution when referencing other instances like this in the Create event, including any code run inside with() blocks.

 

With as a Loop

The with statement essentially performs a loop. Depending on the result of the expression, the statements inside the curly brackets { } will either be not executed at all, executed a single time or multiple times: 

Because with behaves as a loop, you can use the special break and continue statements in it as well. Using break will immediately exit the with code block and move on to any code that is in the event or function after the with should have finished, e.g.:

var _count = 0;
with (obj_enemy)
{
    if (++_count > 10)
    {
        break;
    }
    hp = 100;
}

The above code loops through the instances in the room of an object obj_enemy and sets the variable hp to 100 for the first 10 it finds. If any more than 10 instances exist, the with code will call break and end.

An example of using continue in a with loop would be:

with (obj_enemy_parent)
{
    if (invulnerable == true)
    {
        continue;
    }
    hp -= 25;
}

This code will loop through all instances with the parent obj_enemy_parent, then checks each instance to see if the invulnerable instance variable is true or not. If it is, the continue keyword ends the current iteration of the loop and moves on to the next available instance, otherwise it removes 25 from the hp variable. This will repeat until all instances with that parent have been checked.

Specific Uses

The "other" Instance or Struct

As mentioned above, within the statement(s), the indicated instance or struct has become the target (self) instance that runs the code block, which means that the original instance (that contains the with and the entire code block) has become the other instance.

So - for example - to move all balls to the position of the current instance that actually contains the code, you can type this:

with (obj_ball)
{
    x = other.x;
    y = other.y;
}

Executing Code in a New Instance

with (instance_create_layer(x, y, "Instances", obj_ball))
{
    speed = other.speed;
    direction = other.direction;
}

The above code will create an instance of obj_ball and assign it the speed and direction of the instance that runs the whole code block.

TIP If you simply need to assign values to the new instance's variables, you can pass them through thevar_struct parameter of instance_create_layer / instance_create_depth.

Instance Checking Functions

Quite a few instance functions return either an Object Instance or noone. This makes them convenient to use in combination with the with statement: 

with (instance_nearest(x, y, obj_ball))
{
    instance_destroy();
}

The above code will destroy the instance of obj_ball nearest to the instance running the code. When no instance is found, the function returns noone so that the code inside the with isn't executed.

Assigning Struct Variables

with(clone_struct)
{
    xx = other.x;
    yy = other.y;
    spd = other.speed;
    dir = other.direction;
}

The above code uses with to target a struct and set the given struct member variables to the values stored in the instance variables from the instance calling the code.

Local Variable to Cross Scopes

var _inst = noone;
with (obj_ball)
{
    if (str > other.str)
    {
        _inst = id;
    }
}
if (_inst != noone)
{
    target = _inst;
}

The above code is slightly more complex than previous ones due to it using a local variable. This variable is local to either the event or the script function and not to the instance or struct and so can be used and accessed by all instances that are referenced within the code block. So, in the code above we have set a local variable to the special keyword noone and then use the with construction to have every instance of obj_ball check their str variable against that of the instance running the code block. If the value of the variable is larger, then they store their unique ID in the inst local variable, meaning that at the end of the code, only the instance with a value greater than the calling instance (or the keyword noone if none are larger) will be stored in the local variable _inst.