FlowCanvas Forums › Support › two-way binding properties node.
Tagged: binding way
Hi guys,
I have created a custom node ‘TweenPosition’ with different properties (look image attachment). When the node is selected then I want change for example the value of property Y via script.
This is my attempt:
1 2 3 4 5 6 7 8 9 10 |
[ContextMenu("Test Change Value Node")] public void TestChangeValueNode() { Node activeNode = GraphEditorUtility.activeNode; TweenPosition node = activeNode as TweenPosition; ValueInput<float> port = node.GetInputPort("y") as ValueInput<float>; port.SetDefaultAndSerializedValue(float.Parse(input.text)); } |
I don’t know if this is the right way to do that because in this way I’m changing the default value and not the current value.
Anyway, I want to edit this value both ways (two-way binding).
I need to do that because I want to show current gameobject status on which the selected node is working in editor mode.
Any suggestions?
Thank you
Giuseppe
Hello there,
Using the [ContextMenu] attribute is totaly valid within nodes like you do, but to change the “current value” please dont use the “SetDefaultAndSerializedValue”. Simply set the “.serializedValue” property. Setting this property will actually only set the serialized value that you see in the inspector and leave the defaultValue unchanged.
“SetDefaultAndSerializedValue” is just a shortcut to setting both “.defaultValue” and “.serializedValue” properties :).
Please let me know if that works for you.
Thank you.
Join us on Discord: https://discord.gg/97q2Rjh
Thanks, It’s works!
I want that user can editing some variables of node through the tools of support.
So, I have created a node custom “TweenPosition” for create an animation of an object in a determinate position (To). You can look my example here:
If you can look at my code I would like to know if it is the correct way to achieve this goal.Basically what I do that when the node is selected I then transfer the reference of the “to” field of the node to an internal my class that serves to manage the waypoint on the scene.
This class is my custom flownode
Tweenposition.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
using NodeCanvas.Editor; using NodeCanvas.Framework; using ParadoxNotion.Design; using UnityEditor; using UnityEngine; namespace FlowCanvas.Nodes { [Name("Tween Position")] [Category("ActionSwipeStory")] [Description("crea effetto transizione")] public class TweenPosition : FlowControlNode { ValueInput<GameObject> obj; ValueInput<Vector3> to; protected override void RegisterPorts() { obj = AddValueInput<GameObject>("object"); to = AddValueInput<Vector3>("to"); var t = AddValueInput<float>("time"); var start = AddFlowOutput("startEvent"); var end = AddFlowOutput("finishEvent"); AddFlowInput("In", (f) => { Debug.Log("Tween position"); start.Call(f); LeanTween.move(obj.value, to.value, t.value).setOnComplete(() => { end.Call(f); }); }); } protected override void OnNodePicked() { if (obj.value != null) { ConfigurationPlaceholder(); GraphEditorUtility.onActiveElementChanged += onActiveElementChanged; } } void ConfigurationPlaceholder() { Debug.Log("ConfigurationPlaceholder"); if (obj.value != null) { TweenPositionMonoB action = obj.value.GetComponent<TweenPositionMonoB>(); if (action == null) { action = obj.value.AddComponent<TweenPositionMonoB>(); } action.to = to; Selection.activeGameObject = obj.value; } } void OnNodeUnPicked() { Selection.activeGameObject = null; } void onActiveElementChanged(IGraphElement node) { if (node != this) { OnNodeUnPicked(); GraphEditorUtility.onActiveElementChanged -= onActiveElementChanged; } } protected override void OnNodeGUI() { base.OnNodeGUI(); } protected override void OnNodeReleased() { Debug.Log("Nodo rilasciato"); } } } |
This class is useful to create the placeholder waypoint on the object on which the node works
TweenPositionEditor.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; [CustomEditor(typeof(TweenPositionMonoB))] public class TweenPositionEditor : Editor { void OnSceneGUI() { var t = target as TweenPositionMonoB; if (t.to != null) { t.to.serializedValue = Handles.FreeMoveHandle(t.to.value, Quaternion.identity, 0.7f, Vector2.zero, Handles.CylinderHandleCap); Handles.DrawLine(t.transform.localPosition, t.to.value); } } } |
This class does the through to share the variable position of the node “Tweenposition.cs” with the class “TweenPositionEditor.cs “that manage the waypoint drawn the handles on the Scenaview
TweenPositionMonoB.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System.Collections; using System.Collections.Generic; using FlowCanvas; using UnityEngine; public class TweenPositionMonoB : MonoBehaviour { public ValueInput<Vector3> to; // Use this for initialization void Start() { } } |
If I may I would ask you another advice. I would like to know when the Serializevalue changes value. I thought I’d extend the class Valueinput<T> and overwrite the serializedvalue set method to launch an event when its value changes from the previous.
Regarding the advice I asked you about how I could listen to the change in the value of an ValueInput, I extended the ValueInput class this way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using FlowCanvas; using NodeCanvas.Editor; using NodeCanvas.Framework; using ParadoxNotion.Design; using UnityEditor; using UnityEngine; namespace MyProject.FlowCanvas { public class ObservableValueInput<T> : global::FlowCanvas.ValueInput<T> { public ObservableValueInput() { } public ObservableValueInput(FlowNode parent, string name, string ID) : base(parent, name, ID) { } public event System.Action<T, T> onValueChanged; public override object serializedValue { get { return base.serializedValue; } set { System.Object oldvalue = base.serializedValue; base.serializedValue = value; if (oldvalue != value) { if (onValueChanged != null) { onValueChanged((T)oldvalue, (T)value); } } } } } } |
I also had to extend the FlowControlNode class by adding the AddObservableValueInput method. The problem that to do this I had to modify the class changing the access level of the QualifyPortNameAndID method and the inputPorts variable from private to protected. How can I achieve the same result without modifying your framework? I do not want to have to report my changes to your framework every time when there is a new update
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using FlowCanvas; using NodeCanvas.Editor; using NodeCanvas.Framework; using ParadoxNotion.Design; using UnityEditor; using UnityEngine; namespace MyProject.FlowCanvas { /// <summary> /// To be able to extend the class correctly, you have changed the visibility levels to the parent class of the source framework: /// inputPorts da private a protected /// QualitiPortNameAndId da private a protected /// /// /// /// </summary> abstract public class FlowControlNode :FlowCanvas.Nodes.FlowControlNode { /// <summary> /// Consente di poter aggiungere al nodo una input port di tipo ObservableValueInput /// in cui ci si può mettere in ascolto quando i suo valore cambia /// </summary> /// <param name="name">porta</param> /// <param name="ID"></param> /// <typeparam name="T"></typeparam> /// <returns></returns> public ObservableValueInput<T> AddObservableValueInput<T>(string name, string ID = "") { QualifyPortNameAndID(ref name, ref ID, inputPorts); return (ObservableValueInput<T>)(inputPorts[ID] = new ObservableValueInput<T>(this, name, ID)); } protected override void OnNodePicked() { GraphEditorUtility.onActiveElementChanged += onActiveElementChanged; } protected abstract void OnNodeUnPicked(); void onActiveElementChanged(IGraphElement node) { if (node != this) { OnNodeUnPicked(); GraphEditorUtility.onActiveElementChanged -= onActiveElementChanged; } } } } |
Hello and sorry for the late reply.
Hmm, regarding the ObservableInput, probably the best way right now, would be to make the FlowNode class a “partial” class and as such, you will be able to provide extra registration methods (or other functionality as well) without changing the source code itself. If making FlowNode a partial class works for your use case, I will gladly make it partial officially for the next version.
Furthermore and based on your use case, I am thinking that maybe an OnSceneGUI callback could be added for nodes, so that editor specific utilities like Handles.PositionHandle can be used much easier.
Let me know what you think on the above.
Thank you.
Join us on Discord: https://discord.gg/97q2Rjh
Thank you for you advices.
I do your flownode class a partial class and it’s works and more easy extended flownode. So I will gladly when you make it partial officially for the next version 🙂
Furthermore for my use case I have followed your advice for extend my nodo with a custom method OnSceneGUI
1 2 3 4 5 6 7 8 |
public class MyNode: FlowControlNode { public ObservableValueInput<GameObject> subject; ... virtual public void OnSceneGUI () { } ... } |
To do this when the node is selected I add to the reference subject gameobject a support class “ActionsceneGUI”
1 2 3 4 5 6 7 8 9 |
protected override void OnNodePicked () { if(subject.value!=null){ ActionSceneGUI actionGUI = subject.value.GetComponent<ActionSceneGUI> (); if (actionGUI == null) subject.value.AddComponent<ActionSceneGUI> (); } } |
So to ActionSceneGUI I associate a class ActionGUIEditor that extends Editor and invokes the OnsceneGUI method based on the selected node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[CustomEditor (typeof (ActionSceneGUI))] public class ActionSceneGUIEditor : Editor { void OnSceneGUI () { var t = target as ActionSceneGUI; if (NodeCanvas.Editor.GraphEditorUtility.activeNode != null) { if (NodeCanvas.Editor.GraphEditorUtility.activeNode is MyNode) { var node = NodeCanvas.Editor.GraphEditorUtility.activeNode as MyNode; if (t.gameObject == node.subject.value) { node.OnSceneGUI (); } } } } } |
This avoids me writing a lot of code around. Thanks for you advice 🙂 . I was wondering if you have any more optimal solutions or if it may be enough so
Hello again,
Thanks for the follow up. I am glad that it works for you. I will make FlowNode partial in the next version, no problem 🙂
Regarding OnSceneGUI, I am thinking of adding a virtual “OnSceneGUISelected” in the base “Node” class, so that it’s easy for people to simply override and work with SceneGUI without a lot of fuss 🙂 It will basically do prety much what you are already doing, but more tight with the rest of the framework. Until the next version (v3.0) is out though, I think your solution is fine as it is 🙂
Thanks!
Join us on Discord: https://discord.gg/97q2Rjh