Hey, I’m new to Unity and C#, and having a very basic issue with using tags to print something to console. Briefly, I have a game where a player can either turn left or right, but only one of these options is “correct”. I want to print out whether the player was correct or incorrect when they enter a trigger zone. I’m not sure what is best practice for Unity/C#, but I was assuming it would be to handle all of this in one script applied to the player.
I first generate a random (uniformly distributed) array of 0s and 1s to decide which side is correct for a given attempt, which seems to be working fine. I then want to use the first value in this array to determine which trigger will return “correct” and which “incorrect”, then once either collision is triggered to increment onto the next value in the array. I have tried this using a do/while statement, and a for statement like in the first part of my code, but neither was working correctly. Any help appreciated, thanks
void Start()
{
int[] trialList = new int[trialN];
System.Random r = new System.Random();
while (trialList.Any(item => item == 1) == false)
{
for (int i = 0; i < trialList.Length; i++)
{
trialList[i] = r.Next(0, 2);
}
}
}
private void OnTriggerEnter(Collider other)
{
do
{
// trialTarget = trialList[ii];
if (trialList[trialCounter] == 0)
{
if (other.CompareTag("LeftReward"))
{
print("Correct");
print(trialList[trialCounter]);
trialCounter++;
}
else if (other.CompareTag("RightReward"))
{ print("Incorrect"); trialCounter++; }
}
else if (trialList[trialCounter] == 1)
{
if (other.CompareTag("RightReward"))
{ print("Correct"); trialCounter++; }
else if (other.CompareTag("LeftReward"))
{ print("Incorrect"); trialCounter++; }
}
} while (trialCounter < trialN);
}
Why did you put this while into your Start method?
You check if there are !(Any 1), so it’s only true if all values are zero. Which should only be true when you just instantiated the array, which is when Start() will be run anyways.
Edit: You also declare trialList inside of Start(), meaning it only exists there. Did you perhaps mean to overwrite a class variable with the same name? For things like this it would help to see the whole code, or at least all relevant parts, which definitely includes class variables.
The do-while loop in your OnTriggerEnter method is bad, since now whenever you step into either of your triggers, you just go through your entire array and basically skip the game. Also, if the other object is neither of the hardcoded values, this will crash the game since you’d run an infinite loop.
So TL;DR: remove both while’s. There may be other problems with the code which i overlooked, but to speed this process up it would be helpful if you told us was “not working correctly” means in your context. Currently i assume you are printing a bunch of “Correct” and “Incorrect” as soon as you step into your first trigger, but this information would have been helpful to know beforehand.
This is worth a read. Help us help you: http://plbm.com/?p=220
To be honest, I was basing that whole section off someone else’s code and I ended up copying over the while statement. You’re right, I didn’t need it there or in the OnTriggerEnter section.
I managed to get it working (as in “correct” or “incorrect” will print to console when the player enters a trigger depending on the indexed value of trialList), but I had to move the section I generated trialList into OnTriggerEnter() because again you were right, I was not overwriting the class variable. I still need the variable accessible outside of OnTriggerEnter() though.
My bad I didn’t mean to cut off the class variables. I’ve copied the whole (working) code below, ideally I would like to generate trialList using the for loop I have written outside of OnTriggerEnter (or in a way that overwrites the class variable of the same name) and have it be static so it is accessible both for any sections within the script and by other scripts. I’m going to try and figure out how to do this myself anyway but would appreciate any input/help.
Also thanks for all your feedback.
public class TrialControl : MonoBehaviour
{
// public int trialTarget; // Initiate control for left or right trial variable
public static int trialTarget;
public static int trialN = 10; //Number of trials
public int trialCounter = 0;
public bool trialComplete = false;
// public int[] trialList;
public void OnTriggerEnter(Collider other)
{
int[] trialList = new int[trialN];
System.Random r = new System.Random();
for (int i = 0; i < trialList.Length; i++) //(start; end; step size)
{
trialList[i] = r.Next(0, 2);
}
/*for (int i = 0; i < trialList.Length; i++)
{
print(trialList[i]);
}*/
if (trialList[trialCounter] == 0)
{
if (other.CompareTag("LeftReward"))
{
print("Correct");
print(trialList[trialCounter]);
}
else if (other.CompareTag("RightReward"))
{
print("Incorrect");
print(trialList[trialCounter]);
}
trialCounter++;
}
else if (trialList[trialCounter] == 1)
{
if (other.CompareTag("RightReward"))
{
print("Correct");
print(trialList[trialCounter]);
}
else if (other.CompareTag("LeftReward"))
{
print("Incorrect");
print(trialList[trialCounter]);
}
trialCounter++;
}
}
}
edit: changed the script to fix an error with how it was behaving - working as intended now.
edit2: I’m aware that I need to move trialCounter++ into the if/elif statements nested in the main if/else rule
I just wanted to update with the solution I found. Basically I was trying to create trialList using the for loop below - I’ve done this in Start() which I’m not sure is the right way to do it, but by assigning trialList to a static class first I am able to generate it in Start() then call on the variable in OnTriggerEnter(). I don’t think there are any issues with the way I’ve done it and I should be able to access the trialList variable from other scripts now (although I haven’t tested it yet), but still welcome to any criticisms, thanks.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using UnityEngine;
public class TrialControl : MonoBehaviour
{
static class GlobalTrialList
{
public static int[] trialList;
}
public static int trialTarget;
public static int trialN = 10; //Number of trials
public int trialCounter = 0;
public bool trialComplete = false;
public int[] GenTrialList(int trialN)
{
int[] trialList = new int[trialN];
System.Random r = new System.Random();
for (int i = 0; i < trialList.Length; i++) //(start; end; step size)
{
trialList[i] = r.Next(0, 2);
}
return trialList;
}
void Start()
{
GlobalTrialList.trialList = GenTrialList(trialN);
}
public void OnTriggerEnter(Collider other)
{
int[] decision = new int[trialN];
if (GlobalTrialList.trialList[trialCounter] == 0)
{
if (other.CompareTag("LeftReward"))
{
print("Correct");
decision[trialCounter] = 1;
print(GlobalTrialList.trialList[trialCounter]);
trialCounter++;
}
else if (other.CompareTag("RightReward"))
{
print("Incorrect");
decision[trialCounter] = 0;
print(GlobalTrialList.trialList[trialCounter]);
trialCounter++;
}
}
else if (GlobalTrialList.trialList[trialCounter] == 1)
{
if (other.CompareTag("RightReward"))
{
print("Correct");
decision[trialCounter] = 1;
print(GlobalTrialList.trialList[trialCounter]);
trialCounter++;
}
else if (other.CompareTag("LeftReward"))
{
print("Incorrect");
decision[trialCounter] = 0;
print(GlobalTrialList.trialList[trialCounter]);
trialCounter++;
}
}
}
}
As long as it works, it’s fine. As for criticism or answers to your previous posts:
Generating trialList in OnTriggerEnter results in a new list each time you check, which causes lots of bugs. One example would be that you could enter the same trigger twice (once being wrong, then re-entering the same) and since the list gets generated anew, it could then be the right choice. But i guess you already figured that out since you moved the generation into Start()
Awake() or Start() are exactly the right places to put something that should happen only once. In case you ever programmed something object oriented outside of Unity, think of them along the lines of the constructur method for Unity scripts. Generally speaking, this is an insanely helpful graphic you may wanna have a look at and save for later inspection: Unity - Manual: Order of execution for event functions
The trialList does not have to be static, but if you want to access it globally from other classes that’s obviously fine.
The only thing that’s probably not working as intended is your decision array. You are attempting to save the decisions the player made. However, the array you created exists only inside of OnTriggerEnter, which means the decision is lost afterwards. You should save the variable either in the class itself and just write to a new index of it when a decision is made, or (if you wanna save this longer than one game-session) you could use PlayerPrefs to save and load it. You also, currently, do not seem to be doing anything with decisions, which is probably why you did not notice.
Just to clarify, you are currently creating an empty array each time OnTriggerEnter is called, which you then fill with exactly one value at the current index, and discard after the method scope is left (all method variables are).
I do need trialList to be accessible by other classes in other scripts - are you saying that even given this it doesn’t have to be static, or it only doesn’t have to be static to work in the context of the script I have provided?
Yeah at the moment the decision array is just a placeholder - I didn’t mean to include it. However you pointed me in the right direction for what I intended to do with it anyway, so thanks for providing all that info. I was planning to create an array of trialN length populated with None/Nans or whatever the C# equivalent (edit: I think I just initialise the array the same way I did for trialList i.e. decision = new int[trialN]) is outside of OnTriggerEnter then overwrite each index as you suggested in the same if/elif statements I was using to print “correct” and “incorrect”. I need to be able to save the completed decision index, but I don’t need this to carry over between game-sessions - I don’t think PlayerPrefs is suitable for this because as I understand it, I want these data output to a file and not the registry.
The latter. Tho it is true that there are other ways to handle it, so no it does not have to be static, but i’d say it’s the most convenient option in your situation.
I’m not quite sure what it is you need. You say you do not need the information to carry over between game sessions. That’s exactly what you’d use PlayerPrefs or a save file for. If you only need the information to carry over between ingame scenes, make it static as well.
Edit: In C# we have null for empty references, and 0 as default value for numbers. Which, to my knowledge, is pretty common in object oriented languages.
Having re-read that I realised it wasn’t super clear, sorry. Basically, I am trying to save information (e.g. player position, decisions) to a file. I do need access to this saved data, but I don’t load it in the game - as in the save file once generated is never used by the game, but the save file itself is valuable to me outside of the game. At the moment I’m just generating a .txt file, the reason I didn’t think PlayerPrefs was appropriate was simply because it was saving to the registry instead of a file which was just making extra work for myself in accessing it.
I think the original question of this thread has been answered so I’m going to mark it as resolved, and seriously thank you so much Yoreki for your help, I really appreciate it.
It did raise another question though, I know I can resolve the problem on my own but I’m more looking to actually understand the solution, so if you’re not sick of answering my questions and helping me I thought I’d leave it here.
Say I have the following code:
public class TrialControl : MonoBehaviour
{
public static int trialN = 10; //Number of trials
public int trialCounter = 0;
public static bool sessionComplete;
}
In Start(), I set sessionComplete = false, then in OnTriggerEnter() I have the if statements that increment trialCounter as previously posted.
If I then have something in OnTriggerEnter() that will check whether trialCounter is at some arbitrary limit like this:
if (other.CompareTag("sometag"))
{
if (trialCounter == trialN)
{
sessionComplete = true;
}
}
}
my understanding is that if I were to try and access sessionComplete in another script, I would get a null value as the changes I make to it in Start and OnTriggerEnter aren’t overwriting the original sessionComplete variable but instead making a copy of it in that function. And further, if I were to try and read the value of sessionComplete in OnTriggerEnter(), it would also be null instead of false for the same reason.
If I initialised it in a static class at the top of the script this would solve the problem and allow me to access the updated bool value both by other methods within the script and by other scripts, but this doesn’t seem very clean. I’m just wondering if there is a better/cleaner way to change the value of a variable that is initialised at the start of the script in a way so that the updated value is accessible both to other methods within the script and by other scripts.
You should see how many one-line questions we get. People not using code tags. People not posting error messages, or even describing their problems. Useless titles. People expecting free scripts as replies. And so much more.
Really, helping someone who at least puts some effort into posting their questions is all one can ask for
Firstly about null:
Null would only be returned for reference types (so objects), if the reference does not exist yet / anymore. Value types (integers, floats, bools, structs, …) have default values they contain if the value was never set before. For numbers this is 0, for bools it is false. So if anything, since sessionComplete is a bool, it’d be its default value of ‘false’ if it was never set.
Secondly about variables and scopes:
The short version is that any place that has access to a variable can change that. The long version follows. There is a concept called scope. Effectively a scope is defined by the curley brackets we use all over the place {}. Your classes have a scope, namespaces have a scope, methods and even if-statements or for-loops all have their own scope. These scopes can be nested. For example, a method scope is always contained in a class scope. You always have access to variables (and other things, such as methods) defined in overlying scopes, as well as your own. This is why a method can access class variables such as sessionComplete and change them. Thus yes, the method can change the actual value of the variable, since it has access to it. What you probably meant is that the value changes to variables in a scope get lost once you exit this scope. An example probably helps:
// Class variable
private int a = 1;
public void MOne(){
a = 2; // this works, it changes the class variable
}
public void MTwo(){
int b = 42;
// does some other stuff here..
if(true){
b = 43; // this works as b was defined in a known scope
}
}
public void MThree(){
b = 0; // Even assuming MTwo was called before this, this would throw a compiler error, since the variable b does not exist in any scope known to MThree.
}
public void Main(){
MOne();
// a is still 2 at this point and stays so until changed
MTwo();
// b also does not exist here, as we already left the scope it was defined in
MThree();
}
As long as you have access to a variable, you can change that. If we have access to a class reference, we can change their public variables. If, however, we return a value in a method, that would be a copy of the returned value. In the end it comes down whether you are working on a reference or on a value type that’s not a member of a class. I hope this makes sense. It’s a bit hard to summarize in a few sentences. I recommend looking up value vs reference types as well as scopes if you got some time and need clarification.
Just to make sure; no any changes you do to sessionComplete will be known to any other script that may access it. Even more so since it’s static, which by definition means that there can only exist one value for this variable in memory.
That’s exactly what static does. If you only wanted to access it inside your own class, you would not need static. Like in my example above, where each of the methods could access the private non-static member variable ‘a’ and change it. There are many use cases for static, but as i mentioned it causes the variable to only be saved once in memory, instead of once per instance of the class. It is thus bound to the class itself instead of any instance, which is why you can get it by writing ClassName.someStaticVariable. Let’s assume you had an “Enemy” class with a MAX_HP value and a currentHP value. The MAX_HP would be the same for all enemies, thus you’d make it static. You’d now be able to write Enemy.MAX_HP, but not Enemy.currentHP, since currentHP is bound to each instance of a class and the value also exists once per instance (so 1000 times for 1000 enemies).
(Assuming MAX_HP, as the name implies, cannot be changed, we’d actually make it ‘const’ for constant tho)
Is there a reason to your question? Were you having troubles with getting your sessionComplete in other scripts?
EDIT: I just tested this (the problem I mention below) and it was working fine - I guess whatever issue I was having was being caused by something else entirely, but it was frustratingly simple to create a static int in one script, change it’s value in Start(), then call on this updated value from another script in OnTriggerEnter(). Either way, problem solved, thanks heaps
I think I am getting lost a bit in the terminology and I really just need to take the time to look at some more C# primers. I think I understood you up until this point, based on what you said it sounds like the problem I am experiencing is because I am changing a variable within a method i.e. making a copy of the variable, and losing this change once I “exit” the scope of the method. I’ll definitely have a look at your recommendations and hopefully it’ll all click for me.
It’s possible I just haven’t articulated my problem very well. Using the code you provided as an example, What I am trying to do would be to initialise a = 1 (although in my case it would have been a static int), change the value of a in MOne() to 2, then in Main() when I call on a I would want it to return a value of 2 (so for example, print(a) or Console.WriteLine(a) should return 2).
The specific problem I am having is that I have no issue calling on “a” in other methods or scripts, what I am having trouble with is returning the value of “a” as 2 instead of the initial value of 1 after I try and update the value of “a” within a method scope that is not accessible by other methods.
It definitely did help a lot, thanks. I still don’t completely get it but I think you’ve given me a bit more direction into what I should try to understand to get this working.