Make a loop run only once when script is added to gameObject - c#

I have a script that runs in editor as well as in Play Mode. However I want a loop to be executed ONLY ONCE when I add the script to a gameObject. That is I do not want it to be called 2nd time when I play the game. How is this possible?
[ExecuteAlways]
..
..
void Start()
{
for (int i = 0; i < Go.Count; i++) {
//Only Once
}
}

You can use the OnValidate method who is called everytime your MonoBehaviour changed on editor time. You'll have to set a variable to validate that the script did the call.
public class Test : MonoBehaviour
{
[HideInInspector]
[SerializeField]
private bool isInitializedOnEditor = false;
#if UNITY_EDITOR
private void MySpecialMethod()
{
//...
}
private void OnValidate()
{
if(isInitializedOnEditor == false)
{
MySpecialMethod();
isInitializedOnEditor = true;
//Don't forget to save your change on this gameobject if require
UnityEditor.EditorUtility.SetDirty(this);
}
}
#endif
}
I used attributes [HideInInspector] and [SerializeField] to set the bool serializable (because otherwise the next time you load the scene it'll call the method again) but not show on inspector (you don't want the unity editor user can change it manually).
#if UNITY_EDITOR is required otherwise you cant compile because UnityEditor is only available on editor mode.
SetDirty() is used to tell Unity this MonoBehaviour have change and it should serialize it again. Otherwise the next time you will load the scene you'll loose your change here. You don't have to use it if your code job don't change the MonoBehaviour
Edit
If you don't want to call you method when on play mode, you can make it like:
#if UNITY_EDITOR
private void OnValidate()
{
if (isInitializedOnEditor == false && Application.isPlaying == false)
{
MySpecialMethod();
isInitializedOnEditor = true;
//Don't forget to save your change on this gameobject if require
UnityEditor.EditorUtility.SetDirty(this);
}
}
#endif
Note on == false
When I need to use a if and check if a bool is false I prefer to write the whole comparison if(myBool == false) than the shortcut if(!myBool) for readability purpose. We could debate it but it's on purpose.

Use break;
[ExecuteAlways]
..
..
void Start()
{
for (int i = 0; i < Go.Count; i++) {
//Only Once
break;
}
}
The Loop will run Once till it hits the break, and the the loop will end.

Related

Stacking effects & powerups in UI | Unity C#

How could I make it so, for example, if only one thing is on it will move to the first slot, I think you know what I mean... So how could I make this?1
I thought about using arrays but I don't understand how.
This is my code currently, rn it just shows and hides but doesnt move them so there just a gap.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class UI : MonoBehaviour
{
public Text myText;
public GameObject Player;
public GameObject SugarIcon;
public GameObject DashIcon;
public GameObject JumpIcon;
public GameObject ShieldIcon;
private string dashreadystring;
private string doublejumpstring;
private string triplejumpstring;
private string shieldbuffstring;
private int iconamount = 0;
void Start()
{
GameObject Player = GameObject.Find("Player");
}
void Update()
{
PlayerScript PlayerScript = Player.GetComponent<PlayerScript>();
bool dashready = PlayerScript.canDash;
float sugarbuff = PlayerScript.sugarbuffactive;
int airJumpCount = PlayerScript.airJumpCount;
int airJumpCountMax = PlayerScript.airJumpCountMax;
bool canDoubleJump = PlayerScript.canDoubleJump;
bool shieldon = PlayerScript.shieldon;
float score = Player.transform.position.x;
// If Dash is ready, show icon, if not, hide it.
if (dashready == true)
{
DashIcon.GetComponent<Renderer>().enabled = true;
}
else
{
DashIcon.GetComponent<Renderer>().enabled = false;
}
// If Sugarbuff is active, show icon, if not, hide it.
if (sugarbuff == 1.5)
{
SugarIcon.GetComponent<Renderer>().enabled = true;
}
else
{
SugarIcon.GetComponent<Renderer>().enabled = false;
}
// If can jump,
if ((airJumpCount < airJumpCountMax) | ((canDoubleJump) && (sugarbuff == 1.5f)))
{
JumpIcon.GetComponent<Renderer>().enabled = true;
}
else
{
JumpIcon.GetComponent<Renderer>().enabled = false;
}
if (shieldon)
{
ShieldIcon.GetComponent<Renderer>().enabled = true;
}
else
{
ShieldIcon.GetComponent<Renderer>().enabled = false;
}
}
}
How could I make this? Thanks.
If your icons are children of a parent container that uses a HorizontalLayoutGroup component, it will automatically line up your icons horizontally (use VerticalLayoutGroup or GridLayoutGroup to autoalign in different patterns).
Then, if you disable the gameObject of the icons, they will disappear. The HorizontalLayoutGroup will rearrange the remaining icons in the way I think you want. When you reenable the icon, it will be reinserted into the horizontal layout.
Notes:
You are currently disabling the Renderer Component on the gameObject. This does make the object invisible, but it still exists and takes up space. Instead, disable the gameObject for the icon itself (SugarIcon.enabled = false). Hopefully the Icon objects are just images, and have no scripts/logic on them, but if so, you'll need to decouple that.
It looks like you're using GetComponent() calls many times every frame. Don't do this -- GetComponent() calls have a pretty big overhead. Instead, call it once, when you initialize your manager, and then cache the result. This will save a ton of processing.
// Call GetComponent() and cache the reference one time (when you initialize the objects).
Renderer sugarIconRenderer = SugarIcon.GetComponent<Renderer>();
// Call the cached reference every frame when you need it.
sugarIconRenderer.enabled = false;

Error accessing property across classes: an object reference is required for the non-static field method or property

I have been dabbling in unity and have run into a problem as I an unable to figure out how to notify another class that a ability has been used and the countdown is active. I realize that it is because I need to be making a static reference but not quite sure how to use one while still being able to change the value. I will include just the important bits so that you don't have to waste your time. (I want to be able to have the usedAbilities.canFireballnow be equal to that as when I call it in the fireball script.)
fireball
float canuseFireballtimer = 0;
bool startCooldown = false;
// Update is called once per frame
void Update()
{
if (startCooldown) {
usedAbilities.canFireballnow = false; // error
canuseFireballtimer += Time.deltaTime;
if (canuseFireballtimer >= 5) {
usedAbilities.canFireballnow = true; //error
}
}
if (Input.GetKeyDown(KeyCode.Q) && enoughmana && usedAbilities.canFireballnow) { // error
startCooldown = true;
ManaBar.mana -= 10f;
Instantiate(fireballPrefab, fireballSpawn.position, fireballSpawn.rotation);
}
}
usedAbilities script
public bool canFireballnow = true;
Thanks,
A fellow Programmer
First of all, you need to add a reference to your object. You can do that by using [SerializeField] before creating a variable of type GameObject. Like this:
[SerializeField]
GameObject obj;
Then, in Unity, you can drag the GameObject from the Hierarchy to the Inspector.
This is what you should see in the Inspector:
Then, you need to get the script component of the GameObject to finally be able to read it's value.
obj.GetComponent<name_of_your_script>().value;
Your final code should look like this:
[SerializeField]
GameObject abilities;
void Update() {
if (canuseFireballtimer >= 5) {
abilities.GetComponent<usedAbilities>().canFireballnow = true;
}
Note: Your variable should be public.

How do I properly detect game objects?

My goal is to write one script that I can use on different game objects and it should have specific variables tied to it on that game object only without affecting other scripts in the process.
For example, if I take this script and put it on two game objects each game object should have their own unique variable value in that same script.
If my question is not clear enough, I'm more than happy to elaborate further.
I have a good understanding of the Unity Editor, however, I'm pretty new to C# so I don't think it's unreasonable that I made a rookie mistake somewhere in my code.
The way I've got things setup is that I have two separate scripts:
Fighting controls the values like the Team, Health, Attack Damage, Cool Down, Cooling down and Snap
TrigDetect controls the detection of a trigger being activated as a result of an enemy entering the trigger radius.
The problem I'm currently having lies in the TrigDetect script I guess.
It should also be noted that an empty attached to each game object in question contains both of these scripts and is tagged as "Troop".
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
//public GameObject[] Enemy;
bool once = false;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Entered");
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Exitted");
}
}
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Troop"))
{
Fighting self = GetComponent<Fighting>();
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
//Enemy = GameObject.FindGameObjectsWithTag("Troop");
//Debug.Log("Staying");
//Debug.Log(Enemy);
//Debug.Log(self.Health);
//Debug.Log(fScript.Health);
if (once == false)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
if (self.Team != fScript.Team)
{
if (self.CoolingDown == false)
{
self.CoolingDown = true;
fScript.Health -= self.AttackDamage;
}
else
{
self.CoolDown -= Time.deltaTime;
if (self.CoolDown <= 0)
{
self.CoolingDown = false;
self.CoolDown = self.original;
}
}
}
}
}
}
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
public bool CoolingDown = false;
public bool Snap = false;
// Update is called once per frame
void Update () {
if (Snap == true || Health <= 0)
{
//Destroy(gameObject, .5f);
Destroy(transform.parent.gameObject);
}
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
}
The expected result when I move one game object into the trigger radius of the other is that they should both start subtracting Health from each other based on the AttackDamage value. They should do this every time the CoolingDown value is false. When an attack is executed, it's flipped to true and a timer starts, when the timer is done it's flipped back to false.
However, upon moving the two objects into each other's radius', the first object has its health taken away as expected and then proceeds to do nothing until it's health reaches 0 then it dies because of the object attacking it. The object attacking is successfully attacking the other object but, is still not being affected by the object it's attacking.
Basically, Find(name) only returns the first instance of anything by that name, thus your g = Find(name) is almost guaranteed to never be the object related to your trigger/collision condition. The OnTriggerStay(Collider other) already gives you the 'other' collider that's in your trigger zone, so use it. :)
Replace this:
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
with this:
Fighting fScript = other.GetComponent<Fighting>();
To your question header:
Every instaced (non-static) value is allways unique to the according component and thereby to the according GameObject it is attached to. You might want to refrase the question because this is actually not your issue.
The problem is that when you do
GameObject.Find("Detection");
it actually finds the same object both times: Namely the first one in the hierarchy. So in one of of the two components you find your own empty object and skip the rest in
if(self.Team != FScript.Team)
.. you could try to use
other.Find("Detection");
instead to only search in the according context .. However, you should not use Find at all!
It is very performance intense
You should allways reuse references and not search them over and over again
You don't need it in your case
Since you say both scripts are attached to the same object you can simply use
GetComponent<Fighting>();
and you can do so already in Awake and reuse the reference instead:
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
Than for the collision you don't have to use Find either because you already have the reference of the object you collide with: other.gameObject. I don't know your entire setup but you can search for the component either downwards in the hierachy
// the flag true is sued to also find inactive gameObjects and components
// leave it without parameters if you don't want this
var otherFighting = other.GetComponentInChildren<Fighting>(true);
or searcg upwards in the hierachy
var otherFighting = other.GetComponentInParent<Fighting>(true);
or if you already know you collide exactly with the correct GameObject anyway simply use
var otherFighting = other.GetComponent<Fighting>();
I will use the latter in my example.
Than cheking the health all the time in Update is a huge perfomance issue. You should rather have a method e.g. TakeDamage and do your check only if your health is actually changed:
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
// you don't need that flag see below
//public bool CoolingDown = false;
public bool Snap = false;
private void Update()
{
// you might also put this in a callback instead of update at some point later
if(Snap == true)
{
Destroy(transform.parent.gameObject);
}
// Note: this also makes not muh sense because if you destroyed
// the parent than you cannot instantiate it again!
// use a prefab instead
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
public void TakeDamge(int DamageAmount)
{
Health -= DamageAmount;
if (Health > 0) return;
Destroy(transform.parent.gameObject);
}
}
Another performance issue in general: Even if Start, Update etc are empty, if they are present in your script Unity will call them. So if you don't use them then completely remove them to avoid that useless overhead.
So I would have
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
bool once = false;
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
void OnTriggerStay(Collider other)
{
// if wrong tag do nothing
if (!other.CompareTag("Troop")) return;
Fighting fScript = other.GetComponent<Fighting>();
// here you should add a check
if(!fScript)
{
// By using the other.gameObject as context you can find with which object
// you collided exactly by clicking on the Log
Debug.LogError("Something went wrong: Could not get the Fighting component of other", other.gameObject);
}
if (!once)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
// if same team do nothing
if (self.Team == fScript.Team) return;
// you don't need the CoolingDown bool at all:
self.CoolDown -= Time.deltaTime;
// if still cooling down do nothing
if(self.CoolDown > 0) return;
fScript.TakeDamage(self.AttackDamage);
self.CoolDown = self.original;
}
}

Unity, editor-time script, On GameObject Added to Scene Event

Say you have a trivial prefab, "Box", which we'll say is nothing more than a standard meter cube.
1 - The prefab Box is in your Project panel
2 - Drag it to the Scene
3 - Obviously it now appears in the Hierarchy panel also, and probably selected and shown in Inspector
To be clear, game is NOT Play when you do this, you're only in ordinary Editor mode.
Is it possible to make a script (an "Editor script"?) so that,
when you do "1" and "2" above, (again this is in Editor mode, not during a game)
when 3 happens, we can affect the new Box item in the scene
So, simple example: we will set the Z position to "2" always, no matter where you drop it.
In short: Editor code so that every time you drag a prefab P to the scene, it sets the position z to 2.0.
Is this possible in Unity? I know nothing of "editor scripts".
It seems very obvious this should be possible.
You can add a custom window editor which implements OnHierarchyChange to handle all the changes in the hierarchy window. This script must be inside the Editor folder. To make it work automatically make sure you have this window opened first.
using System.Linq;
using UnityEditor;
using UnityEngine;
public class HierarchyMonitorWindow : EditorWindow
{
[MenuItem("Window/Hierarchy Monitor")]
static void CreateWindow()
{
EditorWindow.GetWindow<HierarchyMonitorWindow>();
}
void OnHierarchyChange()
{
var addedObjects = Resources.FindObjectsOfTypeAll<MyScript>()
.Where(x => x.isAdded < 2);
foreach (var item in addedObjects)
{
//if (item.isAdded == 0) early setup
if (item.isAdded == 1) {
// do setup here,
// will happen just after user releases mouse
// will only happen once
Vector3 p = transform.position;
item.transform.position = new Vector3(p.x, 2f, p.z);
}
// finish with this:
item.isAdded++;
}
}
}
I attached the following script to the box:
public class MyScript : MonoBehaviour {
public int isAdded { get; set; }
}
Note that OnHierarchyChange is called twice (once when you start dragging the box onto the scene, and once when you release the mouse button) so isAdded is defined as an int to enable its comparison with 2. So you can also have initialization logic when x.isAdded < 1
You could thy this:
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteInEditMode]
public class PrintAwake : MonoBehaviour
{
#if UNITY_EDITOR
void Awake() .. Start() also works perfectly
{
if(!EditorApplication.isPlaying)
Debug.Log("Editor causes this Awake");
}
#endif
}
See https://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html
Analysis:
This does in fact work!
One problem! It happens when the object starts to exist, so, when you are dragging it to the scene, but before you let go. So in fact, if you specifically want to adjust the position in some way (snapping to a grid - whatever) it is not possible using this technique!
So, for example, this will work perfectly:
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteInEditMode]
public class PrintAwake : MonoBehaviour
{
#if UNITY_EDITOR
void Start() {
if(!EditorApplication.isPlaying) {
Debug.Log("Editor causes this START!!");
RandomSpinSetup();
}
}
#endif
private void RandomSpinSetup() {
float r = Random.Range(3,8) * 10f;
transform.eulerAngles = new Vector3(0f, r, 0f);
name = "Cube: " + r + "°";
}
}
Note that this works correctly, that is to say it does "not run" when you actually Play the game. If you hit "Play" it won't then again set random spins :)
Great stuff
Have a similar issue - wanted to do some stuff after object was dragged into scene (or scene was opened with already existed object). But in my case gameobject was disabled. So I couldn't use neither Awake, nor Start.
Solved via akin of dirty trick - just used constructor for my MonoBehaviour class. Unity blocks any attempts to use most of API inside MonoBehaviour constructors, but we could just wait for some time, for example via EditorApplication.delayedCall. So code looks like this:
public class ExampleClass: MonoBehaviour
{
//...
// some runtime logic
//...
#if UNITY_EDITOR
//Constructor
ExampleClass()
{
EditorApplication.delayCall += DoSomeStuffForDisabledObjectAfterCreation;
}
void DoSomeStuffForDisabledObjectAfterCreation()
{
if (!isActiveAndEnabled)
{
//Some usefull stuff
}
}
#endif
}
Monobehaviours have a Reset method, that only gets called in Editor mode, whenever you reset or first instantiate an object.
public class Example : MonoBehaviour
{
void Reset()
{
transform.position =
new Vector3(transform.position.x, 2.22f, transform.position.z);
Debug.Log("Hi ...");
}
}

How can I call IEnumerator when a boolean is true?

I'm using the IEnumerator function and have had some issues with my if statement working:
IEnumerator Spawning()
{
Debug.Log("Spawning being called...");
if (GameObject.Find("FPSController").GetComponent<BoxCollide>().hitTrigger == true)
{
Debug.Log("CUSTOMER SHOULD SPAWN!");
while (countStop == false) {
yield return new WaitForSeconds(2);
Debug.Log("Fisher Spawned!");
counter++;
spawnNewCharacter.SpawnCharacter();
if (counter >= 3){
countStop = true;
}
}
}
}
After some debugging, it turns out that my if statement actually works. This issue is actually the IEnumerator function being called as soon as I run my game. I need a way to call this IEnumerator function only when my hitTrigger == true, but I can't seem to get anything to work.
I've tried this on top of the IEnumerator function:
void Update()
{
if (GameObject.Find("FPSController").GetComponent<BoxCollide>().hitTrigger == true)
{
Debug.Log("Spawning now...");
StartCoroutine(Spawning());
}
}
But still can't even get any of the Debug.Log's to come through. Would appreciate some help on this!
Side Information
Find() and GetComponent()
You don't want to use GameObject.Find(...) in the Update method, as it's an expensive call. The Update method is called each frame, so you'd call GameObject.Find(...) 60 times in 1 second at 60fps.
So when you use GetComponent() or Find() you want to save a reference to these objects like shown in the snippets below.
Better locations to use methods like GetComponent() or GameObject.Find() are the Awake() and Start() methods.
Awake
Awake is used to initialize any variables or game state before the
game starts. Awake is called only once during the lifetime of the
script instance. Awake is called after all objects are initialized so
you can safely speak to other objects or query them using eg.
GameObject.FindWithTag. [...]
Explanation is taken from the linked documentation.
Start
Start is called on the frame when a script is enabled just before any
of the Update methods is called the first time. Like the Awake
function, Start is called exactly once in the lifetime of the script.
However, Awake is called when the script object is initialised,
regardless of whether or not the script is enabled. Start may not be
called on the same frame as Awake if the script is not enabled at
initialisation time.
Explanation is also taken from the linked documentation
Possible Solution
Add the first Component (FPSControllerCollission) onto the object that holds your FPSController.
It makes use of unities OnTriggerEnter & OnTriggerExit methods.
This script is gonna set the IsTriggered bool to true, when a trigger entered the space of the box collider.
Note: A collider acts as a trigger, when the "Is Trigger" checkbox on
the component is checked.
You can do similar with OnCollisionEnter/Exit to recognize, when a Collider enters the space of the box collider.
Note that the following is only an example and you'll have to tweak /
integrate it into your code
[RequireComponent(typeof(BoxCollider))]
public class FPSControllerCollission : MonoBehaviour {
public bool IsTriggered;
private void OnTriggerEnter(Collider other) {
this.IsTriggered = true;
}
private void OnTriggerExit(Collider other) {
//Maybe check the tag/type of the other object here
this.IsTriggered = false;
}
}
The following SpawnController class could be integrated in the class you allready have.
public class SpawnController : MonoBehaviour {
private FPSControllerCollission _fpsControllerCollission;
private void Awake() {
this._fpsControllerCollission = FindObjectOfType<FPSControllerCollission>();
}
private void Update() {
if (this._fpsControllerCollission.IsTriggered) {
StartCoroutine(nameof(Spawning));
}
}
IEnumerator Spawning() {
Debug.Log("Spawning being called...");
if (this._fpsControllerCollission == true) {
Debug.Log("CUSTOMER SHOULD SPAWN!");
bool countStop = false;
int counter;
while (countStop == false) {
yield return new WaitForSeconds(2);
Debug.Log("Fisher Spawned!");
counter++;
spawnNewCharacter.SpawnCharacter();
if (counter >= 3) {
countStop = true;
}
}
}
}
}

Categories