Node.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace XNode
  5. {
  6. /// <summary>
  7. /// Base class for all nodes
  8. /// </summary>
  9. /// <example>
  10. /// Classes extending this class will be considered as valid nodes by xNode.
  11. /// <code>
  12. /// [System.Serializable]
  13. /// public class Adder : Node {
  14. /// [Input] public float a;
  15. /// [Input] public float b;
  16. /// [Output] public float result;
  17. ///
  18. /// // GetValue should be overridden to return a value for any specified output port
  19. /// public override object GetValue(NodePort port) {
  20. /// return a + b;
  21. /// }
  22. /// }
  23. /// </code>
  24. /// </example>
  25. [Serializable]
  26. public abstract class Node : ScriptableObject
  27. {
  28. /// <summary> Used by <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> to determine when to display the field value associated with a <see cref="NodePort"/> </summary>
  29. public enum ShowBackingValue
  30. {
  31. /// <summary> Never show the backing value </summary>
  32. Never,
  33. /// <summary> Show the backing value only when the port does not have any active connections </summary>
  34. Unconnected,
  35. /// <summary> Always show the backing value </summary>
  36. Always
  37. }
  38. public enum ConnectionType
  39. {
  40. /// <summary> Allow multiple connections</summary>
  41. Multiple,
  42. /// <summary> always override the current connection </summary>
  43. Override,
  44. }
  45. /// <summary> Tells which types of input to allow </summary>
  46. public enum TypeConstraint
  47. {
  48. /// <summary> Allow all types of input</summary>
  49. None,
  50. /// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary>
  51. Inherited,
  52. /// <summary> Allow only similar types </summary>
  53. Strict,
  54. /// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
  55. InheritedInverse,
  56. }
  57. public virtual bool IsRemove()
  58. {
  59. return true;
  60. }
  61. #region Obsolete
  62. [Obsolete("Use DynamicPorts instead")]
  63. public IEnumerable<NodePort> InstancePorts
  64. {
  65. get { return DynamicPorts; }
  66. }
  67. [Obsolete("Use DynamicOutputs instead")]
  68. public IEnumerable<NodePort> InstanceOutputs
  69. {
  70. get { return DynamicOutputs; }
  71. }
  72. [Obsolete("Use DynamicInputs instead")]
  73. public IEnumerable<NodePort> InstanceInputs
  74. {
  75. get { return DynamicInputs; }
  76. }
  77. [Obsolete("Use AddDynamicInput instead")]
  78. public NodePort AddInstanceInput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
  79. string fieldName = null)
  80. {
  81. return AddDynamicInput(type, connectionType, typeConstraint, fieldName);
  82. }
  83. [Obsolete("Use AddDynamicOutput instead")]
  84. public NodePort AddInstanceOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
  85. string fieldName = null)
  86. {
  87. return AddDynamicOutput(type, connectionType, typeConstraint, fieldName);
  88. }
  89. [Obsolete("Use AddDynamicPort instead")]
  90. private NodePort AddInstancePort(Type type, NodePort.IO direction, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
  91. string fieldName = null)
  92. {
  93. return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
  94. }
  95. [Obsolete("Use RemoveDynamicPort instead")]
  96. public void RemoveInstancePort(string fieldName)
  97. {
  98. RemoveDynamicPort(fieldName);
  99. }
  100. [Obsolete("Use RemoveDynamicPort instead")]
  101. public void RemoveInstancePort(NodePort port)
  102. {
  103. RemoveDynamicPort(port);
  104. }
  105. [Obsolete("Use ClearDynamicPorts instead")]
  106. public void ClearInstancePorts()
  107. {
  108. ClearDynamicPorts();
  109. }
  110. #endregion
  111. /// <summary> Iterate over all ports on this node. </summary>
  112. public IEnumerable<NodePort> Ports
  113. {
  114. get
  115. {
  116. foreach (NodePort port in ports.Values) yield return port;
  117. }
  118. }
  119. /// <summary> Iterate over all outputs on this node. </summary>
  120. public IEnumerable<NodePort> Outputs
  121. {
  122. get
  123. {
  124. foreach (NodePort port in Ports)
  125. {
  126. if (port.IsOutput) yield return port;
  127. }
  128. }
  129. }
  130. /// <summary> Iterate over all inputs on this node. </summary>
  131. public IEnumerable<NodePort> Inputs
  132. {
  133. get
  134. {
  135. foreach (NodePort port in Ports)
  136. {
  137. if (port.IsInput) yield return port;
  138. }
  139. }
  140. }
  141. /// <summary> Iterate over all dynamic ports on this node. </summary>
  142. public IEnumerable<NodePort> DynamicPorts
  143. {
  144. get
  145. {
  146. foreach (NodePort port in Ports)
  147. {
  148. if (port.IsDynamic) yield return port;
  149. }
  150. }
  151. }
  152. /// <summary> Iterate over all dynamic outputs on this node. </summary>
  153. public IEnumerable<NodePort> DynamicOutputs
  154. {
  155. get
  156. {
  157. foreach (NodePort port in Ports)
  158. {
  159. if (port.IsDynamic && port.IsOutput) yield return port;
  160. }
  161. }
  162. }
  163. /// <summary> Iterate over all dynamic inputs on this node. </summary>
  164. public IEnumerable<NodePort> DynamicInputs
  165. {
  166. get
  167. {
  168. foreach (NodePort port in Ports)
  169. {
  170. if (port.IsDynamic && port.IsInput) yield return port;
  171. }
  172. }
  173. }
  174. /// <summary> Parent <see cref="NodeGraph"/> </summary>
  175. [SerializeField] public NodeGraph graph;
  176. /// <summary> Position on the <see cref="NodeGraph"/> </summary>
  177. [SerializeField] public Vector2 position;
  178. [NodeSerialize("GUID")] [SerializeField]
  179. public int guid;
  180. [HideInInspector] [SerializeField] private int uniqueID;
  181. /// <summary>
  182. /// 唯一ID,首次创建的时候赋值
  183. /// </summary>
  184. public int UniqueID
  185. {
  186. get
  187. {
  188. if (uniqueID == 0)
  189. {
  190. uniqueID = GetInstanceID();
  191. }
  192. return uniqueID;
  193. }
  194. set
  195. {
  196. if (UniqueID == 0)
  197. {
  198. uniqueID = value;
  199. }
  200. }
  201. }
  202. /// <summary>
  203. /// 复制组件的时候调用
  204. /// </summary>
  205. public void ResetUniqueID()
  206. {
  207. uniqueID = GetInstanceID();
  208. }
  209. /// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary>
  210. [SerializeField] private NodePortDictionary ports = new NodePortDictionary();
  211. /// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
  212. public static NodeGraph graphHotfix;
  213. protected void OnEnable()
  214. {
  215. if (graphHotfix != null) graph = graphHotfix;
  216. graphHotfix = null;
  217. UpdatePorts();
  218. Init();
  219. }
  220. /// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. </summary>
  221. public void UpdatePorts()
  222. {
  223. NodeDataCache.UpdatePorts(this, ports);
  224. }
  225. /// <summary> Initialize node. Called on enable. </summary>
  226. public virtual void Init()
  227. {
  228. }
  229. /// <summary> Checks all connections for invalid references, and removes them. </summary>
  230. public void VerifyConnections()
  231. {
  232. foreach (NodePort port in Ports) port.VerifyConnections();
  233. }
  234. #region Dynamic Ports
  235. /// <summary> Convenience function. </summary>
  236. /// <seealso cref="AddInstancePort"/>
  237. /// <seealso cref="AddInstanceOutput"/>
  238. public NodePort AddDynamicInput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null)
  239. {
  240. return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
  241. }
  242. /// <summary> Convenience function. </summary>
  243. /// <seealso cref="AddInstancePort"/>
  244. /// <seealso cref="AddInstanceInput"/>
  245. public NodePort AddDynamicOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
  246. string fieldName = null)
  247. {
  248. return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
  249. }
  250. /// <summary> Add a dynamic, serialized port to this node. </summary>
  251. /// <seealso cref="AddDynamicInput"/>
  252. /// <seealso cref="AddDynamicOutput"/>
  253. private NodePort AddDynamicPort(Type type, NodePort.IO direction, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
  254. string fieldName = null)
  255. {
  256. if (fieldName == null)
  257. {
  258. fieldName = "dynamicInput_0";
  259. int i = 0;
  260. while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
  261. }
  262. else if (HasPort(fieldName))
  263. {
  264. Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
  265. return ports[fieldName];
  266. }
  267. NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
  268. ports.Add(fieldName, port);
  269. return port;
  270. }
  271. /// <summary> Remove an dynamic port from the node </summary>
  272. public void RemoveDynamicPort(string fieldName)
  273. {
  274. NodePort dynamicPort = GetPort(fieldName);
  275. if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
  276. RemoveDynamicPort(GetPort(fieldName));
  277. }
  278. /// <summary> Remove an dynamic port from the node </summary>
  279. public void RemoveDynamicPort(NodePort port)
  280. {
  281. if (port == null) throw new ArgumentNullException("port");
  282. else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
  283. port.ClearConnections();
  284. ports.Remove(port.fieldName);
  285. }
  286. /// <summary> Removes all dynamic ports from the node </summary>
  287. [ContextMenu("Clear Dynamic Ports")]
  288. public void ClearDynamicPorts()
  289. {
  290. List<NodePort> dynamicPorts = new List<NodePort>(DynamicPorts);
  291. foreach (NodePort port in dynamicPorts)
  292. {
  293. RemoveDynamicPort(port);
  294. }
  295. }
  296. #endregion
  297. #region Ports
  298. /// <summary> Returns output port which matches fieldName </summary>
  299. public NodePort GetOutputPort(string fieldName)
  300. {
  301. NodePort port = GetPort(fieldName);
  302. if (port == null || port.direction != NodePort.IO.Output) return null;
  303. else return port;
  304. }
  305. /// <summary> Returns input port which matches fieldName </summary>
  306. public NodePort GetInputPort(string fieldName)
  307. {
  308. NodePort port = GetPort(fieldName);
  309. if (port == null || port.direction != NodePort.IO.Input) return null;
  310. else return port;
  311. }
  312. /// <summary> Returns port which matches fieldName </summary>
  313. public NodePort GetPort(string fieldName)
  314. {
  315. NodePort port;
  316. if (ports.TryGetValue(fieldName, out port)) return port;
  317. else return null;
  318. }
  319. public bool HasPort(string fieldName)
  320. {
  321. return ports.ContainsKey(fieldName);
  322. }
  323. #endregion
  324. #region Inputs/Outputs
  325. /// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
  326. /// <param name="fieldName">Field name of requested input port</param>
  327. /// <param name="fallback">If no ports are connected, this value will be returned</param>
  328. public T GetInputValue<T>(string fieldName, T fallback = default(T))
  329. {
  330. NodePort port = GetPort(fieldName);
  331. if (port != null && port.IsConnected) return port.GetInputValue<T>();
  332. else return fallback;
  333. }
  334. /// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
  335. /// <param name="fieldName">Field name of requested input port</param>
  336. /// <param name="fallback">If no ports are connected, this value will be returned</param>
  337. public T[] GetInputValues<T>(string fieldName, params T[] fallback)
  338. {
  339. NodePort port = GetPort(fieldName);
  340. if (port != null && port.IsConnected) return port.GetInputValues<T>();
  341. else return fallback;
  342. }
  343. /// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary>
  344. /// <param name="port">The requested port.</param>
  345. public virtual object GetValue(NodePort port)
  346. {
  347. Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
  348. return null;
  349. }
  350. #endregion
  351. /// <summary> Called after a connection between two <see cref="NodePort"/>s is created </summary>
  352. /// <param name="from">Output</param> <param name="to">Input</param>
  353. public virtual void OnCreateConnection(NodePort from, NodePort to)
  354. {
  355. }
  356. /// <summary> Called after a connection is removed from this port </summary>
  357. /// <param name="port">Output or Input</param>
  358. public virtual void OnRemoveConnection(NodePort port)
  359. {
  360. }
  361. /// <summary> Disconnect everything from this node </summary>
  362. public void ClearConnections()
  363. {
  364. foreach (NodePort port in Ports) port.ClearConnections();
  365. }
  366. #region Attributes
  367. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
  368. public class NodeSerializeAttribute : Attribute
  369. {
  370. public string showName;
  371. public string tooltip;
  372. public int showType;
  373. public bool isDisable;
  374. public NodeSerializeAttribute(string showName, string tooltip = "", int showType = 0, bool isDisable = false)
  375. {
  376. this.showName = showName;
  377. this.tooltip = tooltip;
  378. this.showType = showType;
  379. this.isDisable = isDisable;
  380. }
  381. }
  382. /// <summary>
  383. /// 这里暂时用来处理RandomNode的
  384. /// </summary>
  385. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
  386. public class NodeSerializeAttributeSpecial : Attribute
  387. {
  388. }
  389. // [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
  390. // public class DisabledAttribute : Attribute
  391. // {
  392. // }
  393. /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
  394. [AttributeUsage(AttributeTargets.Field)]
  395. public class InputAttribute : Attribute
  396. {
  397. public ShowBackingValue backingValue;
  398. public ConnectionType connectionType;
  399. [Obsolete("Use dynamicPortList instead")]
  400. public bool instancePortList
  401. {
  402. get { return dynamicPortList; }
  403. set { dynamicPortList = value; }
  404. }
  405. public bool dynamicPortList;
  406. public TypeConstraint typeConstraint;
  407. /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
  408. /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
  409. /// <param name="connectionType">Should we allow multiple connections? </param>
  410. /// <param name="typeConstraint">Constrains which input connections can be made to this port </param>
  411. /// <param name="dynamicPortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param>
  412. public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple,
  413. TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false)
  414. {
  415. this.backingValue = backingValue;
  416. this.connectionType = connectionType;
  417. this.dynamicPortList = dynamicPortList;
  418. this.typeConstraint = typeConstraint;
  419. }
  420. }
  421. /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
  422. [AttributeUsage(AttributeTargets.Field)]
  423. public class OutputAttribute : Attribute
  424. {
  425. public ShowBackingValue backingValue;
  426. public ConnectionType connectionType;
  427. [Obsolete("Use dynamicPortList instead")]
  428. public bool instancePortList
  429. {
  430. get { return dynamicPortList; }
  431. set { dynamicPortList = value; }
  432. }
  433. public bool dynamicPortList;
  434. public TypeConstraint typeConstraint;
  435. /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
  436. /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
  437. /// <param name="connectionType">Should we allow multiple connections? </param>
  438. /// <param name="typeConstraint">Constrains which input connections can be made from this port </param>
  439. /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
  440. public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
  441. bool dynamicPortList = false)
  442. {
  443. this.backingValue = backingValue;
  444. this.connectionType = connectionType;
  445. this.dynamicPortList = dynamicPortList;
  446. this.typeConstraint = typeConstraint;
  447. }
  448. /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
  449. /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
  450. /// <param name="connectionType">Should we allow multiple connections? </param>
  451. /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
  452. [Obsolete("Use constructor with TypeConstraint")]
  453. public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList)
  454. {
  455. }
  456. }
  457. /// <summary> Manually supply node class with a context menu path </summary>
  458. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true)]
  459. public class CreateNodeMenuAttribute : Attribute
  460. {
  461. public string menuName;
  462. public int order;
  463. public bool isServerRecord;
  464. public string tooltip = "帮助文本默认在[CreateNodeMenuAttribute]中配置,暂时由程序维护,有更好的意见可以及时反馈";
  465. public string showName;
  466. /// <summary> Manually supply node class with a context menu path </summary>
  467. /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
  468. public CreateNodeMenuAttribute(string menuName)
  469. {
  470. this.menuName = menuName;
  471. this.order = 0;
  472. }
  473. /// <summary> Manually supply node class with a context menu path </summary>
  474. /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
  475. /// <param name="order"> The order by which the menu items are displayed. </param>
  476. public CreateNodeMenuAttribute(string menuName, int order)
  477. {
  478. this.menuName = menuName;
  479. this.order = order;
  480. }
  481. public CreateNodeMenuAttribute(string menuName, int order, bool isServerRecord, string tooltip, string showName)
  482. {
  483. this.menuName = menuName;
  484. this.order = order;
  485. this.isServerRecord = isServerRecord;
  486. this.tooltip = tooltip;
  487. this.showName = showName;
  488. }
  489. }
  490. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true)]
  491. public class CreateDialogueNodeMenuAttribute : Attribute
  492. {
  493. public string menuName;
  494. public int order;
  495. public bool isServerRecord;
  496. public string tooltip;
  497. public string showName;
  498. /// <summary> Manually supply node class with a context menu path </summary>
  499. /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
  500. public CreateDialogueNodeMenuAttribute(string menuName)
  501. {
  502. this.menuName = menuName;
  503. this.order = 0;
  504. }
  505. /// <summary> Manually supply node class with a context menu path </summary>
  506. /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
  507. /// <param name="order"> The order by which the menu items are displayed. </param>
  508. public CreateDialogueNodeMenuAttribute(string menuName, int order)
  509. {
  510. this.menuName = menuName;
  511. this.order = order;
  512. }
  513. public CreateDialogueNodeMenuAttribute(string menuName, int order, bool isServerRecord, string tooltip, string showName)
  514. {
  515. this.menuName = menuName;
  516. this.order = order;
  517. this.isServerRecord = isServerRecord;
  518. this.tooltip = tooltip;
  519. this.showName = showName;
  520. }
  521. }
  522. /// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
  523. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  524. public class DisallowMultipleNodesAttribute : Attribute
  525. {
  526. // TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node
  527. // while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both.
  528. public int max;
  529. /// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
  530. /// <param name="max"> How many nodes to allow. Defaults to 1. </param>
  531. public DisallowMultipleNodesAttribute(int max = 1)
  532. {
  533. this.max = max;
  534. }
  535. }
  536. /// <summary> Specify a color for this node type </summary>
  537. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  538. public class NodeTintAttribute : Attribute
  539. {
  540. public Color color;
  541. /// <summary> Specify a color for this node type </summary>
  542. /// <param name="r"> Red [0.0f .. 1.0f] </param>
  543. /// <param name="g"> Green [0.0f .. 1.0f] </param>
  544. /// <param name="b"> Blue [0.0f .. 1.0f] </param>
  545. public NodeTintAttribute(float r, float g, float b)
  546. {
  547. color = new Color(r, g, b);
  548. }
  549. /// <summary> Specify a color for this node type </summary>
  550. /// <param name="hex"> HEX color value </param>
  551. public NodeTintAttribute(string hex)
  552. {
  553. ColorUtility.TryParseHtmlString(hex, out color);
  554. }
  555. /// <summary> Specify a color for this node type </summary>
  556. /// <param name="r"> Red [0 .. 255] </param>
  557. /// <param name="g"> Green [0 .. 255] </param>
  558. /// <param name="b"> Blue [0 .. 255] </param>
  559. public NodeTintAttribute(byte r, byte g, byte b)
  560. {
  561. color = new Color32(r, g, b, byte.MaxValue);
  562. }
  563. }
  564. /// <summary> Specify a width for this node type </summary>
  565. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  566. public class NodeWidthAttribute : Attribute
  567. {
  568. public int width;
  569. /// <summary> Specify a width for this node type </summary>
  570. /// <param name="width"> Width </param>
  571. public NodeWidthAttribute(int width)
  572. {
  573. this.width = width;
  574. }
  575. }
  576. #endregion
  577. [Serializable]
  578. private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver
  579. {
  580. [SerializeField] private List<string> keys = new List<string>();
  581. [SerializeField] private List<NodePort> values = new List<NodePort>();
  582. public void OnBeforeSerialize()
  583. {
  584. keys.Clear();
  585. values.Clear();
  586. foreach (KeyValuePair<string, NodePort> pair in this)
  587. {
  588. keys.Add(pair.Key);
  589. values.Add(pair.Value);
  590. }
  591. }
  592. public void OnAfterDeserialize()
  593. {
  594. this.Clear();
  595. if (keys.Count != values.Count)
  596. throw new Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
  597. for (int i = 0; i < keys.Count; i++)
  598. this.Add(keys[i], values[i]);
  599. }
  600. }
  601. }
  602. }