(SOLVED) Serious lag when having many tree objects

Discussion in 'Programming' started by ZombieSquirrel, Oct 5, 2019.

  1. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    Hi everyone!

    For a while now, I've had serious lag issues, to the point where the game becomes unplayable.
    This only happens when I have a lot of tree objects in my game.

    Trees have a chance to spawn a new one each day in my game. They also grow each day.
    In order for a tree to be placed, it must first check if that is allowed (if no other object is present at that location).

    When I have like 50 trees, it's fine. But once the numbers go up by a lot, it causes massive lag.

    I can't pinpoint why. The other weird thing is , when I move to another room, the lag endures. Rooms are persistent by the way.

    So since I'm out of solutions, let me sum up what I have:

    -Many tree objects, 6 types of trees.
    -They only turn to solid if the player is in a collision_rectangle around the tree.
    -Collision check happens from the player object, not in each individual tree.
    -Trees have a 1 in 4 chance each new day to spawn a seed.
    -When a seed spawn triggers, it has a 1 in 12 chance to choose direction. Basically,
    1 in 4 chance to spawn, and then spawns randomly in 1 out of 12 places around the original tree.
    -Seeds can only be spawned in areas that are allowed (no other object present)
    -There is no parent object.
    -All together, it's a couple of hundred lines of code in the tree object (which causes tree types, tree spawns, tree growth, tree cut by player,... It really works great in low amounts of trees)
    -Depth of trees is just a single number in their create event.
    -Depth of player is updated in player step event

    It leaves me wondering what the cause could be.
    Is it the collision checks?
    Is it the amount of code (hundreds of trees = hundreds x hundreds lines of code)?
    Is it each individual draw event? (not drawing the tree reduces lag a lot, but not completely).
    Why does the lag carry over to other rooms (also persistent) where there are only a couple of trees?
    Is it just the amount of objects and there's no way around it?

    It really comes down to "how to reduce lag when working with a large amount of objects" really. I probably have to start from scratch with my trees, however I already learned a lot about player interaction, growth, spawns,... So the knowledge doesn't go to waste.

    I'm missing something here... Something NOT TO DO when having a lot of objects.
     
  2. FlameRooster

    FlameRooster Member

    Joined:
    Feb 14, 2017
    Posts:
    133
    ParodyKnaveBob likes this.
  3. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    A yes, a question I forgot to add in that list.

    I was thinking about that as well. But what about moving to another room?
    I'd think, since I'm in another (persistent) room, the draw events of the hundreds of objects from the previous room no longer run?
    I will definitely try and add code to stop the draw event when the objects move out of view.
     
  4. FlameRooster

    FlameRooster Member

    Joined:
    Feb 14, 2017
    Posts:
    133
    I haven't found a use for persistent rooms for myself and so I have never used them, so I can't really say in that matter.

    I don't think stopping the draw event is going to do too much. Objects are heavy and so your best bet is to activate/deactivate somehow. Or, you can make the trees a part of a tile set (or tile set prop) and just brush the trees in that way, so they aren't heavy objects.
     
  5. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    Well here's my limitation in understanding the engine. If the issue is that you can't have many objects in the game, I have a problem :D
     
  6. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    Also, I need them to be objects. The player needs to interact with them in a variety of ways. Having them be "just images" on a tile layer won't work for me. Also, that would screw up their depth values.
     
  7. Amon

    Amon Member

    Joined:
    Sep 13, 2016
    Posts:
    274
    Yep, I agree. The issue is having many objects in your game with your understanding of coding. Try object pooling, deactivating instances when not in view etc.
     
    FlameRooster likes this.
  8. FlameRooster

    FlameRooster Member

    Joined:
    Feb 14, 2017
    Posts:
    133
    I suggested that and he says he can't do that due to persistent rooms, so I don't know.
     
  9. Joe Ellis

    Joe Ellis Member

    Joined:
    Aug 30, 2016
    Posts:
    948
    You can have thousands of instances with no lag at all, I tested it with an empty object that does nothing. So its not instances themselves causing the problem. But there could be a few things that each of the instances is doing that adds up to alot of processing power. I tbh would like to see your actual project out of interest, and there might be something I find that's like "Oh hell no!"
    But also, the tree branching thing can be done alot more efficiently with arrays. Arrays containing other arrays mainly. And I know you said you need them to be instances, but you don't. You can just make a special collision script or whatever you need to do. Look at how people handle per triangle collision systems in 3d. They don't make every triangle a separate instance, instead each triangle is an array. I contains all the coordinates and everything needed for instances to interact and collide with. The same thing can apply to your tree branches.
     
  10. IndianaBones

    IndianaBones Member

    Joined:
    Jul 5, 2016
    Posts:
    2,220
    Have you used Debug mode to check what is taking up most of your performance? This should usually be your first step, as you want to target the thing that is actually eating cycles, otherwise you're just guessing.
     
    ParodyKnaveBob likes this.
  11. Joe Ellis

    Joe Ellis Member

    Joined:
    Aug 30, 2016
    Posts:
    948
    I've never used debug mode, I always feel like I'd be cheating if I used that haha. I preferred to work out what it was instead, sure, I could've saved hours of time, but I also learned alot. I wouldn't recommend this approach for the general humans though, I'm a genius, so it's easier for me
     
  12. IndianaBones

    IndianaBones Member

    Joined:
    Jul 5, 2016
    Posts:
    2,220
    That's what the debug mode / profiler is there for! I don't have time to spare when I can see at a glance what I need. Imma gunna call you crazy joe from now on! :)
     
  13. RangerX

    RangerX Member

    Joined:
    Jun 20, 2016
    Posts:
    2,596
    ZombieSquirrel, maybe you could let us see your trees' code. We could maybe see something to optimise.
    Here's some other generic advice:

    - Use a parent object here. Its the perfect situation.
    - Deactivate the trees in a "outside view" event and have your main character or whatever camera activate objects
    - Does all your checks absolutely needed every step? Only run code when its reasonably necessary.
     
  14. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    Here's the code in the step event (don't worry about create event, that(s just initialisation of the variables you see here, and the draw event is literally only a couple of lines of code "draw_sprite(....)"


    STEP EVENT:
    ------------------------------------

    //After hit with axe; run treecut script
    SCR_TreeCut();
    //a script that changes tree HP ONLY IF the player hit's it with an axe, this is triggered in the player object.

    //Set trees to solid once player moves out of the collision box area when planting.
    ///////////////////////////////////////////////////////////////////////////////////
    if (TreeStage >= 1)
    && ((collision_rectangle(x,y,x+15,y+15, OBJ_Player, false, true))) //does not use mask! just checks if player solid is in this area!!
    {
    if (solid != true) {solid = true;}
    }
    else if (solid = true)
    {
    solid = false;
    }
    //Make trees transparent if player moves behind them (tree stage specific)
    if (TreeStage = 2)
    {
    if (OBJ_Player.x >= x-15 && OBJ_Player.x <= x+15) && (OBJ_Player.y >= y-16 && OBJ_Player.y <= y+1)
    {
    TreeTransp = 0.40;
    } else {TreeTransp = 1;}
    }
    if (TreeStage = 3)
    {
    if (OBJ_Player.x >= x-15 && OBJ_Player.x <= x+15) && (OBJ_Player.y >= y-32 && OBJ_Player.y <= y+1)
    {
    TreeTransp = 0.40;
    } else {TreeTransp = 1;}
    }
    if (TreeStage = 4)
    {
    if (OBJ_Player.x >= x-24 && OBJ_Player.x <= x+24) && (OBJ_Player.y >= y-74 && OBJ_Player.y <= y-3)
    {
    TreeTransp = 0.40;
    } else {TreeTransp = 1;}
    }

    //Grow tree only if no other tree is present in a square of tiles around it.
    if (DayGrow < TimeTotalDays) && (TreeDead = false) && (TreeTopAlive = true)
    {
    DayGrow += 1;
    if (TreeStage < 4)

    //When the player leaves this map, and comes back many days later, the trees need to catch up and know how many times to grow.
    //Hence, DayGrow +1, it does it thing, DayGrow again +1,... untill DayGrow = TimeTotalDays (TimeTotalDays = current day).
    {
    if !(collision_rectangle(x-16, y-16, x+31, y+31, OBJ_Tree, false, true))
    {TreeStage += 1;}
    }
    if (TreeStage = 4)
    {
    SpawnSeedChance = irandom_range(1,3)
    {
    if (SpawnSeedChance = 1)
    {
    SCR_TreeSpawnSeed(TreeType, DayGrow);
    }
    } //end of: SpawnSeedChance = irandom_range(1,6)
    } //end of: if (TreeStage = 4)
    } //end of: if (DayGrow < TimeTotalDays) && (TreeDead = false)




    //Spawn shadow only once (shadows always appear below player, depth of them is set in shadow object)
    if (TreeStage >= 3) && (ShadowNotSpawnedyet = true)
    {
    with (instance_create_depth(x, y, depth, OBJ_Tree_Shadow)) {ShadowStage = other.TreeStage;}
    ShadowNotSpawnedyet = false;
    }
    if (position_meeting(x, y, OBJ_Tree_Shadow))
    {
    with (instance_position(x, y, OBJ_Tree_Shadow)) {ShadowStage = other.TreeStage;}
    }




    DRAW EVENT
    ----------------------------------
    if (TreeStage < 3)
    {
    draw_sprite_ext(TreeStumpSprite, TreeStage, x+8, y+11, 1, 1, TreeSwivel*1.5, c_white, 1);
    draw_sprite_ext(TreeSprite, TreeStage, x+8, y+9, 1, 1, TreeSwivel*1.5, c_white, TreeTransp);
    }
    if (TreeStage >= 3)
    {
    draw_sprite_ext(TreeStumpSprite, TreeStage, x+8, y+11, 1, 1, StumpRot, c_white, 1);
    if (TreeTopDrop = false)
    {
    draw_sprite_ext(TreeSprite, TreeStage, x+8+DropOffSet, y+9-abs(DropOffSet/5), 1, 1, TreeSwivel, c_white, TreeTransp);
    //use drop offset to move tree away from stump when cut!
    }
    }
     
  15. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    Look at my previous reply :) Sorry for the late response, I'm in Europe and it was almost 3 at night when I first posted this thread, I went to sleep xD
     
  16. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    I should also mention that seeds are separate objects with a depth always below player, just like the shadow. Basically the "SCR_TreeSpawnSeed(TreeType, DayGrow);" script
    determines through the TreeType and the DayGrow variable what kind of seed needs to be spawned, and make the seed know at which day it was spawned.
    Then the seed goes through a random number each day to see if it can grow or not. If it can, it destroys itself and spawns a tree in its first stage.
     
  17. RangerX

    RangerX Member

    Joined:
    Jun 20, 2016
    Posts:
    2,596
    Ok it seems there's not much wrong in your code there. What should take the most time in your code there is the "collision" family of functions. That being said, try to optimise your logic for the "shortcircuit evaluation" function of game maker. (see your global game settings). This function make so when an evaluation is false, it automatically go to your "then" or "else" without checking the other conditions in your "if " line. I suppose you know where am going there but you can try and never have a collision function as the first check. This way it will never be run if not necessary. Do that with any costly check in your code logic.

    Second thing, I doubt you need to do all of this every step. Trees could try to spawn seeds only every 2 or 4 steps and same thing for the growing. You could count the steps with a variable and have the seed part never being read in the same step as the grow part.

    Last thing, does this axe script really needs to be read every step?
     
    ZombieSquirrel likes this.
  18. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    I've just turned off that collision check and the lag remains. However, yeah that axe script... It handles how the tree "swivels" when the player hits the tree, it handles the animation when the tree is destroyed, and handles how wood is spawned, depending on tree type. It's also quite substantial.
    However, in that script, NOTHING HAPPENS if the player doesn't hit the tree.
    Basically, all of the code in that script lies within an if statement:
    if (TreeHit = true)
    {
    … script code
    }

    However, you were right! I removed that script from the code, and the lag is gone... I feel a bit like an idiot now xD
    But that leaves me to wonder... Does GM run through code that should not be executed? I mean, TreeHit = false all the time, unless I specifically let my player stand next to a tree, and attack it.
    And since that variable is false, all of that script's code should not run right? It can only run in one tree at a time.
    But if GM reads it every step, without executing it, it might be the cause of the lag?

    I love these forums. Each time i have a major hurdle to overcome, I always find the cause and solution to the problem here, but it seems like it's never one of the more complex things I'm doing xD It's usually something minor, and it leaves me feeling that I need to learn even more :p
     
  19. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    I made it so the script no longer runs every step in the Tree object step event. Basically I added one more "check" that is turned on when the player hits the tree.
    Now the the script only runs if that "check" is turned on.
    The lag reduction is enormous!

    Thanks so much, I couldn't immediately see this as the problem. That simple question you asked me opened my eyes to the problem. Thanks!
     
    ParodyKnaveBob, Joe Ellis and RangerX like this.
  20. RangerX

    RangerX Member

    Joined:
    Jun 20, 2016
    Posts:
    2,596
    Calling a script takes time. Using a script is good (or a parent depending the situation) if you have multiple different objects needing the same code.
    If you have tons of the same object, too many scripts are going to be called.
    Always continue coding with the mindset "do I need to run this every step". Use parents, use scripts when it makes sense. Never have activation, deactivation, or collision functions as the first comparison in you "if" lines.
     
  21. ZombieSquirrel

    ZombieSquirrel Member

    Joined:
    Oct 18, 2018
    Posts:
    98
    UPDATE!!!!
    The lag is almost completely gone now!

    I did 2 "tricks", as I noticed that the ENGINE has issues with having so many instances PROCESSED.

    Trick 1: minimizing step event code. Basically, I locked long blocks of code behind a couple booleans, making sure that code that DOES NOT NEED TO RUN EVERY STEP, doesn't run every step.
    Basically, each instance now only runs a minimal of code each step. This reduced lag a lot.

    Trick 2: Have my CAMERA control if these instances are visible or not. If the object is out of the camera's view, they are turned to not visible. If they come into view, they are turned back to visble.
    BUT!!! This didn't completely remove the lag.

    basically, in my draw event I now have something like
    if (TreeOnCamera = true)
    {
    draw code
    }
    (the camera switches this to false or true)

    I then realized where the "engine processing" comes into play. So even when having only those
    "in the camera's view" be visible, those will still run code a lot right?
    Well now I did:

    if (TreeOnCamera = true)
    {
    shader_set(SHD_DrawTree) //a simple passtrhough shader, nothing fancy and really beginner-level

    draw code

    shader_reset();
    }


    Lag? Almost none. I can go to a massive amount of instances, more than needed and it actually now looks ugly with so many overlapping, so I'm building in an instance limit on my map.


    I never imagined I'd get this result, but I thought "why have GM and the processor work through all that draw code if the videocard could do it for them through a shader?"

    This is my very first attempt at a shader, I don't understand how any of it really "works" yet, but the result is much, much better o_O

    I'm not even sure it's completely because of the shader, you know, sending the draw stuff to the graphics card to handle.
    I'm pretty sure it's a combination:

    -Minimize code wherever you can
    -Only objects inside the view are needed to be visible
    -Shaders can handle a lot of graphics processing power it seems
     
  22. obscene

    obscene Member

    Joined:
    Jun 21, 2016
    Posts:
    2,433
    Haven't read all your code but just wanted to throw this in the mix... alarms are a good substitute for step events when it's Ok if the code runs every other frame, every third frame, etc. Evenly distribute the time the alarms begin so that each frame only a fraction of the trees are updating.
     

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