Every time I make a new Script in Unity, I always end up doing a bunch of checks for any components my script depends on, like:
SpriteRenderer sr = gameObject.GetComponent<SpriteRenderer>();
if(sr == null)
sr = gameObject.AddComponent<SpriteRenderer>() as SpriteRenderer;
So I decided to play around and make a generic method to do this with any Component, and I came up with the following code, which I simply added right to my own new Script.
T GetOrAddComponent<T>() where T : Component
{
T component = gameObject.GetComponent<T>();
if (component == null)
component = gameObject.AddComponent<T>() as T;
return component;
}
I've tried it and the function works well, but I'm wondering if there is any major downside to this approach, or if there is some other better way to complete the same task, possibly using a existing method I am unaware of or by creating the method in some other way.
The GetComponent method is actually rather taxing on the system. If you are developing for mobile, too many of them can cause a noticeable lag in load times.
I personally use RequireComponent method. Then the script would automatically add the component the moment it's added to the GameObject. Then I would manually drag the name of the component to the field on the script object in the inspector.
Example:
using UnityEngine;
// PlayerScript requires the GameObject to have a RigidBody component
[RequireComponent (typeof (RigidBody))]
public class PlayerScript : MonoBehavior {
[SerializeField]
private RigidBody rb;
...
}
First i recommend you to use "RequireComponent" :
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(AnotherComponent))]
public class NetworkService : MonoBehaviour
{
private SpriteRenderer _sRenderer;
//strong way to never get a null reference.
#if UNITY_EDITOR
private void OnValidate()
{
if (null == _sRenderer)
_sRenderer = GetComponent<_sRenderer>();
}
#endif
the "OnValidate" method will be executed every time you make a change in the value of a parameter in the editor or load the script/component.
You could do it all in one if statement if you'd like:
SpriteRenderer sr;
if(gameObject.GetComponent<SpriteRenderer>())
sr = gameObject.GetComponent<SpriteRenderer>();
else
sr = gameObject.AddComponent<SpriteRenderer>() as SpriteRenderer;
You will potentially be making the GetComponent call twice, but if you like the code structure better so be it. I personally think the way you are doing it is best though.
Though to be even more concise, you can return the component directly instead of having to store it in the variable component
public T GetOrAddComponent<T>() where T : Component
{
if (gameObject.GetComponent<T>())
return gameObject.GetComponent<T>();
else
return gameObject.AddComponent<T>() as T;
}
And as long as you are doing this at scene load or in the start function, there really won't be any performance difference.
public static void GetOrAddComponent<T>(this GameObject model, Action<T> onSucess) where T: Component {
onSucess(model.GetComponent<T>() ?? model.AddComponent<T> () as T);
}
In the new version of Unity (With C# 8.0 and above) you can simply do this in one line.
[SerializeField] private SpriteRenderer spriteRenderer;
public SpriteRenderer GetSpriteRenderer => spriteRenderer ??= GetComponent<SpriteRenderer>() ?? gameObject.AddComponent<SpriteRenderer>();
The code above does three things.
Returns private local spriteRenderer component from the getter property if it's not null.
If spriteRenderer is null, then it proceed to assign the value by calling GetComponent() before returning.
If GetComponent() returns null, then the next operation will add sprite renderer component to the game object and assign to spriteRenderer before returning.
Here's an example using an extension method:
public static class MonoBehaviourGetOrAddComponent {
public static T GetOrAddComponent<T>(this GameObject self) where T : Component
{
T component = self.GetComponent<T>();
return component != null
? component : self.AddComponent<T>() as T;
}
}
Related
I'm new to programming and I'm currently trying to do a simple game in Unity.
The code makes some things dissapear when they touch the ground and it works well but the "Evaporated" variable does not update in the Unity Inspector. When something touch the ground, evaporated should be incremented, but it stays at 0.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class EnemyEvaporate : MonoBehaviour
{
public GameObject Enemy;
public int Evaporated;
void OnCollisionEnter(Collision CollisionInfo)
{
if (CollisionInfo.gameObject.name == "Map" || CollisionInfo.gameObject.name == "Player")
{
Thread.Sleep(15);
gameObject.SetActive(false);
Evaporated++;
}
}
}
I'm not really sure what's the behavior you're trying to get in this script. If Evaporated is some kind of 'Global counter' for score, it need to be written different and not store inside script for this gameObject.
It might be related to your gameObject.SetActive(false) which will deactivate your object, so in fact it will disables also the scripts connected with it (and especially this one. SetActive documentation) and removes them from Update() for a given gameObject (so given enemy. If you have 4 Enemy objects on the Scene, in fact you have 4 instances of this script and each has own Evaporated variable)
Why won't you move the void OnCollisionEnter(Collision CollisionInfo) to your Enemy scipt? I think Enemy should now if it collided with anything, not some external script.
You can also use tags instead of names when detecting Collisions.
Also, gameObject.SetActive(false) - if you set your gameObject to disabled, nothing more from its script will happen unless you set it to active again. It e.g. cancels all Coroutine. The gameObject is just "sleeping", I would say. It's not destroyed - you still can see it in your Hierarchy window - but it can't do anything. (By the way, in this case you're setting EnemyEvaporate gameObject as disabled, not Enemy gameObject.)
Also, you can use Coroutine instead of Thread.Sleep(15). It is more common to use Coroutines than Threads in Unity.
Now, Evaporation. If you want to count how many Enemies evaporated (I suppose, looking at Evaporated++;) you should have some external object to count it. The simplest way I can propose fow now is creating a GameObject with EvaporationCounter : MonoBehaviour script attached to it. You can also use Singleton pattern to be sure there's only one object of this type.
public class EvaporationCounter : MonoBehaviour
{
private int evaporates;
public int Evaporates {
get { return evaporates; }
private set
{
if (value >= 0) evaporates = value;
}
}
public void AddEvaporation()
{
Evaporates++;
}
}
Your Enemy class could look like:
public class Enemy : MonoBehaviour
{
private IEnumerator coroutine;
private EvaporationCounter evaporationCounter;
private void Start()
{
evaporationCounter = FindObjectOfType<EvaporationCounter>();
}
private IEnumerator WaitAndSetInactive(float waitTime)
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(false);
}
private void OnCollisionEnter(Collision CollisionInfo)
{
if (!CollisionInfo.gameObject.CompareTag("Map") && !CollisionInfo.gameObject.CompareTag("Player")) return;
if (evaporationCounter != null)
evaporationCounter.AddEvaporation();
StartCoroutine(WaitAndSetInactive(15f));
}
}
Calling evaporationCounter.AddEvaporation() from the Enemy script is not the best solution, since it does not apply to the Dependency Inversion principle but I would say it's good for the beginning with Unity.
I'm trying to learn how to use Unity and following online tutorials but I am currently having a problem that I don't understand how to fix.
I have a Sprite in my scene and I have attached a script to it however in the Inspector it shows the script is there but I cannot see the variables inside? I had this problem previously and it sorted itself out.
What is the cause of this problem/how do I fix it?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpaceShip : MonoBehaviour {
public float speed = 30;
public GameObject theBullet;
private void FixedUpdate()
{
float horzMove = Input.GetAxisRaw("Horizontal");
GetComponent<Rigidbody2D>().velocity = new Vector2(horzMove, 0) *
speed;
}
// Update is called once per frame
void Update () {
if (Input.GetButtonDown("Jump"))
{
Instantiate(theBullet, transform.position, Quaternion.identity);
}
}
}
Edit: The problem was solved by reimporting.
You either need to declare the variables as Public or [SerializeField] for member variables to appear in the inspector. Note that by declaring something as public allows access to the variable from outside the class (from other scripts/classes for example). By default, private is assigned to member variables.
Example:
public class testscript : MonoBehaviour
{
public int foo; // shows up in inspector
[SerializeField] private int bar; // also shows up while still being private
void Start()
{
}
}
Not is a problem, You forget to do something surely.
It is common at first with Unity.
Start again.
In the scene create a new GameObject and add you script.
If the inspector shows not variable:
The varible do not is public (false, if is public in you script)
There is some syntax error in the script!
or
You were not adding the correct script to the GameObject.
There are not many secrets to that, if all is well enough that the variable is public and this outside of a method of the script so that it is seen in the inspector.
One tip, do not use a GetComponent or Instantiate inside a FixedUpdate or Update because they are expensive, save the Rigidbody2D in a variable in the Start and then use it.
Sorry for my English and good luck.
So im trying to change a variable in another script by touching a cube.
Current setup
1x Player
1x Enemy
Each with their own script Enemy_Stats & Character_Stats
As you can see in this little snippet it's quite a workaround to access the variable from another script.
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Enemy")
{
collision.gameObject.GetComponent<Enemy_Stats>().Health =
collision.gameObject.GetComponent<Enemy_Stats>().Health
- gameObject.GetComponent<Character_Stats>().AttackDamage;
if (collision.gameObject.GetComponent<Enemy_Stats>().Health <= 0)
{
Destroy(collision.gameObject);
}
}
}
Iam new to Unity, but isn't there a way to just refer it with something like:
collision.Health?
How to access variables/functions from another Class. The variable or function you want to access or called must be public not private.
public class ScriptA : MonoBehaviour{
public int playerScore = 0;
void Start()
{
}
public void doSomething()
{
}
}
Access variable playerScore in ScriptA from ScriptB. First, find the GameObject that the script or component is attached to with the GameObject.Find function then use the GetComponent function to retrieve that script or component that is attached to it.
public class ScriptB : MonoBehaviour{
ScriptA scriptInstance = null;
void Start()
{
GameObject tempObj = GameObject.Find("NameOfGameObjectScriptAIsAttachedTo");
scriptInstance = tempObj.GetComponent<ScriptA>();
//Access playerScore variable from ScriptA
scriptInstance.playerScore = 5;
//Call doSomething() function from ScriptA
scriptInstance.doSomething();
}
}
No since Health is not part of the collision object, but Enemy_Stats. You can cache a Component (that's what Enemy_Stats is) if you use it multiple times to save you some typing (and some performance, but that is rather marginal for this example). Also you can cache "known" components like in this case Player_Stats. You can do this e.g. in Start or with a public variable and the inspector.
What you should probably do though is to make the enemy be responsible for his life and not the player, so move the Destroy-part to Enemy_Stats (into the Health property to be exact).
The first thing to make this shorter (and eventually faster) would be to store this: gameObject.GetComponent<Character_Stats>() on Start() in a private variable (you should avoid calling GetComponent on a frequent basis if you can avoid it).
For the Health variable, a way of avoiding GetComponent calls could be caching: you create a Dictionary<GameObject, Enemy_Stats> and read from that as soon as this gameobject collided once
At the very beginning i mean in Awake() method you can find a game-object with tag
and get it's Heath after that in Collision() method you should just decrease the health but, here the condition is there is only one enemy and only one player.
I have the object that have the script component.
public class TeleportReference : MonoBehaviour {
private GameObject reference;
void Start () {
reference = null;
}
public void SetReference(GameObject other){
reference = other;
}
public GameObject GetReference(){
return reference;
}
Now if I search for an object and test the script variables
GameObject test = GameObject.FindGameObjectWithTag("Water");
print(test);
print(test.GetComponent<TeleportReference>());
print(test.GetComponent<TeleportReference>().GetReference());
it works just fine and GetReference() return the variable I stored.
But now if I use it whithin OnTriggerEnter2D
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Water")
{
print(other);
print(other.gameObject);
print(other.GetComponent<TeleportReference>());
print(other.GetComponent<TeleportReference>().GetReference());
}
}
GetReference() return null (or other variable that I used during Start() of TeleportReference class).
All other testing outputs remain the same.
Could anyone give a hint why this could happen? Does that mean that GetComponent created new instance of TeleportReference in second case?
GetComponent did not create a new instance of TeleportReference in second case. I have similar code in one of my projects and I haven't had any problems. So in this case I would look to see if the problem is somewhere else. Are you sure it's the same "Water" object? Do you have multiple objects with "Water" tag? Are you colliding before reference is assigned? There could be a myriad of things going on. Just test to narrow it down.
For example try performing an action on the other.gameObject object to verify it's the right object, like deactivating it. Also try using other.gameObject.GetComponent (not sure if this makes a difference).
Found the issue. It was in the order of assigning variables. That is - changes were made apperantly before Start() function was called. Thus at some point of the code the stored value was overritten back to null.
This works fine:
public class TeleportReference : MonoBehaviour {
private GameObject reference;
void Awake () {
reference = null;
}
public void SetReference(GameObject other){
reference = other;
}
public GameObject GetReference(){
return reference;
}
Thanks everyone for comments, that really helped me in debugging.
I'm trying to get a reference to the GameObject that the script is attached to. Per docs transform.parent.gameObject is used for this but transform.parent is null in both Awake() and Start(). What do I need to do to get this working? This is probably a total noob question but Google didn't come up with a working answer so far.
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
private void Awake()
{
var obj = transform.parent;
Debug.Log(obj);
}
private void Start()
{
var obj = transform.parent;
Debug.Log(obj);
}
}
Nevermind! I'm an idiot! It shouldn't be parent but:
var obj = transform or var obj = transform.gameObject
since this script is part of the game object which it should refer to, not any parent. I had the strange assumption that a script is a child of a game object.
Transform.parent tells you what the parent of your current transform is. I.E. if GameObjectA is a child of GameObjectB, a script that accesses transform.gameObject in GameObjectB will return GameObjectA
What you're looking for, in fact, is just gameObject. This implicitly returns the gameObject your script is attached to.
Create two GameObjects in your scene.
Call one GameObjectA, and the other GameObjectB.
Attach this script to GameObjectB and then drag GameObjectB to be under GameObjectA in the hierarchy
public class ExampleBehaviour : MonoBehaviour {
void Awake () {
Debug.Log(gameObject.name); //Prints "GameObjectB" to the console
Debug.Log(transform.parent.name); //Prints "GameObjectA" to the console
}
}