iOS does NOT care about fields set in the inspector

Hey !

I’ve noticed some really strange and boring thing, whenever I set some MaxHP float field in the inspector, it works exactly as intended in the Editor and in android builds, but on iOS builds, all my different objects that share the same script but with different values of MaxHP set in the hierarchy have the same MaxHP in build.
Some goes got GameObject, Mesh, Material fields, just any fields I guess.

Has anyone already encountered such thing ?

Nope. Do you have any #ifdef’s in your code?

Nope not at all

That’s weird. May you post some examples of declaration and usages of your MaxHP field?

The behaviour you described is static variable behaviour. But I do not uderstand how var may be static in one build and instance in another, without #ifdef

Heres my code (the singleton was created later, I used to store those variables inside the script but I was like, maybe if I put it at one place it’s better, I don’t know) :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Sirenix.OdinInspector;


public class FXPoolManager : MonoBehaviour
{
    public static FXPoolManager Instance = null;

    [SerializeField, Required, TabGroup("Variables")]
    WaitForSecondsVariable waitForExplosion = null;

    [SerializeField, Required, TabGroup("Pools")]
    ParticleSystemPoolVariable explosionFXPool = null;
    [SerializeField, Required, TabGroup("Pools")]
    ParticleSystemPoolVariable smallExplosionFXPool = null;
    [SerializeField, Required, TabGroup("Pools")]
    SpriteRendererArrayVariable burnedSprites = null;

    [SerializeField, Required, TabGroup("Trees")]
    Mesh[] burnedTreeMeshes = null;
    [SerializeField, Required, TabGroup("Trees")]
    Material burnedTreeMaterial = null;

    public WaitForSecondsVariable WaitForExplosion { get { return waitForExplosion; } }

    public ParticleSystemPoolVariable ExplosionFXPool { get { return explosionFXPool; } }
    public ParticleSystemPoolVariable SmallExplosionFXPool { get { return smallExplosionFXPool; } }
    public SpriteRendererArrayVariable BurnedSprites { get { return burnedSprites; } }

    public Mesh[] BurnedTreeMeshes { get { return burnedTreeMeshes; } }
    public Material BurnedTreeMaterial { get { return burnedTreeMaterial; } }



    void Awake ()
    {
        if (Instance == null)
            Instance = this;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using Sirenix.OdinInspector;


public class Building : MonoBehaviour, IDestroyable
{
    [SerializeField, Required, TabGroup("Values")]
    FloatVariable buildingMaxHealth = null, buildingExplosionSize = null;

    public float? Health { get { return health; } }
    public float MaxHealth { get { return GetMaxHealth(); } }
    public int Value { get { return MaxHealth == 0 ? 1 : (int)buildingMaxHealth.Value; } }
    public bool IsDead { get { return false; } }
    public Vector3 Position { get { return transform.position; } }


    float? health = null;
    ParticleSystem explosion = null;
    ParticleSystem smallExplosion = null;
    Coroutine smallExplosionRoutine = null;
    SpriteRenderer burnedSprite = null;
    Vector3 spritePosition = Vector3.zero * .2f;



    float GetMaxHealth ()
    {
        return buildingMaxHealth == null ? 0 : buildingMaxHealth.Value;
    }

    float GetExplosionSize ()
    {
        return buildingExplosionSize == null ? 2 : buildingExplosionSize.Value;
    }

    public bool Attack (float _damage, Vector3 _direction)
    {
        if (!health.HasValue)
            health = MaxHealth;

        health -= _damage;

        if (health <= 0)
        {
            Kill(_direction);
            return true;
        }

        return false;
    }

    public void Kill (Vector3 _direction)
    {
        if (FXPoolManager.Instance.BurnedSprites != null)
        {
            for (int i = 0; i < FXPoolManager.Instance.BurnedSprites.Items.Length; i++)
            {
                burnedSprite = Instantiate(FXPoolManager.Instance.BurnedSprites.Items[Random.Range(0, FXPoolManager.Instance.BurnedSprites.Items.Length)]);
                spritePosition.x = transform.position.x;
                spritePosition.z = transform.position.z;
                burnedSprite.transform.position = spritePosition;
            }
        }

        if (FXPoolManager.Instance.ExplosionFXPool != null)
        {
            explosion = FXPoolManager.Instance.ExplosionFXPool.Pick();
            explosion.transform.localScale = Vector3.one * GetExplosionSize();
            explosion.transform.position = transform.position + Vector3.up * 2;
            explosion.Play();
            explosion.gameObject.AddComponent<ParticleDestroyer>().DestroyParticle(FXPoolManager.Instance.ExplosionFXPool, explosion);
        }

        GetComponent<BoxCollider>().enabled = false;

        _direction.Normalize();
        Vector3 direction = new Vector3(_direction.x * 10, 10, _direction.z * 10);
        transform.DOMove(transform.position + direction, 1.5f).SetEase(Ease.OutQuad).OnComplete(() => Destroy(gameObject));
        StartCoroutine(Rotate(_direction));
    }

    IEnumerator SmallExplosion ()
    {
        smallExplosion = FXPoolManager.Instance.SmallExplosionFXPool.Pick();
        smallExplosion.transform.localScale = Vector3.one;
        smallExplosion.transform.position = transform.position + Vector3.up;
        smallExplosion.Play();
        smallExplosion.gameObject.AddComponent<ParticleDestroyer>().DestroyParticle(FXPoolManager.Instance.SmallExplosionFXPool, smallExplosion);

        yield return FXPoolManager.Instance.WaitForExplosion.Wait;
        smallExplosionRoutine = null;
    }

    IEnumerator Rotate (Vector3 _direction)
    {
        float time = 1.5f;
        int randomSpeed = Random.Range(200, 600);
        while (time > 0)
        {
            transform.Rotate(_direction * Time.deltaTime * randomSpeed);
            time -= Time.deltaTime;
            yield return null;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using Sirenix.OdinInspector;


public class Tree : MonoBehaviour, IDestroyable
{
    public float? Health { get { return health; } }
    public float MaxHealth { get { return maxHealth; } }
    public int Value { get { return 1; } }
    public bool IsDead { get { return false; } }
    public Vector3 Position { get { return transform.position; } }


    float maxHealth = 0;
    float? health = null;
    ParticleSystem explosion = null;
    ParticleSystem smallExplosion = null;
    Coroutine smallExplosionRoutine = null;
    SpriteRenderer burnedSprite = null;
    Vector3 spritePosition = Vector3.zero * .2f;



    public bool Attack (float _damage, Vector3 _direction)
    {
        if (!health.HasValue)
            health = maxHealth;

        health -= _damage;

        if (health <= 0)
        {
            Kill(_direction);
            return true;
        }
        if (smallExplosionRoutine == null)
            smallExplosionRoutine = StartCoroutine(SmallExplosion());
        return false;
    }

    public void Kill (Vector3 _direction)
    {
        GetComponent<BoxCollider>().enabled = false;
        GetComponent<MeshFilter>().mesh = FXPoolManager.Instance.BurnedTreeMeshes[Random.Range(0, FXPoolManager.Instance.BurnedTreeMeshes.Length)];
        GetComponent<MeshRenderer>().material = FXPoolManager.Instance.BurnedTreeMaterial;

        if (FXPoolManager.Instance.BurnedSprites != null)
        {
            for (int i = 0; i < FXPoolManager.Instance.BurnedSprites.Items.Length; i++)
            {
                burnedSprite = Instantiate(FXPoolManager.Instance.BurnedSprites.Items[Random.Range(0, FXPoolManager.Instance.BurnedSprites.Items.Length)]);
                spritePosition.x = transform.position.x;
                spritePosition.z = transform.position.z;
                burnedSprite.transform.position = spritePosition;
            }
        }

        if (FXPoolManager.Instance.ExplosionFXPool != null)
        {
            explosion = FXPoolManager.Instance.ExplosionFXPool.Pick();
            explosion.transform.localScale = Vector3.one * Random.Range(4, 6);
            explosion.transform.position = transform.position + Vector3.up * 3;
            explosion.Play();
            explosion.gameObject.AddComponent<ParticleDestroyer>().DestroyParticle(FXPoolManager.Instance.ExplosionFXPool, explosion);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using Sirenix.OdinInspector;


public class Fields : MonoBehaviour, IDestroyable
{
    [SerializeField, Required]
    GameObject[] objectsToDestroy = null;

    public float? Health { get { return health; } }
    public float MaxHealth { get { return maxHealth; } }
    public int Value { get { return 1; } }
    public bool IsDead { get { return false; } }
    public Vector3 Position { get { return transform.position; } }


    float maxHealth = 0;
    float? health = null;
    SpriteRenderer burnedSprite = null;
    Vector3 spritePosition = Vector3.zero * .2f;
    ParticleSystem explosion = null;



    public bool Attack (float _damage, Vector3 _direction)
    {
        if (!health.HasValue)
            health = maxHealth;

        health -= _damage;

        if (health <= 0)
        {
            Kill(_direction);
            return true;
        }
        return false;
    }

    public void Kill (Vector3 _direction)
    {
        GetComponent<BoxCollider>().enabled = false;

        if (FXPoolManager.Instance.BurnedSprites != null)
        {
            for (int i = 0; i < FXPoolManager.Instance.BurnedSprites.Items.Length; i++)
            {
                burnedSprite = Instantiate(FXPoolManager.Instance.BurnedSprites.Items[Random.Range(0, FXPoolManager.Instance.BurnedSprites.Items.Length)]);
                spritePosition.x = transform.position.x;
                spritePosition.z = transform.position.z;
                burnedSprite.transform.position = spritePosition;
            }
        }

        if (FXPoolManager.Instance.ExplosionFXPool != null)
        {
            explosion = FXPoolManager.Instance.ExplosionFXPool.Pick();
            explosion.transform.localScale = Vector3.one * 2;
            explosion.transform.position = transform.position + Vector3.up;
            explosion.Play();
            explosion.gameObject.AddComponent<ParticleDestroyer>().DestroyParticle(FXPoolManager.Instance.ExplosionFXPool, explosion);
        }

        for (int i = 0; i < objectsToDestroy.Length; i++)
            Destroy(objectsToDestroy[i]);
    }
}

Building doesnt work for health, they all get destroyed instantly like they have 0 maxHP but only some of them do (set in the inspector).
Tree works fine.
Fields doesn’t destroy objectsToDestroy.
All of them spawns the burnedSprites and the explosionFX.

And this behavior only happens on iOS. Works fine on editor and android.

I have also switched platform on the editor to iOS but the behavior is exactly the same as in android platform and android build. The problem only comes from the build in iOS.

I really have no clue about this, plus I’ve asked around and nobody seems to already have encountered something like that in the past.

Does the iOS build have another scene loaded before the scene where you’re changing the values? Could the singleton be surviving from the previous scene, with the values it had there?

@StarManta
There is only one scene included in the build. And the singleton only has references to scriptable objects and mesh / material that do not change. It’s like a database.

Have you tried different unity version? Also, if problem happens with FloatVariable buildingMaxHealth, then inject

Debug.Log($"{gameObject.name} {gameObject.GetInstanceID()}:{buildingMaxHealth?.GetInstanceID()}");

into Building.GetMaxHealth method and check iOS player logs if it gives different ids or the same for different buildings? It will output two numbers like those
someObjectName 1234355325:234325345

@palex-nx
I don’t really understand, what is that for exactly ?

I should also mention something, I have files like this that has serializedfield or public fields inside a #if, but as the whole class is inside, it never caused me any problem in the past, but maybe, I don’t know, everyone’s a suspect (what the class does doesn’t matter) :

#if UNITY_EDITOR

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Sirenix.OdinInspector;


public class ObjectToPrefab : MonoBehaviour
{
    public List<GameObject> allObjects = new List<GameObject>();

    List<Transform> allPositions = new List<Transform>();


    [Button]
    public void Replace (GameObject _prefab, string _parentName)
    {
        GameObject parent = new GameObject();
        parent.name = _parentName;

        allPositions.Clear();
        for (int i = 0; i < allObjects.Count; i++)
        {
            allPositions.Add(allObjects[i].transform);
            allObjects[i].SetActive(false);
        }

        for (int i = 0; i < allPositions.Count; i++)
        {
            GameObject go = PrefabUtility.InstantiatePrefab(_prefab) as GameObject;
            go.transform.position = allPositions[i].transform.position;
            go.transform.rotation = allPositions[i].transform.rotation;
            go.transform.localScale = allPositions[i].transform.localScale;
            go.transform.SetParent(parent.transform);
        }
        allObjects.Clear();
    }

    [Button]
    public void TurnOn ()
    {
        for (int i = 0; i < allObjects.Count; i++)
        {
            allObjects[i].SetActive(true);
        }
    }

    [Button]
    public void TurnOff()
    {
        for (int i = 0; i < allObjects.Count; i++)
        {
            allObjects[i].SetActive(false);
        }
    }
}

#endif

This is for checking if you source references point to same or different values. If value is always the same, then your error is misconfig.

This is not the problem we’re looking for. Such if makes code what works in editor only. It can’t influence player builds. I asked initially about things like

#if UNITY_IOS
#if UNITY_ANDROID

and such

Well, as those assets that can’t be find in iOS never changes at runtime, it would be normal for them to always be the same no ?

And yea I knew you were talking about that but I previously had troubles with serialized fields being inside a #if, but at that moment the whole class was not in the #if. So I don’t know actually.

No. I mean to check if different GOs reference same FloatVariable when they should reference different assets.

Oh, okay I didnt understand. I’m not sure I’ll have the time to do this kind of debug though unfortunately. I’m a bit in a hurry and I think I’m just gonna duplicate scripts to make it work fast, no regards on the code being clean. It takes me 2 hours everytime to have a new build iteration for iOS.