1. Hello Guest! It's with a heavy heart that we must announce the removal of the Legacy GMC Archive. If you wish to save anything from it, now's the time! Please see this topic for more information.
    Dismiss Notice

Using "with" object

Discussion in 'Programming' started by evildead9000, Mar 25, 2018.

Tags:
  1. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Here's one that's probably been asked a million times and I apologize for probably posting it again, but for some reason controlling multiple instances of the same object eludes me greatly!

    I'm creating a simple shooter like Galaga / Space Invaders, and have multiple instances of an enemy. In the step event of that enemy I have the following script.

    Code:
    switch (global.Enemy_State)
       {
       case "Appearing": scr_Enemy_Appearing(); break;
       case "Attacking": scr_Enemy_Attacking(); break;
       }
    
    script_execute(global.Enemy_State);
    
    Can someone please explain the difference between

    In the create event the global.Enemy_State is "Appearing". In the appearing script I have a switch because numerous enemies will also use it:

    Code:
    case obj_Enemy_1:
    
         with (obj_Enemy_1) {
              mp_potential_step(obj_Player1.x, obj_Player1.y, 2, true);
       
               if y >= room_height / random_range(2,5){
               global.Enemy_1_State = "Attacking";
           }
    }
       break;
    }
    
    I've also attempted the code above without "with (obj_Enemy_1)". What happens is that ALL instances of Enemy_1 stop wherever the first one did.

    I would like each Enemy_1 to stop at "room_height / random_range(2,5)", then attack.

    Can someone please explain the difference between using "with (obj_Enemy_1)" and not?
     
    Last edited: Mar 25, 2018
  2. seorin

    seorin Member

    Joined:
    Mar 13, 2018
    Posts:
    41
    with (obj_Enemy_1) executes the code as if it's being done from that object.

    I believe your problem is that when you refer to obj_Enemy_1, you are referring to ALL instances of that object. This can work if there is only one object, or if you want to do something to all instances. If you want to treat instances separately, however, you need the instance ID. You can get the ID as a return value from instance_create_layer, or you can follow some of the example code on this manual page: https://docs2.yoyogames.com/source/...stances/instance_functions/instance_find.html

    If you use the instance ID instead of the object name, you can control each instance separately like you want.
     
    evildead9000 likes this.
  3. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Thanks for the quick response. I checked out the link, but I'm now not sure how to implement it.

    Code:
    var i;
    for (i = 0; i < instance_number(obj_Enemy); i += 1)
       {
       enemy[i] = instance_find(obj_Enemy,i);
       }
    
    Where would I place that code?

    I'm thinking in my create event of the enemy, so each new instance will increment enemy, assigning a higher value even if the previous enemy was destroyed.
    Is that correct? If not, please correct me!
     
  4. seorin

    seorin Member

    Joined:
    Mar 13, 2018
    Posts:
    41
    That code would go elsewhere. It's for a situation where you need to find all instances and you're not sure how many there are. For example, in the create step of another object that loads once with the room. Then it could find all the enemies in there already and put their IDs in an array.

    It sounded from your first post like this code is running on some other object (otherwise you wouldn't need 'with'), like an enemy spawner or controller object. But... I'm having a bit of trouble understanding where you're putting this code and why it's not on the enemy object in the first place. It should work fine if you just put something like this in the step event of the enemy object:
    Code:
    if( y >= room_height / random_range(2,5)) state = "Attacking";
    If you want them to all stop at a random height, but the same random height, then put a height variable on a controller object or in a global. Set it to your random number at room creation (or the beginning of a new spawn wave), then have the enemy object reference that number instead. For example:
    Code:
    On a controller object, spawning a new wave:
    row_height = room_height / random_range(2,5);
    
    In the step event of the enemy object:
    if(y >= oSpawner.row_height) state = "Attacking";
    Also, just as an added recommendation, it's probably better to use an enum for state values instead of strings. That can be put just about anywhere, but I'd put it in the create step of the controller object since there should only be one.
    Code:
    enum ENEMY
    {
        APPEARING,
        ATTACKING,
        DYING
    }
    This works globally and will assign numbers to each of those words so you can write clear code the way you're trying to, but with cheap integer comparisons instead of more costly strings:
    Code:
    state = ENEMY.ATTACKING;
    switch  (state)
    {
        case ENEMY.ATTACKING:
    ...
     
    evildead9000 likes this.
  5. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Can it be done dynamically? I would like all my enemies to stop at a different random height. With that said, I don't know where to place the code (if not the create event of the enemy object).

    Where should I place the code and can you explain why? Is there a different approach?
     
  6. seorin

    seorin Member

    Joined:
    Mar 13, 2018
    Posts:
    41
    Yes. In the create event of the enemy object:
    Code:
    row_height = room_height / random_range(2,5);
    In the step event:
    Code:
    If(y >= row_height) state = ENEMY.ATTACKING;
    Code that you put in an object will run separately on each instance. The only tricky part is when you reference a different object you have to specify which one with an instance ID. If you reference a different object by object name instead, it will apply to ALL instances. That won't be an issue if you're running the code from the enemy object in the first place instead of trying to reference a specific enemy from a separate object.
     
    evildead9000 likes this.
  7. JFitch

    JFitch Member

    Joined:
    Sep 28, 2016
    Posts:
    428
    In this code:
    Code:
    switch (global.Enemy_State)
       {
       case "Appearing": scr_Enemy_Appearing(); break;
       case "Attacking": scr_Enemy_Attacking(); break;
       }
    script_execute(global.Enemy_State);
    what is the purpose of that last line?
     
    evildead9000 likes this.
  8. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Edited and corrected:

    The last line is not necessary. I copied and pasted it without even realizing.
     
    Last edited: Mar 28, 2018
  9. Ampersand

    Ampersand Guest

    While I'm still thinking there is an easier way to do whatever it is you're doing, I just wanted to note that your original code works fine. I believe the reason they all stop at the "same" height is because they're checking every step, and it's nearly statistically impossible for them to not check at least once for the minimum of your random_range. So instead of getting a nice spread like you want, they're all stopping at the minimum (room_height / 5).

    Can you explain to us what you're trying to achieve, and maybe we can better help you? Are the enemies simply moving down and then at a random height changing into attack state?
     
  10. Ephemeral

    Ephemeral Member

    Joined:
    Mar 1, 2017
    Posts:
    185
    I don't know weather to laugh or cry.

    Okay, one at at a time.

    Firstly,
    Code:
    script_execute(global.Enemy_State);
    is doing absolutely nothing and is only not causing an error through luck. Delete or comment out this line and see if doing so has any effect at all, because I bet it won't.
    Also, read the section on switch and case in the manual again, because I'm very sure you misunderstood something fundamental.

    Secondly,
    You are using a global variable because you want every instance to never have separate states, correct? There will never be some enemies that are Appearing while others are Attacking?
    But you are initializing and setting this variable in the object of which there will be multiple instances, which means that it's value is overwritten every time an instance is created and also during every step with multiple instances affecting it.
    This is the kind of thing that really ought to be done with a controlling object separate from your enemies.

    Or, if you do intend for there to be some enemies Attacking at the same time other enemies are Appearing, then you might as well start over from scratch, because I don't even know how you got the code you have if that was your goal.

    Thirdly,
    I reiterate that I'm and like 99% sure you really really misunderstood switch.

    Also, this is just a really awkward way to do this in the first place, almost like you're actively struggling against the whole object/instance system in a really weird and unnecessary way.

    So,
    You should have game-level states in a controller object which has no sprite and only has one instance in the room, so it can manage all the global stuff without interference, and also possibly keep track of and control all the enemies directly.

    You should then have individual enemy behavior contained with each enemy object with instance-scope states where it is specific to each instance. Lay this all out in the Event code first, then break re-used or self-contained code out into scripts to keep things organized. Keep hierarchy in mind.

    And you should definitely read this thread about with and this thread about loops if you haven't already.
     
  11. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Yes. I would like each enemy to stop at a random height, then change into an attacking state.
     
  12. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    I'm 99% sure you're really really trying to help me LESS than really really being condescending.

    I joined Nov 19, 2017, which isn't a very long time ago. Although, I stated that I was having difficulty understanding controlling multiple instances of the same object, you felt the need to go out of your way to basically tell me to start from scratch. Your reply was just a really awkward way to respond in the first place, almost like you're actively struggling against the whole concept of "helping someone" in a really weird and unnecessary way.

    GameMaker Community is an amazing place to share knowledge and many have helped me greatly. I look forward to having a stronger knowledge of GML and possibly be able to help others here as they have helped me.

    With that said, I posted my question after attempting it for a few hours and I tried my best. I do have a good understanding of switch / case, and "script_execute(global.Enemy_State);" isn't necessary at all. It was part of my many attempts to resolve it on my own. I pasted it here, and even replied to JFitch's question about it incorrectly. I'm only human.

    I'm enjoying exploring GML and also figuring out more than one way to code something. I have used Enum, as well as an object controller in the room with no sprite. However, to keep it as simple as possible I opted not to post them here. I'm not sure if having them is absolutely necessary because there is more than one way to code something. I still have much to learn. I'm sure it's possible to have all the code within the enemy object; with no scripts, states, or object controllers. If it's not possible, please let me know and why.

    My goal is to have multiple instances of the same object each stop at a random height on the screen, then change state to attacking. Basically, there will be numerous enemy objects at different heights on the screen attacking. Since each object will appear at different times (controlled by an alarm event), they will each end up attacking at different times. I do not want all the enemies to stop at the same height and attack at the same time.

    Your response helped me somewhat, but was more confusing than helpful and provided a lot of criticism with no code examples. There are many ways to constructively inform someone that what he or she is doing is incorrect, and your response is not one.

    This forum is an invaluable tool for anyone wanting to learn how to create a video game using GameMaker Studio. I'm happy to be a member of this community and proud to take on the difficult endeavor of creating a video game.

    I'd greatly appreciate if anyone can provide an explanation to best implement the object behavior I'm requesting with a code example, which would TRULY help me.
     
    drowned likes this.
  13. seorin

    seorin Member

    Joined:
    Mar 13, 2018
    Posts:
    41
    The code I posted in my last reply should have been exactly what you're asking for. If that didn't work, then I'd need to know what happened to help you further. Some more sample code to get a better context of how this is all supposed to interact might help as well, for me or anyone else trying to help you figure this out.
     
    evildead9000 likes this.
  14. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,689
    If you do not want your enemies to in exact unison, the variable for state cannot be a global variable as you have done, it must be an instance variable. This is clearly mentioned in both seorin's response and in the Manual:
    Set up the variable in the create event:
    Code:
    state = "Appearing";
    
    Then in your step event:
    Code:
    switch (state)
       {
       case "Appearing": scr_Enemy_Appearing(); break;
       case "Attacking": scr_Enemy_Attacking(); break;
       }
    
    That way each instance has their own state instead of sharing one globally.

    As for the question about with statements, in this case you should NOT execute the actions using with (obj_Enemy_1) because that would make every instance of that object perform the action. Leave it without the with so that each instance acts independently according to its own state.
     
    evildead9000 likes this.
  15. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Sorry for delay. I wish I had more time to reply sooner today.

    Thank you Seorin and FrostyCat. It's finally clicking. I got rid of my global variables, which caused all enemies to behave the same as would using with (obj_Enemy_1). I now understand my errors much better.
    I've done the suggestions by Seorin and my enemies are starting to behave as I want.

    Unfortunately I won't have time tonight to fully test. I do want to post all the code I'm using to share with others and will do so tomorrow night.
     
  16. Ephemeral

    Ephemeral Member

    Joined:
    Mar 1, 2017
    Posts:
    185
    *wipes evildead9000's spit off her face*

    Okay then. Consider me rebuked. But I would like to say that if that 9000x-more-condescending-that-I-possibly-was rant is how you react to being told your basic underlying assumptions are wrong, you're never going to learn.

    Anyway,
    Enemy Object Create Event:
    Code:
    enum enemy
    {
        null,
        appearing,
        attacking,
        dying,
        victorious
    }
    
    behavior_state = enemy.appearing;
    attack_threshold = room_height / random_range(2, 5);
    target_id = instance_nearest(x, y, obj_Player1);
    advance_rate = 2;
    
    projectile = obj_bullet; // or whatever your projectile object is called.
    weapon_cooldown = 0.5 * game_get_speed(gamespeed_fps); // number of seconds times FPS
    weapon_wait = 0;
    projectile_speed = 8; // or however fast you want it to go
    Enemy Object Step Event:
    Code:
    if (!instance_exists(target_id)) behavior_state = enemy.victorious;
    
    switch (behavior_state)
    {
        case enemy.appearing:
    
            mp_potential_step(target_id.x, target_id.y, advance_rate, true);
            if (y > attack_threshold) behavior_state = enemy.attacking;
    
        break;
    
        case enemy.attacking:
    
            var attack_vector = point_direction(x, y, target_id.x, target_id.y);
            image_angle = attack_vector; // optional
            if (weapon_wait > 0)
            {
                weapon_wait -= 1;
            }
            else
            {
                var shot = instance_create_layer(x, y, "Projectiles", projectile);
                shot.direction = attack_vector;
                shot.speed = projectile_speed;
                weapon_wait = weapon_cooldown;
            }
    
        break;
    
        case enemy.dying:
    
            instance_destroy();
    
        break;
    
        case enemy.victorious:
        break;
    }
    
    Spawner Create Event:
    Code:
    spawn_delay = 2 * game_get_speed(gamespeed_fps);
    spawn_type = obj_Enemy_1;
    max_enemies = 20;
    
    alarm[0] = spawn_delay;
    
    if (instance_number(object_index) > 1)
    {
        instance_destroy();
        show_message("You messed up. Fix it.");
    }
    Spawner Alarm 0 Event:
    Code:
    if (instance_number(spawn_type) < max_enemies)
    {
        var spx = irandom_range(0, room_width);
        var spy = -32;
        instance_create_layer(spx, spy, "Enemies", spawn_type);
    }
    
    alarm[0] = spawn_delay;
    This doesn't cover projectile collisions, tracking damage, or aesthetic effects, but it covers what you asked about.
     
    Last edited: Mar 30, 2018
    evildead9000 likes this.
  17. evildead9000

    evildead9000 Member

    Joined:
    Nov 20, 2017
    Posts:
    65
    Hello all. Sorry for the long delay. "Life is what happens when you have other plans."

    Much thanks to all who helped me and especially Ephemeral. No hard feelings at all and I apologize if I was condescending.

    There's no need for me to post any code because your code worked great!
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice