I’m trying to instantiate a prefab from a scriptable object, and I’m using a property to set its speed and the time before it’s destroyed. The debug log shows me that the fields are being set properly, but their values are reset to their default values as soon as the script starts running the Update function.
Weirdly, it works just fine when I’ve got the relevant fields set to public (which defeats the entire point of using properties). The field doesn’t seem to be referenced by any other scripts (I’ve checked), so I really have no idea why that would make a difference.
Anyone have any idea what’s happening here? Thanks!
The code for the script on the projectile itself controlling its movement and lifetime:
public class ProjectileController : MonoBehaviour
{
private float speed = 10f; //speed of the projectile
private float lifetime = 15f; //time until projectile is destroyed
public float Speed
{
get { return speed; }
set
{
speed = value;
Debug.Log("Projectile Controller speed:" + speed);
}
}
public float Lifetime { get { return lifetime; } set { lifetime = value; } }
private void OnEnable()
{
StartCoroutine(DestroyProjectileAfterTime());
}
IEnumerator DestroyProjectileAfterTime()
{
yield return new WaitForSeconds(lifetime);
Destroy(gameObject);
}
// Update is called once per frame
void Update()
{
Debug.Log("speed: " + speed);
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
}
}
The code for the script calling and instantiating the projectile:
public class BasicProjectile : Ability
{
public GameObject projectile; // the projectile's prefab
public float projectileSpeed = 100f;
public float projectileLife = 3f;
private Transform launcher; // projectile's spawn location
ProjectileController projectileController; // the projectile's movement script
//run in ability slot's start loop
public override void Initialize(GameObject obj)
{
launcher = obj.transform.Find("FirePoint").transform;
projectileController = projectile.GetComponent<ProjectileController>();
}
//set the projectile's speed and lifetime then instantiate it
public override void Activate()
{
projectileController.Speed = projectileSpeed;
projectileController.Lifetime = projectileLife;
Instantiate(projectile, launcher.position, launcher.rotation);
}
}
If I’m reading you right, the public GameObject projectile is your prefab that’s been assigned to a scriptable object, correct?
If so then my wild guess is that accessing the field by making it public is probably updating the serialized data, while accessing it via the property isn’t working quite the same. And when instantiating, it runs off the serialized data when making the copy. Just a wild guess though.
That said if you’re wanting to change the values of an instantiated object at run time, you should instantiate first, grab the component, then change what you need, like so:
For your wild guess - it definitely sounds pretty plausible. The weird thing is though, I was still accessing the field through the property even after I made it public (since I was just trying everything to make it work at that point). Would your guess still be applicable in that case?
But nice! That worked! The only worry I have is that I’ve read that I should try to minimize the amount of times I call GetComponent, so that’s what I was trying to do in my original code. In your experience, is it really the performance hit it’s sometimes made out to be, or…?
Honestly, no idea! I don’t think I’ve tried to manipulate values in the way you have before so I’ve no real experience as to why that was happening.
Just to double check, when they were private did they have SerializeField attributes on them as well?
Depends on a number of things. As far as I know, GetComponent in itself is quite efficient and were it not, Unity honestly wouldn’t work the way it does. Nowadays we have TryGetComponent too, which means we don’t have to perform a costly null check half the time, which is a lot more expensive an operation to run than GetComponent itself. If anything Instantiate is the most expensive operation of the ones in that code.
In anything performance related, don’t try to excessively pre-empt any potential performance bottle-necks. There’s no point in optimising anything that won’t actually be a performance bottleneck down the line, as it just makes your code less readable. Focus on getting it working - and be readable - and should you have performance issues use the Profiler to pin down what exactly is causing it.
Additionally, if you are making a shooter and are concerned about performance related to instantiating lots of bullets, I would look at the Object Pool pattern.
No, they didn’t. Just for reference so you maybe don’t need to scroll back up, the fields that weirdly worked when changed to public were these ones,
private float speed = 10f; //speed of the projectile
private float lifetime = 5f; //time until projectile is destroyed
public float Speed
{
get { return speed; }
set
{
speed = value;
Debug.Log("Projectile Controller speed:" + speed);
}
}
public float Lifetime { get { return lifetime; } set { lifetime = value; } }
Thanks for the advice and solution! I was already planning on looking into Object Pooling soon, but everything else was pretty helpful and I definitely feel like I’ve learned some stuff
That would be why. Unity will serialize public fields, so modifying the values externally will at least be modifying them in a way that Unity can read later. Private fields with no [SerializeField] attribute will not be serialized.
Alongside that, fields with an inline declaration (eg: private float speed = 10f; ) live silently in the class’s constructor. So when ever you instantiate an Object, it seems that it’ll use the inline values of any non-serialized data.
No doubt if you set the fields to private decorated with a SerializeField attribute, doing it the way you were initially would then also work.
Huh… yep, reverting the changes and adding [SerializeField] also worked. I would not have expected that to be how it worked. But hey, at least now I know. Thanks again!
You can reduce the number of GetComponent calls by declaring your prefab variable as “ProjectileController” instead of GameObject. That way you get the instantiated component reference automatically. So declare your property variable like this:
public ProjectileController projectile;
When you instantiate your prefab you can simply do:
You really shouldn’t mess with the values in the prefab before you instantiate it. Such changes would persist in the editor inside the prefab (at least for values that are serialized).
And that’s really good to know. Reading over the thread again, it looks like spiney hinted at this when he said that I should instantiate objects first if I’m planning to mess with their values at runtime, but I’m glad you explicitly stated the reason why since I hadn’t even thought of that. I’ll keep that in mind, thanks!