Unity Slider Value not cooperating with being set - c#

I have two scripts that I'm currently having problem with. One is attached directly to a slider, which depending on its configuration will either send a slider value to the Options for music or sound.
In the Unity editor, I have it so whenever the options menu is brought up, run the OptionsLevel() script on both of the sliders to set them to the value saved in my options. I have no errors not being able to find the options, however the sliders do not move themselves to the value I am trying to set them too.
As far as I can tell, this should work.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
public class SetVolume : MonoBehaviour
{
public AudioMixer Mixer;
private GameObject canvas;
[SerializeField] public enum Master { MusicVol, SoundVol }
public Master master;
public Slider slider;
public void OptionsLevel() //Call this when opening menu, changesss the slide layout to what already exits in the options
{
if (master.ToString() == "SoundVol")
{
Debug.Log("I am being called sound");
gameObject.GetComponent<Slider>().value = Options.Instance.GetSoundVolume(); //SETS VALUE of Slider, to value in Options
}
if (master.ToString() == "MusicVol")
{
Debug.Log("I am being called music");
slider.value = Options.Instance.GetMusicVolume();
}
}
public void SetLevel(float sliderValue)
{
Mixer.SetFloat(master.ToString(), Mathf.Log10(sliderValue) * 20); //Reads in the slider value and changes the mixer whenever the slider is changed
if (master.ToString() == "SoundVol") //if sound then set the sound volume in the settings
{
Options.Instance.SetSoundVolume(sliderValue);
}
if (master.ToString() == "MusicVol") //if the slider is configured for music it sends a copy of that value to the options
{
Options.Instance.SetMusicVolume(sliderValue);
}
}
}
my options script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Options : MonoBehaviour
{
public float musicFxVolume; // used to measure the value of sliders
public float soundFxVolume;
public float musicRealVolume;
public float soundRealVolume; //used to measure the actual volume level of the mixer
#region Singleton
public static Options Instance;
private void Awake()
{
SetUpSingleton();
Instance = this;
}
private void SetUpSingleton()
{
int numberOptions = FindObjectsOfType<Options>().Length;
if (numberOptions > 1)
{
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
#endregion
public void SetSoundVolume(float volume) //when called this will make a copy of the volume
{
soundFxVolume = volume;
soundRealVolume = Mathf.Log10(volume) * 20; //and make a copy of the volume sent to the mixuer
Debug.Log("Setting the sound volume");
}
public void SetMusicVolume(float volume)
{
musicFxVolume = volume; //just store the value
musicRealVolume = Mathf.Log10(volume) * 20;
Debug.Log("Setting the music volume");
}
public float GetMusicVolume()
{
return musicFxVolume; //returns the value of the copied slider
}
public float GetSoundVolume()
{
return soundFxVolume;
}
}
I'm using Unity 2018.3.14f1, if that matters.

Related

.GetComponent Function Makes my Variable = 0

StripeNum is on a button and the colour function is set to the same colour button and using Debug.Log it shows stripe1 = 20. But in the second script Debug.Log stripe1.stripe1 = 0. Im just referencing the game object the script is on (the same gameobject is used as the object for the button) Image of The Console Showing Debug.Logs
using UnityEngine;
public class StripeNum : MonoBehaviour
{
public void black()
{
stripe1 = 0;
}
public void brown()
{
stripe1 = 10;
}
public void red()
{
stripe1 = 20;
Debug.Log(stripe1);
}
public void orange()
{
stripe1 = 30;
}
public void yellow()
{
stripe1 = 40;
}
public void green()
{
stripe1 = 50;
}
public void blue()
{
stripe1 = 60;
}
public void purple()
{
stripe1 = 70;
}
public void grey()
{
stripe1 = 80;
}
public void white()
{
stripe1 = 90;
}
public float stripe1;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class FinalCalc : MonoBehaviour
{
Scene c_scene;
float TwoStripesAdded;
float FinalR;
public Text FinalNum;
void Start(){
c_scene = SceneManager.GetActiveScene();
GameObject gameobject = GameObject.Find("GameObject");
StripeNum stripe1 = gameobject.GetComponent<StripeNum>();
Stripe2Num stripe2 = gameobject.GetComponent<Stripe2Num>();
Stripe3Num stripe3 = gameobject.GetComponent<Stripe3Num>();
Stripe4Num stripe4 = gameobject.GetComponent<Stripe4Num>();
if (c_scene.buildIndex == 6){
TwoStripesAdded = stripe1.stripe1 + stripe2.stripe1;
Debug.Log($"Added {stripe1.stripe1} and {stripe2.stripe1} and got {TwoStripesAdded}");
FinalR = TwoStripesAdded * stripe3.stripe1;
FinalNum.text = FinalR.ToString() + "Ω " + "±" + stripe4.stripe1 + "%";
}
}
}
Why not just name the class Stripe and have multiple instances of it? You can have empty child GameObjects and you can plop each stripe MonoBehaviour on the empty child. Then you can name the GameObject Stripe1, Stripe2, etc.
In Start() you can find those GameObjects, get the component attached to each, and cache those references so you don't have to keep re-getting them.
Aaaand actually in looking at your code I was assuming you were running this in Update, but you're running the check in Start. I don't know when or how you're setting colors, but if you're doing it in Play mode it's already too late, because this script won't check the colors.
You can't activate the methods until play mode (which is why I was asking you how you set it), so the only way to get a reading is if you set the value in Editor mode, but again that's not going to call a method.
Maybe what you're doing is setting the value in Editor mode that corresponds to a color?
Whatever the case, I think your data is not initialized when you check it, which is only once, before the first frame is drawn. If you're getting other debug messages then I need to know how you're getting. The Sum should be printed before anyone gets to set a value in Play mode.
Also a thought - you're checking the scene for some reason? If you set values and change scenes, all your objects are destroyed. The resistor calculator in one scene is a different instance than the one in scene 6 and will not have any values set on any stripes.

Destroying exact object

I am creating a small 2d game in which you need to survive. Each tree has its own strength = 5. When the player collides and presses the left mouse button then strength is -1 and player wood stat is +1. when the tree strength is equal or less than 0 then the tree is destroyed. Here is my code : (Question is after the code)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Stats: MonoBehaviour
{
//Player Stats
public float hp = 100;
public float wood = 0;
//Tree stats
public float treeLogStrenth = 5;
//Text
public Text woodText;
void Start ()
{
woodText.text = "0";
}
void Update ()
{
woodText.text = wood.ToString();
if (treeLogStrenth <= 0)
{
Destroy(GetComponent<PlayerCollisions>().;
}
}
}
here is another code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCollisions: MonoBehaviour
{
public void OnCollisionStay2D (Collision2D collisionInfo)
{
if (collisionInfo.gameObject.tag == "Tree" && Input.GetMouseButtonDown(0))
{
string treeName = collisionInfo.gameObject.name;
GetComponent<Stats>().wood += 1;
GetComponent<Stats>().treeLogStrenth -= 1;
}
}
}
MY QUESTION : How to make instead of creating all the time another game object for each tree and destroying it, only like one simple code that will do it. please help (UNITY NEWEST VERSION)
So to clear things up: Stats should be attached to the player, right?
You should not do things every frame in Update but rather event driven like
public class Stats : MonoBehaviour
{
// You should never allow your stats to be set via public fields
[SerializeField] private float hp = 100;
[SerializeField] private float wood = 0;
// if you need to read them from somewhere else you can use read-only properties
public float HP => hp;
public float Wood => wood;
[SerializeField] private Text woodText;
private void Start ()
{
woodText.text = "0";
}
public void AddWood(int amount)
{
wood += amount;
woodText.text = wood.ToString();
}
}
Then each tree should rather have its own component instance attached like e.g.
public class Tree : MonoBehaviour
{
[SerializeField] private float treeLogStrenth = 5;
public void HandleClick(Stats playerStats)
{
// if this tree has wood left add it to the player stats
if(treeLogStrength > 0)
{
playerStats.AddWood(1);
treeLogStrenth -= 1;
}
// destroy this tree when no wood left
if (treeLogStrenth <= 0)
{
Destroy(gameObject);
}
}
}
and then finally also attached to the Player
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store the currently collided tree in order to reuse it
private Tree currentlyCollidedTree;
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// Get the Tree component of the tree object you are currently colliding with
// but only once and store the reference in order to reuse it
if(!currentlyCollidedTree) currentlyCollidedTree= collisionInfo.gameObject.GetComponent<Tree>();
// tell the tree to handle a click and pass in your stats reference
currentlyCollidedTree.HandleClick(stats);
}
}
// reset the currentlyCollidedTree field when not colliding anymore
private void OnCollisionExit2D()
{
currentlyCollidedTree = null;
}
}
Well yes there would be an alternative where not every tree needs its own component instance but I would recommend to not use it actually!
You could make your player remember which tree he already clicked in something like
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store all trees we ever clicked on in relation to the according available wood
private Dictionary<GameObject, int> encounteredTrees = new Dictionary<GameObject, int>();
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// did we work on this tree before?
if(encounteredTrees.Contains(collisionInfo.gameObject))
{
// if so gain one wood and remove one from this tree
stats.AddWood(1);
encounteredTrees[collisionInfo.gameObject] -= 1;
// destroy the tree if no wood available and remove it from the dictionary
if(encounteredTrees[collisionInfo.gameObject] <= 0)
{
encounteredTrees.RemoveKey(collisionInfo.gameObject);
Destroy(collisionInfo.gameObject);
}
}
else
{
// the first time we work this tree gain one wood and add
// the tree as new entry to the dictionary with 4 wood left
stats.AddWood(1);
encounteredTrees.Add(collisionInfo.gameObject, 4);
}
}
}
}
this however limits you extremely and it is not possible anymore that you have e.g. different Tree prefabs with different amount of available wood ...

How can I create a custom inspector guilayout.toggle?

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
Control control = (Control)target;
if (GUILayout.Toggle(control.isControl, "Control"))
{
control.ToControl();
}
}
}
And
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl = false;
// Start is called before the first frame update
void Start()
{
}
public void ToControl()
{
if(isControl == false)
{
}
else
{
Destroy(rigidbody);
}
}
}
What I want to do is a guilayout.toggle or a button and to be able to destroy and to add a Rigidbody to the gameobject the Control script will be on.
How do I create back add the Rigidbody to the gameobject ?
And how do I use the isControl flag ? The idea is to use the guilayout.toggle in the editor script.
I want to destroy or add a new rigidbody while the game is running ! But using a guilayout.toggle or button in the inspector.
Actually you wouldn't need an inspector script for that at all. Simply add a repeadetly check for the bool like e.g. in LateUpdate and make the component [ExecuteInEditoMode] like
using UnityEngine;
[ExecuteInEditoMode]
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl;
// repeatedly check the bool
private void LateUpdate()
{
ToControl();
}
public void ToControl()
{
if (!isControl && rigidbody)
{
// in editmode use DestroyImmediate
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(rigidbody);
}
else
{
Destroy(rigidbody);
}
rigidbody = null;
}
else if(isControl && !rigidbody)
{
rigidbody = gameObject.AddComponent<Rigidbody>();
// adjust settings of rigidbody
}
}
}
This way LateUpdate is called both, in playmode and in editmode, and will simply react to the isControl value.
Ofcourse there is an overhead for calling this LateUpdate all the time so if you want to avoid it you can call it only from the editor. However, since you are using base.OnInspectorGUI(); you don't really need an additional Toggle since you already have the one of the default inspector.
So could simply do
using UnityEngine;
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl;
public void ToControl()
{
if (!isControl && rigidbody)
{
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(rigidbody);
}
else
{
Destroy(rigidbody);
}
rigidbody = null;
}
else if(isControl && !rigidbody)
{
rigidbody = gameObject.AddComponent<Rigidbody>();
}
}
}
and in the editor script simply do
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (!control.isControl && control.rigidbody)
{
control.ToControl();
Repaint();
}
else if (control.isControl && !control.rigidbody)
{
control.ToControl();
Repaint();
}
}
}
BUT you will already notice that this might affect how Undo/Redo works - in this case it would e.g. reset the isControl value but not remove along the RigidBody component leading to errors (see more below)
Or since you asked it you can add the ToggleField (currently you will have it twice since one also ships with base.OnInspectorGUI();)
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
control.isControl = EditorGUILayout.Toggle("additional isControl", control.isControl);
if (!control.isControl && control.rigidbody)
{
control.ToControl();
Repaint();
}
else if (control.isControl && !control.rigidbody)
{
control.ToControl();
Repaint();
}
}
}
BUT you will notice that this solution changing the value using the additional isControl lacks the possibility of using Undo/Redo completely and it will NOT mark your scene as "dirty" so Unity might not save those changes!
So if you really want to have your custom toggle field in an inspector script I would actually recommend strongly to use proper SerializedPropertys instead of directly making changes to the target (sometimes it can't be avoided like with the adding of the component though):
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private SerializedProperty _isControl;
private SerializedProperty rigidbody;
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
// link serialized property
_isControl = serializedObject.FindProperty("isControl");
rigidbody = serializedObject.FindProperty("rigidbody");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
// load current values into the serialized copy
serializedObject.Update();
if (!_isControl.boolValue && rigidbody.objectReferenceValue)
{
DestroyImmediate(rigidbody.objectReferenceValue);
rigidbody.objectReferenceValue = null;
}
else if (_isControl.boolValue && !rigidbody.objectReferenceValue)
{
var rb = control.gameObject.AddComponent<Rigidbody>();
rigidbody.objectReferenceValue = rb;
}
// write back changed serialized values to the actual values
serializedObject.ApplyModifiedProperties();
}
}
This looks more complicated and actually you have duplicate code but it gives you full Undo/Redo support and marks your objects and scenes dirty so Unity saves the changes.

Unity 3D Attaching Score Display Script to prefab

I was following Unity 3d tutorial on the Learn Unity website, but here is the thing I wanted to do things a bit differently. It worked out well at start but in the end this turned out to be a bad decision and now I manually need to attach the script to every pickable object.
Here is my code:
Note: What it does is rotate the Pickups and display the score when the pickups collide with player ball.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PickUps : MonoBehaviour {
public Vector3 Rotate;
private int Score;
public Text ScoreGUI;
private void Start()
{
Rotate = new Vector3(0, 25, 0);
Score = 0;
DisplayScore();
}
void Update () {
transform.Rotate(Rotate*Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Ball"))
{
Destroy(this.gameObject);
Score = Score + 1;
DisplayScore();
}
}
void DisplayScore()
{
ScoreGUI.text = "SCORE " + Score.ToString();
}
}
Problem:
It works yes but I need to manually attach the text (under canvas) to every pickup object which is exhausting and not a good thing to do.
What I want To achieve:
Like in the tutorials mostly they use prefabs in this kind of work (I think), problem is I can attach the text to the pickups (objects/biscuits) in the current scene but I cannot drag and attach the text To the prefab of biscuits I made the text just wont attach in its blank for "Text".
You shouldn't change the score Text directly. Use a Controller to make the bridge instead. I would do something like this:
Put this script somewhere in your scene:
public class ScoreManager : Singleton<ScoreManager>
{
private int score = 0;
// Event that will be called everytime the score's changed
public static Action<int> OnScoreChanged;
public void SetScore(int score)
{
this.score = score;
InvokeOnScoreChanged();
}
public void AddScore(int score)
{
this.score += score;
InvokeOnScoreChanged();
}
// Tells to the listeners that the score's changed
private void InvokeOnScoreChanged()
{
if(OnScoreChanged != null)
{
OnScoreChanged(score);
}
}
}
This script attached in the Text game object:
[RequireComponent(typeof(Text))]
public class ScoreText : MonoBehaviour
{
private Text scoreText;
private void Awake()
{
scoreText = GetComponent<Text>();
RegisterEvents();
}
private void OnDestroy()
{
UnregisterEvents();
}
private void RegisterEvents()
{
// Register the listener to the manager's event
ScoreManager.OnScoreChanged += HandleOnScoreChanged;
}
private void UnregisterEvents()
{
// Unregister the listener
ScoreManager.OnScoreChanged -= HandleOnScoreChanged;
}
private void HandleOnScoreChanged(int newScore)
{
scoreText.text = newScore.ToString();
}
}
And in your PickUps class:
void DisplayScore()
{
ScoreManager.Instance.SetScore(Score); // Maybe what you need is AddScore to not
// reset the value everytime
}
A simple singleton you can use (you can find more complete ones on the internet):
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null) Debug.LogError("Singleton of type " + typeof(T).ToString() + " not found in the scene.");
}
return instance;
}
}
}
But be careful, the singleton pattern can be a shot in the foot if not used correctly. You should only it them moderately for managers.

An Unity3D button appears to be null while trying to disable it

What I'm trying to do is disable the button when the time is up. But I get an error while I set the button to be disable: it appears to be null.
The following script (RightButton.cs) includes a function called DisableButton that is invoked by the main script (TappingEngine.cs).
The statement that hangs inside the DisableButton function is btn.interactable = false;.
/* RightButton.cs */
using UnityEngine;
using UnityEngine.UI;
public class RightButton : MonoBehaviour {
Button btn;
// Use this for initialization
void Start () {
Debug.Log("Right button ready.");
}
public void Tap()
{
Debug.Log("Tap right! tot: " + TappingEngine.te.nTaps);
TappingEngine.te.Tap(TappingEngine.tapType_t.right);
}
public void DisableButton()
{
btn = this.GetComponent<Button>();
btn.interactable = false;
}
}
The following code is a part of the main script (which is quite long). However, someone advised me to post the full version for clarity.
(Focus on the two functions Update () and DisableButtons().)
/* TappingEngine.cs */
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class TappingEngine : MonoBehaviour {
public static TappingEngine te;
public int nTaps = 0;
public int GlobalTotal;
public int RightTaps;
public int LeftTaps;
public string FirstTap;
public int LastTap;
public int MaxChain;
public int FastestTwoTaps;
public int FastestTwoChainedTaps;
public GlobalTotalTaps GlobalTotalTaps_script;
public TotalRightTaps RightTotalTaps_script;
public TotalLeftTaps LeftTotalTaps_script;
public FirstTap FirstTap_script;
public RightButton RightButton_script;
const float TimeSpan = 5F;
public float Chrono;
public bool ChronoStart = false;
public bool ChronoFinished = false;
public float ElapsedPercent = 0F;
public enum tapType_t { right, left };
// Use this for initialization
void Start () {
GlobalTotal = 0;
te = this;
GlobalTotalTaps_script = FindObjectOfType(typeof(GlobalTotalTaps)) as GlobalTotalTaps;
RightTotalTaps_script = FindObjectOfType(typeof(TotalRightTaps)) as TotalRightTaps;
LeftTotalTaps_script = FindObjectOfType(typeof(TotalLeftTaps)) as TotalLeftTaps;
FirstTap_script = FindObjectOfType(typeof(FirstTap)) as FirstTap;
RightButton_script = FindObjectOfType(typeof(RightButton)) as RightButton;
}
// Update is called once per frame
void Update () {
if(ChronoStart) {
ElapsedPercent = (Time.time - Chrono) * 100 / TimeSpan;
if(ElapsedPercent >= 100F) {
DisableButtons();
//SceneManager.LoadScene("Conclusion", LoadSceneMode.Single);
}
}
Debug.Log("Elapsed time: " + (Time.time - Chrono));
}
private void DisableButtons()
{
RightButton_script.DisableButton();
}
public void OnTap() {
Debug.Log("Tap!");
}
public void Tap(tapType_t tapType) {
Manage_GlobalTotalTaps();
if(GlobalTotal == 1) {
Manage_FirstTap(tapType);
ChronoStart = true;
Chrono = Time.time;
}
Manage_LeftRight(tapType);
}
private void Manage_FirstTap(tapType_t tapType)
{
FirstTap = tapType.ToString();
FirstTap_script.FastUpdate();
}
private void Manage_LeftRight(tapType_t tapType)
{
if (tapType.Equals(tapType_t.left))
{
LeftTaps++;
LeftTotalTaps_script.FastUpdate();
}
else
{
RightTaps++;
RightTotalTaps_script.FastUpdate();
}
}
public void Manage_GlobalTotalTaps() {
GlobalTotal++;
GlobalTotalTaps_script.FastUpdate();
}
}
Below are a couple of screenshots that I hope will be useful.
Until now I have not yet figured out where the problem is. Moreover, the script of the button is very similar to that of the various Text objects that represent the statistics (the list on the right side of the screen).
With Text objects it works, with the Button object not.
I asked the same question on Unity3D Forum and I got the solution here.
One the game object where I attached the RightButton.cs there is a variable on line 24 Button btn; that needs to be set to public, so public Button btn;.
This makes appearing a new entry in the Inspector of the Right Button object (see the image below) and so in Unity3D drag and drop the Right Button GameObject into Right Button (Script)'s proper field (Btn in this case).

Categories