Unity - passing a param along with value in onValueChanged - unity3d

Ok, so, a Slider in Unity has an "On Value Changed (Single)" property. As far as I've managed so far, you can set on it a callback function that can take a parameter (e.g. a string) which you hardcode in the Inspector, and/or a function that takes a float (the new value that the slider has been set to). However, I'd like to be able to pass both, and I haven't seen a way to do it. For example, I'd like to have a function saveValue(string name, float value), and several sliders feeding their values into it - each slider would have a different name written in the Inspector, and the updated value would be passed as value. I suspect Unity does not support that, but I haven't found any documentation explicitly describing this feature, so I'm not sure. Can this be done?

This should get you started:
Create a class that all of your sliders can access (for example by reference). And put the method you want them to call in there. The method should take the two parameters that you want to use (in my case I want it to take a GameObject and a float):
public class SliderManagerScript : MonoBehaviour
{
public void SliderValueChangeHandler(GameObject slider, float value)
{
Debug.Log("Slider name: " + slider.gameObject.transform.name);
Debug.Log("Slider value: " + value);
}
}
On each of the sliders that you want to call the above method, add a script that can access the above class and add an event listener for the Slider's onValueChanged property. Register a delegate for that listener which calls the method shown above and passes in the appropriate values. E.g
public class SliderScript : MonoBehaviour
{
public GameObject sliderManager;
void Start()
{
GetComponent<Slider>().onValueChanged.AddListener(delegate { sliderManager.GetComponent<SliderManagerScript>().SliderValueChangeHandler(this.gameObject, this.GetComponent<Slider>().value); });
}
}
And that should work fine. In fact, you should be able to simplify it a lot more depending on how you've architected your code.

Related

Unity3D: Custom UnityEvent AddListener not firing

I have my own custom UnityEvent and am trying to add a listener.
I have used AddListener on numerous other UI objects, such as buttons, dropdowns, toggles, etc. so I understand the process. However, when I Invoke my UnityEvent, it simply doesn't fire.
I'm receiving no error messages, and after doing reading and research, everything looks correct. So, not sure what to do further.
This is an object that emits when it's rotated.
This is the basics of my code:
using UnityEngine.Events;
public class Rotator: MonoBehaviour
{
public UnityEvent OnRotate;
int angle = 0;
int newAngle = 0;
void Start()
{
OnRotate = new UnityEvent();
}
void Update()
{
newAngle = (int)transform.rotation.eulersAngles.z;
if (newAngle != angle)
{
print ("Actual Instance ID: " + GetInstanceID());
print ("Invoking!");
OnRotate.Invoke();
angle = newAngle;
}
}
}
and
public class Owner: MonoBehaviour
{
public Rotator rotator;
void Start()
{
print ("Rotator Instance ID: " + rotator.GetInstanceID());
rotator.OnRotate.AddListener(
() => UpdateRotation()
);
}
void UpdateRotation()
{
print ("Invoked!");
}
}
When the Rotator has it's angle changed, I get this in the console:
Actual Instance ID: 11234
Rotator Instance ID: 11234
Invoking!
The instance ID is to make sure I'm working with the same objects and not going in circles for nothing. They match, so I'm listening to the object that's firing.
However, the listener isn't firing. I've tried different combinations with delegates, etc. but it's all the same. No errors. It just doesn't invoke.
Obviously, I'm doing something wrong, but what is it?
Thanks for any help.
Somehow your answered your new edited version of the question with exactly the code you previously provided in the First Version of your Question!
As I tried to tell you ... if you anywhere in your code do OnRotate = new UnityEvent() of course you thereby erase any persistent callbacks and any runtime callbacks added before that moment!
In short
Simply leave it as
public UnityEvent OnRotate;
and you don't even have to think about it anymore.
For understanding why it also works if you put it in Awake please simply have a look at the Order of Execution for Event Functions
&rightarrow; First Awake and OnEnabled is called for every GameObject/Component. Then all Start methods are called as soon as the GameObject/Component is active.
Within each of these blocks (Awake + OnEnable) and (Start) the order of execution between different component types is not guaranteed unless you explicitly configure it via the Script Execution Order Settings where you could define that Owner is simply run before Rotator .. then having both in Start would also work again.
Why does it also work if you do it on the public field?
&rightarrow; Because this field is serialized. That means it is initialized automatically in the Inspector and then stored together with the Scene or prefab asset including any persistent callbacks.
And then Later Unity re-uses the serialized Version of the field so actually you can completely remove the new UnityEvent(); since it doesn't have any effect on a serialized field! It will always be initialized automatically anyway!
Ok, I found out what the issue was.
My question now is "why?".
I changed my code from:
public UnityEvent OnRotate;
void Start() {
OnRotate = new UnityEvent();
}
to
public UnityEvent OnRotate = new UnityEvent();
void Start() {
}
And now it works.
Although, now that I think about it, Awake() is the method where they all fire before initialization, whereas Start() is when the object is created. So the Start() of the Rotator is probably getting called after the Owner is adding a listener.

Auto-Draggable Variables

I want to be able to drag & drop variables (such as floats, integers, maybe even classes) in the Inspector view. So that if I have FirstScript and SecondScript I could easily drag & drop variables between them so that they can change each others' data. I need this to create more modular workflow.
I know that ScriptableObjects allow similar functionality but I need to create them manually through Create button in Inspector. Instead I want this to happen automatically.
This should look something like scripts below. So that I could drag the original_float to float_to_change field right in the Inspector.
// First Component
public class FirstScript : MonoBehaviour
{
public DraggableFloat original_float = 5.0f;
}
// SecondComponent
public class SecondScript: MonoBehaviour
{
public DraggableFloat float_to_change;
public void ChangeFloat()
{
// The value of original_float is changed through reference
float_to_change += 10.0f;
}
}

How can I set a public variable using getChildIndex in as3?

I was coding a very simple program that lets you move around a circle, with also a rectangle in the stage. I wanted to make the circle get in front of the rectangle while you are dragging it, but when you released the mouse, the circle would be sent back.
I don't know how to set a public variable using the getChildIndex method. I don't really care about the rest of the code. I'm mainly interested in how can I make the getChildIndex method work with a public variable.
package code
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.Sprite;
public class Main extends MovieClip
{
public var myCircleIndex:int = getChildIndex(myCircle);
public function Main()
{
myCircle.addEventListener(MouseEvent.MOUSE_DOWN, mouseClicking);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
}
public function mouseClicking(e:MouseEvent): void
{
myCircle.startDrag();
setChildIndex(myCircle, numChildren-1);
}
public function mouseReleased(e:MouseEvent): void
{
myCircle.stopDrag();
setChildIndex(myCircle, myCircleIndex);
}
}
}
I'm using an instance ("myCircle") that I created directly in the stage as a movie clip.
The problem is in the public var I set at the beginning, it doesn't let me get the child index of myCircle, but if I put the same line inside a function, it works.
I know I could directly put the index number of myCircle in the last line (and erasing the public var myCircleIndex), but I figured out that there would be a way of using the getChildIndex for a public var in a class.
How do you use getChildIndex in a public variable inside a class?
The reason it doesn't work, is because your timeline objects don't yet exist when the line public var myCircleIndex:int runs.
You shouldn't try and access non-primitive objects in your class level variable declarations for this very reason, as nothing else in the class is available yet when those vars are created.
Here is how you can refactor this (see the code comments):
public class Main extends MovieClip
{
public var myCircleIndex:int; //just create the reference here, don't assign it
public var myCircle:flash.display.DisplayObject; //this line is just for better compile time checking and code completion, completely optional
public function Main()
{
//wait for all the display stuff to be created before trying to access it. The constructor function can run before timeline stuff is created, so it's not safe to reference stage or timeline objects here.
if(!stage){
this.addEventListener(Event.ADDED_TO_STAGE, timelineCreated);
}else {
timelineCreated(null);
}
}
private function timelineCreated(e:Event):void {
//now that we're certain the timeline stuff has been created, we can reference timeline objects and stage:
//store the initial z-index of myCircle
myCircleIndex = getChildIndex(myCircle);
//the rest of your code that was in the construction -Main()- before
myCircle.addEventListener(MouseEvent.MOUSE_DOWN, mouseClicking);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased);
}
//no change to any of the following stuff
public function mouseClicking(e:MouseEvent): void
{
myCircle.startDrag();
setChildIndex(myCircle, numChildren-1);
}
public function mouseReleased(e:MouseEvent): void
{
myCircle.stopDrag();
setChildIndex(myCircle, myCircleIndex);
}
}
All you need to do to put the circle behind the square is on release do addChild(myRectangle) or addChildAt(myCircle, 0);
You are overcomplicating things by trying to track a variable in my opinion. Let flash sort it out behind the scenes.
If you want a little more finesse and want to just put the circle directly behind the square (if there were 100 layers and the square is at level 12, but you aren't sure which level the square is at) you could do
addChildAt(myCircle, getChildIndex(myRectangle)-1);
note
setChildIndex(myCircle, numChildren-1);
That's fine to do it that way. The more common way to do this is just
addChild(myCircle);
It does the exact same thing. Many people are confused by this thinking this would add a new myCircle but it just brings it to the front if it's already in the display list, and if it's not in the display list, it adds it to the display list at the top z-order (numChildren-1).

IntSlider callback function? Editor Scripting

I just got into Editor Scripting recently and I'm still getting a hold of all the basics. I wanted to know if the IntSlider (that we use to create a slider of integer values) has any methods or functions that can be used to get more functionality?
Something like the Slider class:
You have -maxValue, -minValue, -value, -onValueChanged (Callback executed when the value of the slider is changed)
These work during the "Play" mode.
I need to access this information while in the editor. Specifically: I need to access the onValueChanged (if it exists) of the IntSlider so I can assign a function for it to execute.
IntSlider Code:
totalRooms = EditorGUILayout.IntSlider(new GUIContent ("Total Rooms"), totalRooms, 0, 10);
Is there a way to achieve this for the inspector? Or something that can be created to solve this? If you can point me in the right direction, I'd be grateful.
I'm using Unity 5.3.1f
There is no such event in Unity at the moment. However this can be implemented easily like this:
Add a UnityEvent in your actual script.
public UnityEvent OnVariablesValueChanged;
Then in your editor script check if value is changed. (Note: I tried to write easily understandable code sample for this. you can change it accordingly)
[CustomEditor(typeof(MyScript))]
public class MyScriptEditor : Editor
{
public override void OnInspectorGUI()
{
MyScript myTarget = (MyScript)target;
int prevValue = myTarget.variable;
myTarget.variable = EditorGUI.IntSlider(new Rect(0,0,100, 20), prevValue, 1, 10);
int newValue = myTarget.variable;
if (prevValue != newValue)
{
Debug.Log("New value of variable is :" + myTarget.variable);
myTarget.OnVariablesValueChanged.Invoke();
}
base.OnInspectorGUI();
}
}
Then add listener to OnVariablesValueChanged event from inspector.
Hope this helps.

How I can get uGUI event parameter?

I'm using new event system, represented in unity 4.6.
Here is example on attaching listener to Toggle component.
My question is: "How can I get Boolean parameter from On Value Changed(Boolean)?"
Currently I'm forced to keeping link of that Toggle object in code and checked new value from it.
public void OnVRtoggled() {
var value = toggleVR.isOn;
}
But I think that there must be way of getting boolean value from event. Is that possible?
In Unity 5.1.2 you just need to add a bool parameter in your callback method
public void OnToggledVR(bool isOn)
{
}