1
\$\begingroup\$

I am trying to understand the flyweight pattern better by trying to optimize memory usage by spawning 10000 copies of an enemy GameObject that I have which has an Animator component, SpriteRenderer component, and BoxCollider2D component.

I've identified that the intrinsic states would be the sprite and the animator controllers so I reference these in a ScriptableObject for every one of my GameObjects to use for their Animator and SpriteRenderer components.

using UnityEngine; [CreateAssetMenu(fileName = "NewEnemyFlyweightData", menuName = "ScriptableObjects/EnemyFlyweightData")] public class EnemyFlyweightData : ScriptableObject { public Sprite DefaultSprite; public RuntimeAnimatorController AnimatorController; } 

I use this spawner in order to test spawn 10000 of these GameObjects.

using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; public class EnemySpawner : MonoBehaviour { [SerializeField] private Enemy _prefabToSpawn; [SerializeField] private EnemyFlyweightData _enemyFlyweightData; private Stack<Enemy> _objectPool; private const int StartingAmount = 10000; private SpawnPoint _spawnPoint; private void Awake() { _spawnPoint = FindObjectOfType<SpawnPoint>(); _objectPool = new Stack<Enemy>(); for (int i = 0; i < StartingAmount; i++) { Enemy enemy = Instantiate(_prefabToSpawn); enemy.AddComponent<Animator>().runtimeAnimatorController = _enemyFlyweightData.AnimatorController; enemy.AddComponent<SpriteRenderer>().sprite = _enemyFlyweightData.DefaultSprite; _objectPool.Push(enemy); } } private void Start() { while (_objectPool.Count > 0) { SpawnEnemy(); } } private void SpawnEnemy() { if (_objectPool.Count > 0) { GameObject go = _objectPool.Pop().gameObject; go.SetActive(true); _spawnPoint.SpawnGameObject(go); } } } 

However, it seems to use lots of memory and it doesn't seem to make a difference when compared to just putting everything in one prefab and instantiating that way. In fact, it says it consumes even more memory when trying flyweight.

Memory usage when instantiating 10000 GameObjects with my flyweight attempt according to profiler:

enter image description here

Memory usage when instantiating 10000 GameObjects without flyweight with everything on prefab according to profiler:

enter image description here

What am I doing wrong here and how should I be referencing my intrinsic states (animations, sprites) in this case so that I can conserve memory more noticeably?

\$\endgroup\$
4
  • \$\begingroup\$ Welcome. Please edit to post your code as formatted text rather than a screenshot. Our formatting guide can be found here and more info regarding the "code as text not images" policy can be found here. \$\endgroup\$ Commented Jul 19, 2024 at 20:56
  • 1
    \$\begingroup\$ In the very first place, if you intend to run high object counts, you should check out DOTS approach in Unity. The architecture will be different but it is much more efficient. \$\endgroup\$ Commented Jul 19, 2024 at 21:14
  • 1
    \$\begingroup\$ @Pikalek I modified it. \$\endgroup\$ Commented Jul 19, 2024 at 22:38
  • \$\begingroup\$ @Engineer is DOTS/ECS compatible with mobile? \$\endgroup\$ Commented Jul 19, 2024 at 22:39

1 Answer 1

4
\$\begingroup\$

When it comes to components referencing assets, then the engine is already using the "flyweight" pattern internally. When multiple SpriteRenderers render the same sprite asset, then they are already referencing one single shared copy of that sprite's texture in memory. Same with Animators and the AnimationController assets. So what you are doing here doesn't really do anything you wouldn't get if you just did it the normal way.

If you want to apply the flyweight pattern, then it makes sense to look at your own components. For example, let's take a look at this component:

public class Enemy : MonoBehavior { public int maxHp; public float movementSpeed; public float attackSpeed; public int attackDamage; public int scoreValue; public string localizedName; //... } 

Now let's assume that:

  • All these values don't change during the game
  • All enemies of the same enemy type have the same values for all these variables
  • You have only a few types of enemies, but a whole lot of them instantiated at the same time

That means that you are keeping a separate copy of all these values in memory for every single enemy. That's a lot of redundant data. In this case it can make sense to extract all these values into a public class EnemyType : ScriptableObject and have all enemies reference an EnemyType. That means that all enemies of the same type share one copy of these values.

...or does it? How much data are we actually talking here? Do we even get to 100 byte of redundant data per enemy? You would need to have a lot of enemies at the same time for this to matter.

\$\endgroup\$
2
  • \$\begingroup\$ Oh ok so basically if you have primitive data type fields that don't ever change then it's best to separate them into an intrinsic container like a ScriptableObject. Otherwise, for every class or component in Unity, Flyweight is pretty much not needed? @Philipp \$\endgroup\$ Commented Jul 19, 2024 at 22:38
  • \$\begingroup\$ @mtg Yes. But as I wrote, it's only worth the effort in some very narrow circumstances. All the usual candidates for big blobs of data, like textures or sounds, are managed by the engine. I would have to get creative to come up with a reasonable use-case for this in Unity. The usual reason why you would use ScriptableObject's in Unity is because it somehow makes your content authoring workflow easier. Saving memory is usually only a secondary concern. \$\endgroup\$ Commented Jul 20, 2024 at 6:22

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.