0
\$\begingroup\$

I have background generation script with 2 arrays. One contains stars prefabs with sprites and another contains nebulas prefabs with sprites. Both have identical types of variables as generating parameters and use same methods for spawning. Nonetheless, it works only with stars. Nebulas don't spawn. What might be a problem?

enter image description here enter image description here enter image description here enter image description here

/* This script provides background generation. */

[System.Serializable]
public struct SpritePrefab //struct for stars and nebulas prefabs
{
    #if UNITY_EDITOR

    [HideInInspector]
    public string name;

    #endif
    public GameObject prefab;

    [Tooltip("Share of this sprite in percentages of 100% total.")]
    [Range(0f, 100f)]
    public float share;
}

//________________________________________________________________________

/* These arrays contain all prefabs with all of the sprites needed to generate background. */

[Header("Sprites arrays.")]

[Tooltip("Array of stars prefabs.")]
[SerializeField]
private SpritePrefab[] calculatedStarsPrefabs; //arrays for stars sprites for second distributing method which is DistributeProportionatelyForStars2()

[SerializeField]
[HideInInspector]
private SpritePrefab[] normalizedStarsPrefabs; //array for final use of stars prefabs. Created to avoid struggling with distributing logick when working in Inspector. 

[Tooltip("Array of nebulas prefabs.")]
[SerializeField]
private SpritePrefab[] calculatedNebulasPrefabs; //array for nebulas sprites

[SerializeField]
[HideInInspector]
private SpritePrefab[] normalizedNebulasPrefabs;

//=============================================================================

/* These values determine generating parameters such as spawning range and number of stars. */

[Header("Stars spawning parametrs.")]

[Tooltip("Stars Range to spaw.")]
[SerializeField]
private float starsSpawningRange; //spawning range used in vector2 x and y axis

[Tooltip("Number of stars to spaw.")]
[SerializeField]
private int numberOfStars; //Number of stars to spaw

//__________________________________________________

[Tooltip("Minimal star size.")]
[SerializeField]
private float minimalStarSize; //minimal star size

[Tooltip("Maximum star size.")]
[SerializeField]
private float maximalStarSize; //maximal star size

//=============================================================================

[Header("Nebulas spawning parametrs.")]

[Tooltip("Nebulas Range to spaw.")]
[SerializeField]
private float nebulasSpawningRange; //spawning range used in vector2 x and y axis

[Tooltip("Number of nebulas to spaw.")]
[SerializeField]
private int numberOfNebulas; //Number of nebulas to spaw

//__________________________________________________

[Tooltip("Minimal nebula size.")]
[SerializeField]
private float minimalNebulaSize; //minimal nebula size

[Tooltip("Maximum nebula size.")]
[SerializeField]
private float maximalNebulaSize; //maximal nebula size

//=============================================================================

private string[] sortingLayersNames = new string[6] { "S&N 1", "S&N 2", "S&N 3", "S&N 4", "S&N 5", "S&N 6"};

//=============================================================================

private void Awake()
{
    SpritePrefabsGenerating(normalizedStarsPrefabs, numberOfStars, minimalStarSize, maximalStarSize, starsSpawningRange);
    SpritePrefabsGenerating(normalizedNebulasPrefabs, numberOfNebulas, minimalNebulaSize, maximalNebulaSize, nebulasSpawningRange);
}

//=============================================================================


private void SpritePrefabsGenerating(SpritePrefab[] spritesArray, int numberOfInstances, float minPrefabSize, float maxPrefabSize, float spawningRange)
{
    int indexInArray = 0; //this variable counts type of star

    // loops until i == share of prefabs in persentages multiplied by number of instances divided by 100(%) to get total number of particular type of stars

    for (int j = 0; j < spritesArray.Length; j++) //loops through prefabs array length
    {
        for (int i = 0; i < spritesArray[indexInArray].share * numberOfInstances / 100; i++) //loops until 
        {
            InstantiateSpritePrefab(spritesArray, indexInArray, minPrefabSize, maxPrefabSize, spawningRange); //instantiates prefab
        }
        indexInArray++; //updates variable to get another type of prefab in array
    }
}

private void InstantiateSpritePrefab(SpritePrefab[] spritesArray, int index, float minPrefabSize, float maxPrefabSize, 
    float spawningRange) //receives index to get prefab from array and instantiate it
{
    var spritePrefab = Instantiate(spritesArray[index].prefab,
        new Vector3(Random.Range(-spawningRange, spawningRange), // define spawning range along x axis
            Random.Range(-spawningRange, spawningRange), 100f), // define spawning range along y axis
            Quaternion.identity);

    float randomsizeSize = Random.Range(minPrefabSize, maxPrefabSize); //defines size variety
    spritePrefab.transform.localScale = new Vector2(randomsizeSize, randomsizeSize); //randomizing size variety

    //assigning sprite in prefab to random sorting layer
    spritePrefab.GetComponent<SpriteRenderer>().sortingLayerName = sortingLayersNames[Random.Range(0, sortingLayersNames.Length)]; 
}

//=============================================================================

#if UNITY_EDITOR
private void OnValidate()
{
    DistributeProportionatelyForSpritesPrefabs(calculatedStarsPrefabs, normalizedStarsPrefabs);
    DistributeProportionatelyForSpritesPrefabs(calculatedNebulasPrefabs, normalizedNebulasPrefabs);
}
#endif



#if UNITY_EDITOR


void DistributeProportionatelyForSpritesPrefabs(SpritePrefab[] calculatedArray, SpritePrefab[] normalizedArray) // When the array is modified, update all the entries' names, and total up the weight accounted for.
{
    float totalWeight = 0; //initiates total share as 0

    for (int i = 0; i < calculatedArray.Length; i++)
    {
        var localPrefabStruct = calculatedArray[i]; //assigning actual prefab struct to local variable
        if (localPrefabStruct.prefab == null)
            continue;

        localPrefabStruct.name = localPrefabStruct.prefab.name; //asigning actual prefab name to name field in struct
        calculatedArray[i] = localPrefabStruct; //reasigning same prefab struct with name
        totalWeight += localPrefabStruct.share; //adds prefabs share to totla share
    }

    if (totalWeight == 0f)
    {
        return;
    }


    float shareMultiplier = 100f / totalWeight; //get number for multiplying

    if (normalizedArray == null || normalizedArray.Length != calculatedArray.Length) // Create/resize the normalized version.
        normalizedArray = new SpritePrefab[calculatedArray.Length];

    // Fill this array with copies of the original,
    // reweighted to add up to 100
    for (int i = 0; i < calculatedArray.Length; i++)
    {
        var localPrefabStruct = calculatedArray[i];
        if (localPrefabStruct.prefab == null) 
            localPrefabStruct.share = 0f;

        localPrefabStruct.share *= shareMultiplier; //keeps total share within 100.
        normalizedArray[i] = localPrefabStruct; // filling final array
    }
}
#endif
\$\endgroup\$
12
  • \$\begingroup\$ Do the nebulae come up in the Hierarchy? My gut feel is they're spawning, alright... or you'd be faced with "cannot instantiate null prefab" type errors jamming up your console logs. Could it be scale, or something else, that's just preventing them from displaying? \$\endgroup\$
    – Engineer
    Mar 26, 2018 at 15:26
  • \$\begingroup\$ What happens if you switch the arrays - i.e. put the nebulas in stars array? \$\endgroup\$
    – wondra
    Mar 26, 2018 at 15:27
  • \$\begingroup\$ I put one nebula prefab in stars array. it works alright. Problem is not with prefab apparently. \$\endgroup\$ Mar 27, 2018 at 5:24
  • \$\begingroup\$ No they don't appear in the Hierarchy. \$\endgroup\$ Mar 27, 2018 at 5:25
  • \$\begingroup\$ I added int totalCount which ++ each instantiate() of prefab. And debug.log when generation is finished. In stars case, it gets 7004 though I set a total number of stars as 7000. With nebulas, it is 0. \$\endgroup\$ Mar 27, 2018 at 5:47

1 Answer 1

1
\$\begingroup\$

This looks like a refactoring error.

The original example code used just one set of prefabs, with two member variables for the editable and normalized arrays, respectively.

When the editable array changed in size, the normalized array member variable was updated to point to a new array of the same size to match.

When you refactored this to work with two pairs of editable & normalized arrays, you packaged up the normalizing behaviour into a method that could be called on each, and the resizing logic took on a slightly different form:

void DistributeProportionatelyForSpritesPrefabs(
    SpritePrefab[] calculatedArray, 
    SpritePrefab[] normalizedArray ) 
{

// ...

if (normalizedArray == null || normalizedArray.Length != calculatedArray.Length) // Create/resize the normalized version.
    normalizedArray = new SpritePrefab[calculatedArray.Length];

Note that the normalizedArray we're assigning here is not a member variable. It's an argument to the function. That means that if we change which array it's pointing to (by creating a new one of the correct size), we haven't changed which array the original member variable normalizedStarPrefabs or normalizedNebulaPrefabs is pointing to. So the rest of our code still sees the old array.

This is solved easily enough by using the ref keyword:

void DistributeProportionatelyForSpritesPrefabs(
    SpritePrefab[] calculatedArray, 
    ref SpritePrefab[] normalizedArray ) 

This forces the argument to refer to the variable we pass, so if we assign a new array into it, our member variable gets that new array.

We need to add the keyword where we call the method too, to show clearly what's going to happen to the variable we're passing in:

DistributeProportionatelyForSpritesPrefabs(
    calculatedStarsPrefabs,
    ref normalizedStarsPrefabs);
DistributeProportionatelyForSpritesPrefabs(
    calculatedNebulasPrefabs, 
    ref normalizedNebulasPrefabs);

Or, another way to solve this is to encapsulate the behaviour of these paired arrays in their own type, eg...

[System.Serializable]
public struct NormalizedPrefabList : IEnumerable<SpritePrefab> {
    [SerializeField]
    [HideInInspector]
    private SpritePrefab[] _normalizedPrefabs;

    public SpritePrefab this[int index] {
        get { return _normalizedPrefabs[index]; }
    }

    public IEnumerator<SpritePrefab> GetEnumerator() {
        return (IEnumerator<SpritePrefab>)_normalizedPrefabs.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return _normalizedPrefabs.GetEnumerator();
    }

#if UNITY_EDITOR
    [SerializeField]
    private SpritePrefab[] _editablePrefabs;

    // Call this in OnValidate()
    public void Renormalize() {
        // Put the renormalizing method here. No arguments needed - act on "this"
    }
#endif
}

That way the rest of your code can just treat the whole thing as an array, accessing its normalized entries with [indexing] or foreach, and doesn't have to worry about coordinating its internals, beyond calling Renormalize() in OnValidate() when something might have changed in the editor.

\$\endgroup\$
1
  • \$\begingroup\$ It worked! Brilliantly! :) \$\endgroup\$ Mar 31, 2018 at 8:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .