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 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. The with statement has the following syntax:

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

For the expression, you can indicate one or more instances (or a struct) to perform the code on, using an instance ID, the object ID (which indicates that all instances in the room of this object are to run the code block), the struct ID, or one of the special keywords (all or other). This will then change the scope of the code within the curly brackets {} from the instance, struct or function that actually holds the code 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, as if that instance is the current ( self) instance. 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;
}

Essentially this is a loop, and each iteration of the loop, the code will run on one instance of the object obj_ball.

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);
}

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;
}

The with statement is an extremely powerful tool and is useful in many, many circumstances so it is important that you understand fully how it can be used. To help there are a few more examples of use below:

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.

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.

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.

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.

It is worth noting that you can use the special break and continue statements within a with call too. 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, eg:

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

The above code loops through the instances in the room of the 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 instance 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.