How can I loop over objects each object after a specific time? - c#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public class CompareObjects : MonoBehaviour
{
public float waitTime;
private GameObject[] allObjects;
public void Compare()
{
allObjects = FindObjectsOfType<GameObject>();
foreach (GameObject go in allObjects)
{
Debug.Log(go.name + " >>>>> " + go.scene.name + " >>>>> is active object");
StartCoroutine(Comparing());
}
}
IEnumerator Comparing()
{
yield return new WaitForSeconds(waitTime);
}
}
The idea is not to choke the whole editor and wait for the foreach loop to finish, but to make that it will loop over the first item wait a second then will continue to the next and so on.
The way it is now it's choking the editor, freezing it until the loop is over.
What I did so far and it's not working good yet :
I created a new editor script for a button in the inspector :
using UnityEngine;
using System.Collections;
using UnityEditor;
[CustomEditor(typeof(CompareObjects))]
public class CompareObjectsButton : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
CompareObjects myTarget = (CompareObjects)target;
if (GUILayout.Button("Compare Objects"))
{
myTarget.StartComparing();
}
}
}
Then in Compare Objects :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public class CompareObjects : MonoBehaviour
{
public float waitTime;
private Coroutine comparer;
private GameObject[] allObjects;
public void StartComparing()
{
if (comparer == null)
{
comparer = StartCoroutine(Compare());
}
}
public void StopComparing()
{
if (comparer != null)
{
StopCoroutine(comparer);
comparer = null;
}
}
IEnumerator Compare()
{
while (true)
{
allObjects = FindObjectsOfType<GameObject>();
foreach (GameObject go in allObjects)
{
Debug.Log(go.name + " >>>>> " + go.scene.name + " >>>>> is active object");
yield return new WaitForSeconds(waitTime);
}
}
}
}
But it's doing only one object and not all the objects.

StartCoroutine kind of acts in the same manner as starting a new thread, so when you call Compare all you are doing is looping through the game objects and creating event objects which, while running in the main thread, don't prevent the method which created it from running, so ultimately only the new event object waits the specified time while the main method continues to loop through the rest of the game objects and creating more event objects, sins it itself isn't calling any sleep functions, instead you should do something like this:
public void StartComparing()
{
if (comparer == null)
{
comparer = StartCoroutine(Compare());
}
}
public void StopComparing()
{
if (comparer != null)
{
StopCoroutine(comparer);
comparer = null;
}
}
IEnumerator Compare()
{
while (true)
{
allObjects = FindObjectsOfType<GameObject>();
foreach (GameObject go in allObjects)
{
Debug.Log(go.name + " >>>>> " + go.scene.name + " >>>>> is active object");
yield return new WaitForSeconds(waitTime);
}
}
}
private Coroutine comparer;
private GameObject[] allObjects;
public float waitTime;
StartComparing will start a coroutine of Compare which will go through all the objects, so, first object then wait the specified waitTime then the second object and so on, this is done until StopComparing is called.
Tested the code, here is a small (literally) preview of it, you will see that after changing the speed it logs the objects slower:

Related

C# Unity Queue NullReferenceException

I am trying to create a simple dialogue system for my game in Unity. I've set up a special trigger for a Dialogue to start and the code is passing right variable but somehow it gets stuck at clearing the queue and throws a NullReferenceException.
I've seen through debugger that all variables and triggers work perfectly fine till cs:27 inside DialogueManager.cs. I've also checked the inspector to make sure everything is correctly assigned.
Dialogue class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Dialogue
{
public string name;
[TextArea(3,10)]
public string[] sentences;
}
DialogueTrigger class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPC_DialogTrigger : MonoBehaviour
{
// Player
public Transform player;
// GameMaager to close
public GameObject Gameplay;
// Camera and Canvas to turn on
public GameObject DialogueManager;
// NPC Details
public GameObject InteractionNPCNameTextField;
public Transform interactionTransform;
public float radius = 3f;
private bool isBusy = false;
// DialogueStart
public GameObject DialogueStart;
void Start()
{
InteractionNPCNameTextField.gameObject.SetActive(false);
}
void Update()
{
float distance = Vector3.Distance(player.position, interactionTransform.position);
if (distance <= radius)
{
if (isBusy == false)
{
InteractionNPCNameTextField.gameObject.SetActive(true);
if (Input.GetKeyDown(KeyCode.E))
{
Dialogue();
Debug.Log("Started Dialogue procedure.");
}
}
}
else if (distance > radius)
{
InteractionNPCNameTextField.gameObject.SetActive(false);
}
}
public void Dialogue()
{
Gameplay.SetActive(false);
DialogueManager.SetActive(true);
DialogueStart.GetComponent<DialogueStart>().TriggerDialogue();
Debug.Log("Triggered Dialogue.");
}
void OnDrawGizmosSelected()
{
if (interactionTransform == null)
{
interactionTransform = transform;
}
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(interactionTransform.position, radius);
}
}
DialogueStart class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DialogueStart : MonoBehaviour
{
public Dialogue dialogue;
public void TriggerDialogue()
{
FindObjectOfType<DialogueManager>().StartDialogue(dialogue);
Debug.Log("Dialogue sent to dialogue manager.");
}
}
DialogueManager class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class DialogueManager : MonoBehaviour
{
public Text nameText;
public Text DialogueText;
private Queue<string> sentences;
public GameObject DialogueManagerUI;
void Start()
{
if (sentences == null)
{
sentences = new Queue<string>();
}
}
public void StartDialogue (Dialogue dialogue)
{
Debug.Log("Received dialogues: " + dialogue);
nameText.text = dialogue.name;
Debug.Log("Start Dialogue: " + sentences.Count);
sentences.Clear();
Debug.Log("Sentences Cleared: " + sentences);
foreach (string sentence in dialogue.sentences)
{
sentences.Enqueue(sentence);
}
DisplayNextSentence();
}
public void DisplayNextSentence()
{
if(sentences.Count == 0)
{
EndDialogue();
return;
}
string sentence = sentences.Dequeue();
DialogueText.text = sentence;
}
void EndDialogue()
{
Debug.Log("End of conversation.");
}
private void Update()
{
if (DialogueManagerUI.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else if (!DialogueManagerUI.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
}
}
Visual Studio doesn't give me any errors.
Unity error code -> Screenshot
Debugger right before issue -> Screenshot2
Seems to me like the Queue is never assigned to sentences and it remains empty.
If it is so - why?
It sounds like DialogueManager.StartDialogue probably via DialogueStart.TriggerDialogue is called from somewhere in either Awake or at least before your Start was executed.
Especially
DialogueManager.SetActive(true);
lets assume the DialogueManager object is inactive at first anyway. So maybe your stuff is called before it is set to active.
This might also be due to the Start where you set
InteractionNPCNameTextField.gameObject.SetActive(false);
so any component on this GameObject might not have its Start method getting called. Maybe you referenced the wrong GameObject here?
Debugging would help to figure out in which order your methods get called.
In general my thumb rule for initializing is
Do everything that depends only on yourself in Awake
Do everything that depends on other components being setup already in Start
This way you can (almost) always be sure that stuff is already initialized when you need it.
However, these are only guesses but
The simple solution here:
You could already solve this by simply initializing the sentences right away using
private readonly Queue<string> sentences = new Queue<string>();
now it is definitely initialized even before Start or Awake get called!

How can I compare objects from one scene against objects from other scene/s?

It might be slowly or take long if I have for example over 10000 objects since each object should be compared against all the others if I'm not mistaken.
Why ? In my game I did some changes on one big object children it have a lot of children. I can't remember what the changes I did on what objects. But I don't want also just to copy the original objects from the original scene and overwrite the others with the changes since some of the changes I want to keep.
There are changes I did and know and want to keep and there are changes I did and not sure where and when and those I might want to remove later.
The main goal is to get a nice output log or to a text file something that will tell how many objects the same how many not and each objects that are not the same have same name but not same rotation scaling position or components to tell me what are the changes difference.
My first scene is contains the objects with the changes. The second scene contains the original objects before the changes.
This script is attached to empty gameobject in my first scene :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public class CompareObjects : MonoBehaviour
{
public GameObject mainGame;
public string numberOfObjects;
public string comparisonObjects;
public float waitTime;
public List<GameObject> allobjects = new List<GameObject>();
public bool compareAtStart = false;
public bool pauseGameOnComparison = false;
private Coroutine comparer;
private void Start()
{
allobjects = FindObjectsOfType<GameObject>().ToList();
numberOfObjects = allobjects.Count.ToString();
if (compareAtStart == true)
{
StartComparing();
}
}
public void StartComparing()
{
if (pauseGameOnComparison == true)
{
mainGame.SetActive(false);
}
if (comparer == null)
{
comparer = StartCoroutine(Compare());
}
}
public void StopComparing()
{
if (comparer != null)
{
comparisonObjects = "";
allobjects = new List<GameObject>();
StopCoroutine(comparer);
mainGame.SetActive(true);
comparer = null;
}
}
IEnumerator Compare()
{
while (true)
{
if (allobjects.Count == 0)
{
StopComparing();
break;
}
else
{
foreach (GameObject go in allobjects)
{
if (go.name != "Game Manager")
{
Searcher.PrintSearch(go.name);
}
yield return new WaitForSeconds(waitTime);
}
}
}
}
}
In my first scene I moved all the objects under Main Game to easy be able to make the comparison/s if the game is keep running or on pause.
The second class code should make the comparison it self :
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class Searcher
{
[MenuItem("Tools/PrintSearch")]
public static void PrintSearch(string objectName)
{
var componentType = typeof(BoxCollider);
var scenes = Enumerable.Range(0, SceneManager.sceneCount)
.Select(SceneManager.GetSceneAt)
.ToArray();
var objectsWithNames = scenes
.ToDictionary(s => s, s => GetSceneObjectsWithName(s, objectName.ToLower()));
var forLog = objectsWithNames
.SelectMany(pair => pair.Value.Select(child =>
$"Scene {pair.Key.name}: object name '{child.gameObject.name}' {(child.GetComponent(componentType) != null ? "contains" : "do not contains")} component {componentType.Name}"));
Debug.Log(string.Join("\n", forLog));
}
private static Transform[] GetSceneObjectsWithName(Scene scene, string objectName)
{
return scene.GetRootGameObjects()
.SelectMany(g => GetChildHierarchyRecursively(g.transform))
.Where(t => t.gameObject.name.ToLower().Contains(objectName))
.ToArray();
}
private static IEnumerable<Transform> GetChildHierarchyRecursively(Transform parentTransform)
{
yield return parentTransform;
if (parentTransform.childCount > 0)
yield return parentTransform;
}
}
The first problem is I see in the console in the log this :
Method Searcher.PrintSearch has invalid parameters. MenuCommand is the only optional supported parameter.
UnityEditor.EditorApplication:Internal_CallUpdateFunctions()
And it's writing to log all the time :
UnityEngine.Debug:Log(Object)
The second problem is that I'm not sure if I'm doing it at all the right way with the CompareObjects script.

Why it's not playing all the conversations and not in the right order from start to end?

For testing I have two conversations at index 0 and 1.
And I want it to play the first conversation index 0 and then when it finish playing it to start playing the next conversation at index 1.
This is the script with the playing methods. The first should play a list/array of conversations one by one the second should play only a single conversation :
PlayConversations and PlayConversation.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class ConversationTrigger : MonoBehaviour
{
public List<Conversation> conversations = new List<Conversation>();
public static List<int> conversationsToPlay = new List<int>();
public bool conversationEnd;
public GameObject canvas;
public static int conversationIndex;
private DialogueManager dialoguemanager;
private string jsonPath;
public void InitJsonPath()
{
jsonPath = Application.persistentDataPath + "/" + "Json.txt";
}
private void Start()
{
conversationIndex = 0;
dialoguemanager = FindObjectOfType<DialogueManager>();
}
public IEnumerator PlayConversations()
{
canvas.SetActive(true);
conversationEnd = false;
var conversations = conversationsToPlay.ToArray(); // Copy the list
conversationsToPlay.Clear(); // Immediately clear the original list
for (int i = 0; i < conversations.Length; i++) // iterate over the array
{
// Now you also don't need to remove items anymore,
// since you already cleared the list
yield return StartCoroutine(PlayConversation(conversations[i]));
}
}
public IEnumerator PlayConversation(int index)
{
if (conversations.Count > 0 &&
conversations[index].Dialogues.Count > 0)
{
for (int i = 0; i < conversations[index].Dialogues.Count; i++)
{
if (dialoguemanager != null)
{
dialoguemanager.StartDialogue(conversations[index].Dialogues[i]);
}
while (DialogueManager.dialogueEnded == false)
{
yield return null;
}
}
conversationEnd = true;
conversationIndex = index;
canvas.SetActive(false);
Debug.Log("Conversation Ended");
}
}
public void SaveConversations()
{
string jsonTransform = JsonHelper.ToJson(conversations.ToArray(), true);
File.WriteAllText(jsonPath, jsonTransform);
}
public void LoadConversations()
{
string jsonTransform = File.ReadAllText(jsonPath);
conversations.Clear();
conversations.AddRange(JsonHelper.FromJson<Conversation>(jsonTransform));
}
}
For that I made another helper script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayConversations : MonoBehaviour
{
private static ConversationTrigger conversationTrigger;
private static PlayConversations instance;
private void Awake()
{
conversationTrigger = GetComponent<ConversationTrigger>();
instance = this;
}
public static void ConversationToPlay(int index)
{
ConversationTrigger.conversationsToPlay.Add(index);
instance.StartCoroutine(conversationTrigger.PlayConversations());
}
}
And a script for testing :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeginningCutscene : MonoBehaviour
{
public DepthOfField dephOfField;
// Update is called once per frame
void Update()
{
if (dephOfField.dephOfFieldFinished == true)
{
PlayConversations.ConversationToPlay(0);
PlayConversations.ConversationToPlay(1);
}
}
}
But it's start playing first the conversation at index 1 then only part of conversation at index 0 and then start over again then ending.
I think your issue has to do with the way you implemented the ConversationsToPlay method. At each call of the method you start a new coroutine which in turn will call the PlayConversation method. What this means is that each call of ConversationToPlay will play the conversation at the index you passed and that is why they are overlapping.
The simplest solution I can think of is to move the start of the coroutine outside of the ConversationsToPlay method.
Something like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayConversations : MonoBehaviour
{
private static ConversationTrigger conversationTrigger;
private static PlayConversations instance;
private void Awake()
{
conversationTrigger = GetComponent<ConversationTrigger>();
instance = this;
}
public static void AddConversationToPlay(int index)
{
ConversationTrigger.conversationsToPlay.Add(index);
}
public static void StartPlayConversationsCoroutine()
{
instance.StartCoroutine(conversationTrigger.PlayConversations());
}
}
and the test script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeginningCutscene : MonoBehaviour
{
public DepthOfField dephOfField;
// Update is called once per frame
void Update()
{
if (dephOfField.dephOfFieldFinished == true)
{
PlayConversations.AddConversationToPlay(0);
PlayConversations.AddConversationToPlay(1);
PlayConversations.StartPlayConversationsCoroutine();
}
}
}

How can I get a List of doors if the scripts on each door execute after the main script?

The main script is attached to empty gameobject. And this script fire first.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DoorsLockManager : MonoBehaviour
{
private List<List<DoorHori>> doorsLR = new List<List<DoorHori>>();
public static void GetDoors(List<DoorHori> doorsLR)
{
doorsLR.AddRange(doorsLR);
}
private List<HoriDoorManager> Doors = new List<HoriDoorManager>();
private void Start()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
foreach(var door in doors)
{
Doors.Add(door.GetComponent<HoriDoorManager>());
}
}
}
This script the second one is attached to each door and the Start in this script execute after the top script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class HoriDoorManager : MonoBehaviour
{
private List<DoorHori> doors = new List<DoorHori>();
private bool doorLockState;
private void Start()
{
if (transform.parent != null)
{
Transform parent = transform.parent;
var children = parent.GetComponentsInChildren<Transform>();
if(children != null)
{
foreach (Transform door in children)
{
if (door.name == "Door_Left" || door.name == "Door_Right")
doors.Add(door.GetComponent<DoorHori>());
}
DoorsLockManager.GetDoors(doors);
}
}
}
void OnTriggerEnter()
{
if (doorLockState == false)
{
if (doors != null)
{
for(int i =0; i < doors.Count; i++)
{
doors[i].OpenDoor();
}
}
}
}
public void ChangeLockState(bool lockState)
{
doorLockState = lockState;
}
}
The problem is that before it will add all the doors to the doorsLR List in the first script the first script will already execute his Start.
I want first getting all the doors from HoriDoorManager and only then to execute the Start in the DoorsLockManager script.
I don't want to use tags I want to get each two doors from HoriDoorManager add them to the doorsLR when it finished adding all the doors then start working with it in the DoorsLockManager.
Move the code in your HoriDoorManager to Awake (instead of Start). All of the Awake methods in your scene's components will all execute before any Start method is executed.
Here is a further description of the order of events in each scene.

New to unity and c#, unexpected symbol issue in (14,29)

in my code theres this error
Assets/TextChangeScript.cs(14,29): error CS1525: Unexpected symbol (', expecting,', ;', or='
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TextChangeScript : MonoBehaviour
{
public Text m_MyText;
public Text OtherText;
void Start()
{
m_MyText.text = "There was once a mother and her child";
yield WaitForSeconds (3);
m_MyText.text = "The mother loved her child very dearly";
}
}
You are trying to call yield WaitForSeconds in a function that doesn't return an IEnumerator. You need to create a new function that returns IEnumerator and call it with StartCoroutine.
The code after the yield will be executed.
You can check the documentation. Unity is well documented.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class WaitForSecondsExample : MonoBehaviour
{
public Text m_MyText;
public Text OtherText;
void Start()
{
StartCoroutine(Example());
}
IEnumerator Example()
{
m_MyText.text = "There was once a mother and her child";
yield return new WaitForSeconds(3);
m_MyText.text = "The mother loved her child very dearly";
}
}
Use a Coroutine, Unity is well documented on how they work and you can use the link in Chopi's answer.
Now to avoid any confusion, normally in a function you will want a return to be the last call in your function as code behind it typically does not get called. This is not the case for an IEnumerator.
The line yield return new WaitForSeconds(3); returns an expression from a function and is used as a place to mark where execution is to continue, in this case in 3 seconds at that line.
Your start method is not an IEnumerator, and as far as I am tracking there isn't a way to yield in your start in Unity. You can start a coroutine in start and have that coroutine yield. Here is an example of a coroutine that will give you, your 3 second delay between your text:
using UnityEngine;
using UnityEngine.UI;
public class TextChangeScript : MonoBehaviour
{
public Text m_MyText;
public Text OtherText;
IEnumerator StoryText() {
m_MyText.text = "There was once a mother and her child";
yield return new WaitForSeconds (3);
m_MyText.text = "The mother loved her child very dearly";
yield return new WaitForSeconds (3);
m_MyText.text = "Then one day blah blah blah";
}
void Start()
{
StartCoroutine(StoryText());
}
void Update()
{
}
}
Here is an example of using a for loop to loop through text with a 3 second delay between them:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TextChangeScript : MonoBehaviour
{
public Text m_MyText;
public List<string> storyText;
IEnumerator StoryText() {
foreach (string sz in storyText)
{
text.text = sz;
yield return new WaitForSeconds(3); // in 3 seconds. execution will begin here and iterate to the next string in the storyText.
}
}
void Start()
{
StartCoroutine(StoryText());
}
void Update()
{
}
}
Edit:
Thanks to what R1PFake shared in the comments you can also do this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CenterName : MonoBehaviour {
public Text text;
public List<string> storyText;
// Use this for initialization
IEnumerator Start () {
foreach (string sz in storyText)
{
text.text = sz;
yield return new WaitForSeconds(3);
}
}
}

Categories

Resources