using System.Collections.Generic; using UnityEngine; namespace Flexalon { /// /// Sometimes, it's useful to generate child objects instead of defining them statically. /// The Flexalon Cloner can generate objects from a set of prefabs iteratively or randomly, /// and can optionally bind to a data source. /// [AddComponentMenu("Flexalon/Flexalon Cloner"), HelpURL("https://www.flexalon.com/docs/cloner")] public class FlexalonCloner : MonoBehaviour { [SerializeField] private List _objects; /// Prefabs which should be cloned as children. public List Objects { get => _objects; set { _objects = value; MarkDirty(); } } /// In which order should prefabs be cloned. public enum CloneTypes { /// Clone prefabs in the order they are assigned. Iterative, /// Clone prefabs in a random order. Random } [SerializeField] private CloneTypes _cloneType = CloneTypes.Iterative; /// In which order should prefabs be cloned. public CloneTypes CloneType { get => _cloneType; set { _cloneType = value; MarkDirty(); } } [SerializeField] private uint _count; /// How many clones should be generated. public uint Count { get => _count; set { _count = value; MarkDirty(); } } [SerializeField] private int _randomSeed; /// Seed used for the Random clone type, to ensure results remain consistent. public int RandomSeed { get => _randomSeed; set { _randomSeed = value; MarkDirty(); } } [SerializeField] private GameObject _dataSource = null; /// Can be an gameObject with a component that implements FlexalonDataSource. /// The number of objects cloned is set to the number of items in the Data property. public GameObject DataSource { get => _dataSource; set { UnhookDataSource(); _dataSource = value; HookDataSource(); MarkDirty(); } } [SerializeField, HideInInspector] private List _clones = new List(); void OnEnable() { HookDataSource(); MarkDirty(); } private void HookDataSource() { if (isActiveAndEnabled && _dataSource != null && _dataSource) { if (_dataSource.TryGetComponent(out var component)) { component.DataChanged += MarkDirty; } } } private void UnhookDataSource() { if (_dataSource != null && _dataSource) { if (_dataSource.TryGetComponent(out var component)) { component.DataChanged -= MarkDirty; } } } void OnDisable() { UnhookDataSource(); MarkDirty(); } /// Forces the cloner to regenerate its clones. public void MarkDirty() { foreach(var clone in _clones) { if (Application.isPlaying) { Destroy(clone); } else { DestroyImmediate(clone); } } _clones.Clear(); if (isActiveAndEnabled && _objects != null && _objects.Count > 0) { switch (_cloneType) { case CloneTypes.Iterative: GenerateIterativeClones(); break; case CloneTypes.Random: GenerateRandomClones(); break; } } } private IReadOnlyList GetData() { if (_dataSource != null && _dataSource) { return _dataSource.GetComponent()?.Data; } return null; } private void GenerateIterativeClones() { int i = 0; var data = GetData(); var count = data?.Count ?? (int)_count; while (_clones.Count < count) { GenerateClone(i, data); i = (i + 1) % _objects.Count; } } private void GenerateRandomClones() { var random = new System.Random(_randomSeed); var data = GetData(); var count = data?.Count ?? (int)_count; while (_clones.Count < count) { GenerateClone(random.Next(_objects.Count), data); } } private void GenerateClone(int index, IReadOnlyList data) { var clone = Instantiate(_objects[index], Vector3.zero, Quaternion.identity, transform); _clones.Add(clone); if (data != null && clone.TryGetComponent(out var dataBinding)) { dataBinding.SetData(data[_clones.Count - 1]); } } } }