Is there a better way for enemy list pooling?

0 favourites
  • 9 posts
From the Asset Store
Enjoy the powerful simulation with accurate ball physics, build with realistic ball spinning animations
  • Hello guys,

    Background:

    I am trying to watch out for performance from the beginning of development with C2 and don't want to refactor my code so I decided instead of creating/destroying objects, I should disable and enable them on run time as needed. However I have a couple of questions about the whole thing before I attempt moving further into it and wanted to see if you guys can help me with it. Note the game is going to be released as an HTML5 game; playable on mobile and browsers.

    Explanation:

    We have a layout where we need to spawn enemies on run time. The enemies are limited to let's say 10 and depending on the level they have different types and chances. The game has a wave system; each level has 3, so each wave being done a new set of enemies should spawn. Note however that waves doesn't relate to number of enemies but rather time, this means that you could have 3 enemies left but the time for wave 1 ended so now you need to spawn 10 more regardless of the 3 that exist. All enemies are stored in a family called Enemies and there is an EnemyList (array object) that will store UID of each object.

    Questions:

    The easy question first; do you think object pooling in Construct 2 is worth it? In Unity for example it saves memory by not triggering the garbage collection memory thingy (I am not a coder so I don't know the full details, just know it saves memory by not activating 2 process, destroying and creating a new object).

    The harder question -- if the first question's answer is yes -- now my pooling logic proved to be exhausting and complicated which drove me to think I might either be missing something or there might be a better way to do all of this. That is the question, is there a way to do the object pooling better than my below code?

    Variables for preparations:

    Now the action:

    Basically handle the call depending on what is going on:

    Pick a random number from the list of numbers in the family + 1 (since the random function isn't inclusive), then set the enemyPicker to that number. Now pick a spawner and pass its ID to the function that spawns the enemy.

    The function that spawns the enemy compares enemyPicker and checks one of 3 scenarios:

    1- If the enemy is invisible but overlaps the spawner -- it is deactivated and therefore we can activate the current type of enemy that is nearby and set its position to where we are.

    2- There are no overlaps but there is the same type of enemy somewhere else in the map -- we then spawn a whole new enemy and push it to the EnemyList array that currently holds all the enemies we have in the level.

    3- There is actually an enemy overlapping the current spawner -- it is taken by an enemy and we can't use it, so we basically call a "Look for Another Spawner" function to find another spawner.

    This of course has to be repeated to all types of enemies in the game manually one by one:

    Finally here is the "Look for Another Spawner" function (bottom 2 lines).

    As you guys can see it gets complicated and really tedious (not to mention confusing) as I go along -- specially if I wanted to increase the variant of enemies (now there are only 6). So I am unsure if what I am doing is worth it to evade the create/destroy process -- maybe even it is worth it but I am doing it wrong. What do you guys think?

    Thanks a lot in advance!

    EDIT: I noticed a few "bugs" with my setup for example the first check with the Goon enemy shouldn't have inverted "Overlapping with Spawner" and also a few other places. I already fixed them in my game but I am a bit lazy to get new shots, upload, crop, save, C&P links here and update the post. Besides, I believe the bugs are irrelevant to the questions.

  • C2 internally recycles objects so that creating/destroying objects creates a minimal amount of garbage. That said have you measured if you get better performance with what you're doing? I've done stuff that creates/destroys hundreds of sprites every tick without causing more gc pauses.

  • C2 internally recycles objects so that creating/destroying objects creates a minimal amount of garbage. That said have you measured if you get better performance with what you're doing? I've done stuff that creates/destroys hundreds of sprites every tick without causing more gc pauses.

    I am not going to lie to you, no. I didn't. It has been "ingrained" in my mind that garbage collection is bad and therefore I have to watch out for that from learning Unity.

    That said, I did notice something while working on another much smaller game. The game had 2x Every random seconds (3, 10) loop and each one creates 1 object at a time and the object moves from left of screen to right of the screen and then gets destroyed. When testing (using debug) on Chrome it showed that I am using "Est. CPU utilization" at 45-50 % while frames where anywhere between 45-60. Granted that I always have at least 6-7 tabs open in Chrome and other stuff.

    Anyway, That is what got me thinking that with this game I should try an object pooling.

    But if you think it is unnecessary or I am thinking too much into this area and I should spend that much thought in another place, I would.

  • You use a bit more memory then needed by having different objects and by setting them invisible instead of destroying them.

    Setting them invisible will not do the trick. They still collide/overlap. They still count in .. in the iterations and the evaluations, and therefor they also bring performance down. If you really worry about memory and performance, then you destroy them when not needed. Only destroyed sprites have no graphics in memory and are not seen by the events.

    You can of course add 'is visible' to each event. But then you are even farther away from home.

    You think to much in 'IF this THEN that'. You must learn to think in service of the PICKLIST (selected objects list)

    To choose a spawner that is not occupied by a 'sprite' you do this ...

    Is spawner overlapping sprite (inverted)

    Pick random spawner.

    The first condition picks all spawners that do not overlap a sprite. That is the picklist. The second condition takes that picklist, and pick from there a random spawner. Done and over with. Just need a exception for when all is occupied. (wich you currently dont have and can not do that way, would be an endless loop)

    Then, to minimise code, you should work with instances of the same object. There is a plugin "nickname" that can help you if you really want different objects. But. Really. A 'healer' is an instance with an instance variable/boolean set to 'healer'. Another instance can be a 'hunter' by setting that instance variable to 'hunter'. As simple as that. So to spawn a 'soldier', or 'priest', or a 'worker' ..you do something like ...

    (I have no idea why your enemypicker goes in steps of 2)

    Spawn Sprite

    Set Sprite.type to enemypicker (a dot to say it is an instance variable)

    Set sprite's animation to str(enemypicker)

    Set some more abilitys, or have an array with the abilitys on the Y-axis, with the X-axis sync with the types.

    One event for everything.

    I wonder why you need an array with the UID's. You can build that array on any moment with a simple loop, on any moment. Without maintaining it with pushes. Why do you need that anywayz ?

    Check if 'player' collides with 'healer' is as simple as ...

    Global variable 'healer' = 1 (if healer is first type)

    On 'player' colliding with 'Sprite'

    Sprite.Type = healer ?

    Do stuf .....

    All this goes for different sprites in a family too. Besides that you can not create members of a family the same way. Need the 'nickname' plugin to do that.

  • You use a bit more memory then needed by having different objects and by setting them invisible instead of destroying them.

    Setting them invisible will not do the trick. They still collide/overlap. They still count in .. in the iterations and the evaluations, and therefor they also bring performance down. If you really worry about memory and performance, then you destroy them when not needed. Only destroyed sprites have no graphics in memory and are not seen by the events...

    Hmm... I can see what you mean with the invisible not doing the trick. I sometimes fail to separate the logic Unity uses to do things and Construct 2 and things get mixed up (probably because I use both interchangeably and need to focus one project at a time).

    I guess I'll use create and destroy instead. However to answer your comments:

    [quote:3ugksakn]You think to much in 'IF this THEN that'. You must learn to think in service of the PICKLIST (selected objects list)

    To choose a spawner that is not occupied by a 'sprite' you do this ..

    Yes, you are right, sorry about that >.< but good catch, that is just much easier. I didn't notice it until like 15 minutes ago when I was refactoring the code to use create/destroy instead.

    [quote:3ugksakn](I have no idea why your enemypicker goes in steps of 2)

    At this point, come to think of it, you are right. Steps of 1 gives a 1/Enemies.Count chance same as what I have right now with the steps of 2. My math failed when I tried to do this -- I was thinking of making the chance 25% for each. But I realize that isn't the way to do it AT ALL.

    [quote:3ugksakn]Spawn Sprite

    Set Sprite.type to enemypicker (a dot to say it is an instance variable)

    Set sprite's animation to str(enemypicker)

    Set some more abilitys, or have an array with the abilitys on the Y-axis, with the X-axis sync with the types.

    One event for everything.

    I wonder why you need an array with the UID's. You can build that array on any moment with a simple loop, on any moment. Without maintaining it with pushes. Why do you need that anywayz ?

    No reason to use an array aside from saving myself a for loop or something. But either way, what I thought of now is something similar to what you said but outside of families at all. Just 1 object has different animations for each type of object + a variable type like you mentioned and once it set, a sheet exists to dynamically set animations, attacks, etc... to that type.

    That or just use Enemies as you just did and have type determine which object to spawn from it -- I do have the Nickname plugin.

    You and R0J0hound I think confirmed my suspicions this is not worth doing considering the comparison between the two ways in every way. Thanks a lot for the insights guys

  • Hmm... I can see what you mean with the invisible not doing the trick. I sometimes fail to separate the logic Unity uses to do things and Construct 2 and things get mixed up (probably because I use both interchangeably and need to focus one project at a time).

    Its a lot more easyer/more basic, it grows in the roots.

    We need to be able to use invisible helper objects, so 'invisible' needs also to be evaluated by the conditions.

    So, say ... We have 100 enemy's and 1 player.

    99 enemy's are invisible (or somewhere in the marge of the layout, or not on screen, people do those things)

    Now when you use the condition 'on player overlapping enemy' ...

    The system iterates trough each enemy (all 100) and tests if it is overlapping player.

    You see the fall in performance ?

    If you had destroyed the 99 useless enemys, it would not be bizzy iterating.

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Yeah, I get what you are saying. It is completely different from Unity; there you can do an Enemy.SetActive(false); and there you go, the object is deactivated but the amount of memory allocated to it is reserved so you don't need to add extra memory leg work to delete and recreate. The object doesn't then participate in collisions or anything. You can then go Enemy.SetActive(true); to set it back on and you just saved some performance. Same with UE4 if I recall correctly (unless you manage the garbage collection manually with C++ as I heard)

    That is what I understand at least.

    But I get that Construct 2 is completely different and to be honest... this way much better. I don't have to worry about what object is on or off and where it is. I just can destroy it and forget it about it. Such a heavy load cleared off

    Thanks a lot!

  • This is the one and most important manual entry. You get this, you get it all.

    https://www.scirra.com/manual/75/how-events-work

  • I have read it a couple of times but unlike what I believe, it is sometimes not easy to get these concepts present in your mind all the time. But you are absolutely right, this is extremely important, thanks for the reminder I'll go through it again!

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)