Search Unity

Bug Lag Spikes - Procedural Chunk-based 2D Tilemap World Generation w/ Advanced Rule Tiles on the fly

Discussion in '2D' started by spacefudge, Nov 15, 2022.

  1. spacefudge

    spacefudge

    Joined:
    Feb 1, 2021
    Posts:
    8
    Hi Forum People,

    Been trying to figure out a better, more performant solution to my use case for months now. I have scoured the internet, unity forums, articles, and reddit for how to improve the performance of my world generation setup; but I never find anything for my exact use case and I've tried most performance solutions, so maybe someone here can help!

    SETUP

    I am working on a 2D pixel art game utilizing unity's tilemap system to generate infinite worlds.
    The World Generation Algorithm works as follows:
    • Chunks are 16x16 blocks of tiles, 9 chunks are always loaded on those 5 tilemaps
    • When a player leaves a Chunk, 3 chunks ahead are loaded with SetTilesBlock(), and 3 chunks behind are unloaded
    • Deciding what tiles and game objects I need to place on certain tile positions is all Multi-threaded, but tile placement cant be.

    ISSUE

    When new chunks are loaded ahead of player there is a large lag spike as show in the profiler:


    You can download the game here to see for yourself and turn on the in-build debugger by clicking and making 2-3 circles with your mouse: https://spacefudge.itch.io/pixelworld

    Would LOVE any advice here, let me know if needed i can provide more info. Thanks! RJ
     
  2. flasker

    flasker

    Joined:
    Aug 5, 2022
    Posts:
    193
    use a coroutine to set the tiles slowly instead of doing it all at once
     
  3. AjdiNNNN

    AjdiNNNN

    Joined:
    Jan 14, 2019
    Posts:
    22
    can u give some examples, like how to do it, settile method is ultra slow, settiles and settilesblock do it all at once u can't change that, and loading takes ages to function normally, with current methods it seems impossible to generate tiles procedurally, i tried generating chunks it lag spikes for at least second with any configuration i tried spawing 1500x1500 tilemap and it takes forever ofcourse, but currently i have problem with loading as im generating tiles in start(), "async" load scene gets to 0.9f and then to scene activation and it then generates tilemap after that which freezes loading scene and it looks awful for player, and setting tiles from script has been issue forever in unity, only possible solution is using maybe DOTS but that requires a lot of modifications to be possible
     
  4. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,513
    1. Are you using Colliders and Composite Collider? These can really slow it down. On the one hand, better runtime collider performance, but on the other hand big spike. If this is the source of the problem, perhaps what you really need is to simplify your physics.

    2. How "ahead of the player" is your ahead of the player checking? You're detecting where the player is going in anticipation of them arriving there? If you have 5 tilemaps - and time before they arrive - you could separate the tilemaps so you only generate 1 per frame.

    edit:
    3. You also don't need to unload so aggressively.. you can surely frame-delay removal.
    4. Your profile pic doesn't really show the cost particulars very much. You can add profile markers to get more detail about what part is really causing the slowdown. You could also try Deep Profiling but be aware this only shows relative costs b/c merely having Deep Profiling on slows everything down considerably.
    5. At the very least, you can remove the logging - or conditionally compile it out if that's not done already.
     
    Last edited: Nov 15, 2022
  5. flasker

    flasker

    Joined:
    Aug 5, 2022
    Posts:
    193
    settile may be slow but its the only way to avoid lag spike, thats life

    use settile in a coroutine and lay it tile by tile, if your player is faster than the tile generation speed and he is reaching the terrain before its generated you just need to generate more terrain in advance, which might not be optimal but thats also life
     
  6. spacefudge

    spacefudge

    Joined:
    Feb 1, 2021
    Posts:
    8

    1. I am using Composite Colliders for 3 of the layers, I thought it might be the reason for the lag but turning off the collider didnt do anything. But removing them does help some. So what is a good way to go about simplifying my physics? I have custom sliced colliders currently.

    2. There are always 9 chunks spawned and the player is always in the center of the 9
    [ ][ ][ ]
    [ ][x][ ]
    [ ][ ][ ]
    If the player enters any of the outer chunks, I make that outer that chunk the center by generating the chunks around it and unload- setTileBlock (null) to the chunks that do not touch the new center.

    3. So how would I delay unloading? a Coroutine?
    4. Ok I can try out profile markers to get more info this week
    5. How do I remove logging?

    Appreciate the help Lo-renzo!


    Hey Flasker do you think using a coroutine for each SetTileBlock could help? And for unloading?
    Wasnt sure if coroutine stopped the main thread, but I def use it as a timer so thats silly of me.
    Thanks!
     
  7. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,513
    I checked out your demo and it's probably not worth it due to the kind of game it is, requiring accurate hitboxes. In some games, you can use a spatial hash or some other structure and do simple, inexpensive checks. However, for your environment it may help to supply a custom physics shape, so long as that would reduce the total number of vertices of your colliders.
    So long as it takes more than 5 frames at the fastest player movement speed to see the edge of the furthest away chunk, you could break up the loading to 1 tilemap per frame in a coroutine.

    Yes, here's a simple template for all of your issues:
    Code (CSharp):
    1. IEnumerator FrameDelayedActions(List<System.Action> actions)
    2. {
    3.    foreach (var a in actions)
    4.    {
    5.        a.Invoke();
    6.        yield return null; // yield a frame
    7.    }
    8. }
    You could use this to break up what you do to each tilemap (loading/unloading) across frames.
    The simplest thing is to comment it out.
     
  8. flasker

    flasker

    Joined:
    Aug 5, 2022
    Posts:
    193
    well it will help reduce your spike by 66% if you walk to the side and 80% if you walk on the diagonal but there will still be a lag spike if you have 1 sec it will be reduced to 0.3 sec which is still bad

    if you want to fully remove the lag spike you must use settile
     
  9. spacefudge

    spacefudge

    Joined:
    Feb 1, 2021
    Posts:
    8
    Ok I went through and implemented Coroutines for:
    • SetTileBlock on each layer
    • Each Chunk Load
    • Each Unload and for each layer of unload
    I am still getting lag and its more spread out now.

    Here are some profiler screenshots- from what i can tell it seems like AdvancedRuleTiles on SetTileBlock and the Colliders are the issue:






    This is the advanced ruletiles package I am using: https://assetstore.unity.com/packages/tools/sprite-management/advanced-rule-tiles-118786

    How should I go about using custom physics shapes with my tiles?

    I tried Switching to SetTile and adding a frame between all my SetTile calls and it took forever to get them all loaded. Are you suggesting some other Coroutine setup besides every tile? I had to revert it was taking forever and slowing the whole game probably because the physics colliders on the tilemap are updated every frame.


    THANKS guys for sticking with me. Lmk your thoughts and I can try it out, I might need to consider refactoring some systems, but id prefer not to sacrifice quality.

    Maybe I could make this packages code more performant.
     
  10. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,513
    First try: switching from colliders using sprite to grid. This won't be accurate but you just need to toggle a field on your tiles, and it'll tell us whether simplifying the shapes (grid uses only 4 vertices) improves things. If that works, try removing the composite collider and seeing if there's yet more speed. If those tests improve things, then you could add Custom Physics Shapes in the Sprite Editor to have more accurate yet still simple shapes.

    Worth a try. Alternatively you could see if there's a way around using it entirely? Perhaps you're using more complex tiles where you could substitute in some simpler ones? Also since it seems that it's from GetAnimationData, what are you animating? You may be able to animate in a different way, for instance through a shader for water or whatever.
    Is this happening simultaneously to the loading stuff? Because you could queue the unloads to the very end when nothing else going on since it's the lowest priority.