Popular New Releases in Unity
dnSpy
v6.1.8
AirSim
v1.7.0 - Linux
ml-agents
ML-Agents Release 19
osu
2022.409.0
bullet3
PyBullet 3.22
Popular Libraries in Unity
by git-tips javascript
19842 MIT
Most commonly used git tips and tricks.
by dnSpy csharp
18900
.NET debugger and assembly editor
by microsoft c++
12921 NOASSERTION
Open source simulator for autonomous vehicles built on Unreal Engine / Unity, from Microsoft AI & Research
by Unity-Technologies csharp
12332 NOASSERTION
Unity Machine Learning Agents Toolkit
by lettier c++
11004 BSD-3-Clause
🎮 A step-by-step guide to implementing SSAO, depth of field, lighting, normal mapping, and more for your 3D game.
by ppy csharp
9614 MIT
rhythm is just a *click* away!
by bulletphysics c++
9090 NOASSERTION
Bullet Physics SDK: real-time collision detection and multi-physics simulation for VR, games, visual effects, robotics, machine learning etc.
by Unity-Technologies csharp
8960 NOASSERTION
Unity C# reference source code.
by myclabs php
8159 MIT
Create deep copies (clones) of your objects
Trending New libraries in Unity
by jynew csharp
4611 NOASSERTION
金庸群侠传3D重制版
by UnityTechnologies csharp
3966 Apache-2.0
Unity Open Project #1: Chop Chop
by Tencent c++
2646 NOASSERTION
Write your game with TypeScript in UE4 or Unity. Puerts can be pronounced as pu-erh TS(普洱TS)
by JoseDeFreitas python
2346 CC0-1.0
▶️ An awesome list containing awesome YouTubers that teach about technology.
by Unity-Technologies csharp
1628 NOASSERTION
Unity Graphics - Including Scriptable Render Pipeline
by education javascript
1378
Join the GitHub Graduation Yearbook and "walk the stage" on June 5.
by QianMo csharp
1293 MIT
Unity Post Processing Stack Library | Unity引擎的高品质后处理库
by JasonXuDeveloper csharp
1206 MIT
The solution that allows unity games update in runtime. 使Unity开发的游戏支持热更新的解决方案。
by orbitersim c++
1178 NOASSERTION
Open-source repository of Orbiter Space Flight Simulator
Top Authors in Unity
1
287 Libraries
41824
2
200 Libraries
1928
3
194 Libraries
1473
4
140 Libraries
70479
5
70 Libraries
28622
6
50 Libraries
1006
7
47 Libraries
415
8
40 Libraries
3139
9
37 Libraries
4297
10
32 Libraries
1200
1
287 Libraries
41824
2
200 Libraries
1928
3
194 Libraries
1473
4
140 Libraries
70479
5
70 Libraries
28622
6
50 Libraries
1006
7
47 Libraries
415
8
40 Libraries
3139
9
37 Libraries
4297
10
32 Libraries
1200
Trending Kits in Unity
No Trending Kits are available at this moment for Unity
Trending Discussions on Unity
How can I make a constant static immutable instance of a class?
Unity3D Display a UnityEvent Corrrectly in a ReorderableList
How do I avoid async void?
Converting Numbers from Base 10 to Base 60
React unity WebGL not working on React JS project
Unity missing reference at Library/PackageCache/com.unity.collab-proxy@1.2.16
Why is my character's horizontal velocity lost when jumping?
How do I replace a switch statement over an enum with runtime-dynamic type-based generic dispatch in C#?
Make the distance between neighbor notes the same (Theremin like app)
Extracting the measurement unit (degrees, metres, etc.) from spatial data in R
QUESTION
How can I make a constant static immutable instance of a class?
Asked 2022-Mar-07 at 22:32I have a simple class that represents 3 dimensional coordinates called Coord3
that simply has x, y, and z int
values.
I want to declare a static constant variable Coord3.zero
where x, y, and z are set to 0.
I have attempted this with:
1public static readonly Coord3 zero = new Coord3(0, 0, 0);
2
However I found that this variable can be changed. For example if I do
1public static readonly Coord3 zero = new Coord3(0, 0, 0);
2Coord3 coord = Coord3.zero;
3coord.x = 5;
4
this actually changes the x value of Coord3.zero
to be 5. Maybe I am misunderstanding readonly
? I know that in Unity there is Vector3.zero
which never changes. I am trying to achieve the same effect.
ANSWER
Answered 2022-Mar-07 at 21:41readonly
is not quite the same thing as immutable in the sense you mean. readonly
means you cannot assign to a variable. So you couldn't do
1public static readonly Coord3 zero = new Coord3(0, 0, 0);
2Coord3 coord = Coord3.zero;
3coord.x = 5;
4public static readonly Cord3 zero = new Cord3(0, 0, 0);
5
6zero = new Cord3(0, 0, 1);
7
In order to achieve the effect you want, you could need to create a class, struct or record with readonly properties or fields. There's no way to achieve that effect with a type defined in an internal library. If the type allows mutability on a field or property, that field or property is mutable.
QUESTION
Unity3D Display a UnityEvent Corrrectly in a ReorderableList
Asked 2022-Mar-02 at 07:42I am trying to make a custom inspector for my sequence class. The idea is to allow the user to configure various UnityEvents that get called at the start or the end of a sequence.
I want to have a collection of sequences in a ReorderableList so that it is highly configurable inside the inspector.
I am very close to a solution but i am fairly inexperienced with editor scripting. My scripts are almost working correctly. But I still need to solve the best way to dynamically adjust the vertical height of each item, in the DrawListItem method, and the total vertical height in the ElementHeight Method.
I am considering trying to deserialize the unity events so that i can use the GetPersistentEventCount method to get an idea of the required vertical height, but this seems like it is probably overkill. I suspect that there must be a simpler way to retrieve this data.
Currently when i add multiple items to the sequence I am getting what is pictured below, where the event fields overlap each other and the add/remove buttons are beneath the lower Unity Event.
Does anyone know the best way to resolve this?
1using UnityEngine;
2using UnityEditor;
3using UnityEditorInternal;
4using System;
5using UnityEngine.Events;
6
7[CustomEditor(typeof(SequenceManager))]
8public class SequenceManagerEditor : Editor
9{
10 SerializedProperty Sequences;
11
12 ReorderableList list;
13 private void OnEnable()
14 {
15 Sequences = serializedObject.FindProperty("Sequences");
16 list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
17 list.drawElementCallback = DrawListItems;
18 list.drawHeaderCallback = DrawHeader;
19 list.elementHeightCallback = ElementHeight;
20 }
21
22 //Draws the elements in the list
23 void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
24 {
25 SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
26
27
28 ////NAME
29 EditorGUI.LabelField(new Rect(
30 rect.x,
31 rect.y + EditorGUIUtility.standardVerticalSpacing,
32 50,
33 EditorGUIUtility.singleLineHeight), "Name");
34 EditorGUI.PropertyField(
35 new Rect(
36 rect.x + 50,
37 rect.y + EditorGUIUtility.standardVerticalSpacing,
38 rect.width - 50,
39 EditorGUIUtility.singleLineHeight),
40 element.FindPropertyRelative("Name"),
41 GUIContent.none
42 );
43
44 //ON INIT
45 EditorGUI.LabelField(new Rect(
46 rect.x,
47 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
48 50,
49 EditorGUIUtility.singleLineHeight), "OnInit");
50 EditorGUI.PropertyField(new Rect(
51 rect.x + 50,
52 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
53 rect.width - 50,
54 3 * rect.y + 5 * EditorGUIUtility.singleLineHeight),
55 element.FindPropertyRelative("OnInit"),
56 GUIContent.none);
57
58 //ON DONE
59 EditorGUI.LabelField(new Rect(
60 rect.x,
61 rect.y + 7 * EditorGUIUtility.singleLineHeight,
62 50,
63 EditorGUIUtility.singleLineHeight), "OnDone");
64 EditorGUI.PropertyField(
65 new Rect(
66 rect.x + 50,
67 rect.y + 7 * EditorGUIUtility.singleLineHeight,
68 rect.width - 50,
69 3 * rect.y + 12 * EditorGUIUtility.singleLineHeight),
70 element.FindPropertyRelative("OnDone"),
71 GUIContent.none);
72
73 SerializedProperty indexProperty = element.FindPropertyRelative("index");
74 indexProperty.intValue = index;
75 }
76
77 private float ElementHeight(int index)
78 {
79 return (13 * EditorGUIUtility.singleLineHeight);
80 }
81
82 //Draws the header
83 void DrawHeader(Rect rect)
84 {
85 string name = "Sequences";
86 EditorGUI.LabelField(rect, name);
87 }
88
89
90 public override void OnInspectorGUI()
91 {
92 //base.OnInspectorGUI();
93 serializedObject.Update();
94 this.list.DoLayoutList();
95 serializedObject.ApplyModifiedProperties();
96 }
97 }
98
for the sake of completeness, I've also added the Sequence class and SequenceManager class below.
1using UnityEngine;
2using UnityEditor;
3using UnityEditorInternal;
4using System;
5using UnityEngine.Events;
6
7[CustomEditor(typeof(SequenceManager))]
8public class SequenceManagerEditor : Editor
9{
10 SerializedProperty Sequences;
11
12 ReorderableList list;
13 private void OnEnable()
14 {
15 Sequences = serializedObject.FindProperty("Sequences");
16 list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
17 list.drawElementCallback = DrawListItems;
18 list.drawHeaderCallback = DrawHeader;
19 list.elementHeightCallback = ElementHeight;
20 }
21
22 //Draws the elements in the list
23 void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
24 {
25 SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
26
27
28 ////NAME
29 EditorGUI.LabelField(new Rect(
30 rect.x,
31 rect.y + EditorGUIUtility.standardVerticalSpacing,
32 50,
33 EditorGUIUtility.singleLineHeight), "Name");
34 EditorGUI.PropertyField(
35 new Rect(
36 rect.x + 50,
37 rect.y + EditorGUIUtility.standardVerticalSpacing,
38 rect.width - 50,
39 EditorGUIUtility.singleLineHeight),
40 element.FindPropertyRelative("Name"),
41 GUIContent.none
42 );
43
44 //ON INIT
45 EditorGUI.LabelField(new Rect(
46 rect.x,
47 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
48 50,
49 EditorGUIUtility.singleLineHeight), "OnInit");
50 EditorGUI.PropertyField(new Rect(
51 rect.x + 50,
52 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
53 rect.width - 50,
54 3 * rect.y + 5 * EditorGUIUtility.singleLineHeight),
55 element.FindPropertyRelative("OnInit"),
56 GUIContent.none);
57
58 //ON DONE
59 EditorGUI.LabelField(new Rect(
60 rect.x,
61 rect.y + 7 * EditorGUIUtility.singleLineHeight,
62 50,
63 EditorGUIUtility.singleLineHeight), "OnDone");
64 EditorGUI.PropertyField(
65 new Rect(
66 rect.x + 50,
67 rect.y + 7 * EditorGUIUtility.singleLineHeight,
68 rect.width - 50,
69 3 * rect.y + 12 * EditorGUIUtility.singleLineHeight),
70 element.FindPropertyRelative("OnDone"),
71 GUIContent.none);
72
73 SerializedProperty indexProperty = element.FindPropertyRelative("index");
74 indexProperty.intValue = index;
75 }
76
77 private float ElementHeight(int index)
78 {
79 return (13 * EditorGUIUtility.singleLineHeight);
80 }
81
82 //Draws the header
83 void DrawHeader(Rect rect)
84 {
85 string name = "Sequences";
86 EditorGUI.LabelField(rect, name);
87 }
88
89
90 public override void OnInspectorGUI()
91 {
92 //base.OnInspectorGUI();
93 serializedObject.Update();
94 this.list.DoLayoutList();
95 serializedObject.ApplyModifiedProperties();
96 }
97 }
98using UnityEngine;
99using UnityEditor;
100using System;
101using UnityEngine.Events;
102
103[Serializable]
104public class Sequence
105{
106 public string Name;
107 public UnityEvent OnInit;
108 public UnityEvent OnDone;
109 private Module _module;
110 public int index;
111 private bool active;
112
113 //Called By The Sequence Manager At the start of a sequence
114 internal void Init(Module p_module)
115 {
116 Debug.Log($"sequence: {Name} with index: {index} has started");
117 active = true;
118 _module = p_module;
119 if(OnInit.HasNoListners())
120 {
121 Done();
122 }
123 else
124 {
125 OnInit.Invoke();
126 }
127 }
128
129
130 //Called Manually to Trigger the End of the Sequence
131 internal void Done()
132 {
133 if (!OnDone.HasNoListners())
134 {
135 OnDone.Invoke();
136 }
137 active = false;
138 Debug.Log($"sequence: {Name} with index: {index} is done");
139 _module.FinishedSequence(index);
140 }
141
142 //Check if active
143 internal bool GetActive()
144 {
145 return active;
146 }
147}
148
149
1using UnityEngine;
2using UnityEditor;
3using UnityEditorInternal;
4using System;
5using UnityEngine.Events;
6
7[CustomEditor(typeof(SequenceManager))]
8public class SequenceManagerEditor : Editor
9{
10 SerializedProperty Sequences;
11
12 ReorderableList list;
13 private void OnEnable()
14 {
15 Sequences = serializedObject.FindProperty("Sequences");
16 list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
17 list.drawElementCallback = DrawListItems;
18 list.drawHeaderCallback = DrawHeader;
19 list.elementHeightCallback = ElementHeight;
20 }
21
22 //Draws the elements in the list
23 void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
24 {
25 SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
26
27
28 ////NAME
29 EditorGUI.LabelField(new Rect(
30 rect.x,
31 rect.y + EditorGUIUtility.standardVerticalSpacing,
32 50,
33 EditorGUIUtility.singleLineHeight), "Name");
34 EditorGUI.PropertyField(
35 new Rect(
36 rect.x + 50,
37 rect.y + EditorGUIUtility.standardVerticalSpacing,
38 rect.width - 50,
39 EditorGUIUtility.singleLineHeight),
40 element.FindPropertyRelative("Name"),
41 GUIContent.none
42 );
43
44 //ON INIT
45 EditorGUI.LabelField(new Rect(
46 rect.x,
47 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
48 50,
49 EditorGUIUtility.singleLineHeight), "OnInit");
50 EditorGUI.PropertyField(new Rect(
51 rect.x + 50,
52 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
53 rect.width - 50,
54 3 * rect.y + 5 * EditorGUIUtility.singleLineHeight),
55 element.FindPropertyRelative("OnInit"),
56 GUIContent.none);
57
58 //ON DONE
59 EditorGUI.LabelField(new Rect(
60 rect.x,
61 rect.y + 7 * EditorGUIUtility.singleLineHeight,
62 50,
63 EditorGUIUtility.singleLineHeight), "OnDone");
64 EditorGUI.PropertyField(
65 new Rect(
66 rect.x + 50,
67 rect.y + 7 * EditorGUIUtility.singleLineHeight,
68 rect.width - 50,
69 3 * rect.y + 12 * EditorGUIUtility.singleLineHeight),
70 element.FindPropertyRelative("OnDone"),
71 GUIContent.none);
72
73 SerializedProperty indexProperty = element.FindPropertyRelative("index");
74 indexProperty.intValue = index;
75 }
76
77 private float ElementHeight(int index)
78 {
79 return (13 * EditorGUIUtility.singleLineHeight);
80 }
81
82 //Draws the header
83 void DrawHeader(Rect rect)
84 {
85 string name = "Sequences";
86 EditorGUI.LabelField(rect, name);
87 }
88
89
90 public override void OnInspectorGUI()
91 {
92 //base.OnInspectorGUI();
93 serializedObject.Update();
94 this.list.DoLayoutList();
95 serializedObject.ApplyModifiedProperties();
96 }
97 }
98using UnityEngine;
99using UnityEditor;
100using System;
101using UnityEngine.Events;
102
103[Serializable]
104public class Sequence
105{
106 public string Name;
107 public UnityEvent OnInit;
108 public UnityEvent OnDone;
109 private Module _module;
110 public int index;
111 private bool active;
112
113 //Called By The Sequence Manager At the start of a sequence
114 internal void Init(Module p_module)
115 {
116 Debug.Log($"sequence: {Name} with index: {index} has started");
117 active = true;
118 _module = p_module;
119 if(OnInit.HasNoListners())
120 {
121 Done();
122 }
123 else
124 {
125 OnInit.Invoke();
126 }
127 }
128
129
130 //Called Manually to Trigger the End of the Sequence
131 internal void Done()
132 {
133 if (!OnDone.HasNoListners())
134 {
135 OnDone.Invoke();
136 }
137 active = false;
138 Debug.Log($"sequence: {Name} with index: {index} is done");
139 _module.FinishedSequence(index);
140 }
141
142 //Check if active
143 internal bool GetActive()
144 {
145 return active;
146 }
147}
148
149using System;
150namespace UnityEngine
151{
152 [Serializable]
153 public class SequenceManager: MonoBehaviour
154 {
155
156 #region Properties
157 public Sequence[] Sequences;
158 #endregion
159 }
160}
161
ANSWER
Answered 2022-Mar-02 at 03:49It's possible to access public fields of Sequence
by casting the elements of serializedObject.targetObjects
in your editor script. You can also use serializedObject.targetObject
or Editor.target
if you aren't using [CanEditMultipleObjects]
.
1using UnityEngine;
2using UnityEditor;
3using UnityEditorInternal;
4using System;
5using UnityEngine.Events;
6
7[CustomEditor(typeof(SequenceManager))]
8public class SequenceManagerEditor : Editor
9{
10 SerializedProperty Sequences;
11
12 ReorderableList list;
13 private void OnEnable()
14 {
15 Sequences = serializedObject.FindProperty("Sequences");
16 list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
17 list.drawElementCallback = DrawListItems;
18 list.drawHeaderCallback = DrawHeader;
19 list.elementHeightCallback = ElementHeight;
20 }
21
22 //Draws the elements in the list
23 void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
24 {
25 SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
26
27
28 ////NAME
29 EditorGUI.LabelField(new Rect(
30 rect.x,
31 rect.y + EditorGUIUtility.standardVerticalSpacing,
32 50,
33 EditorGUIUtility.singleLineHeight), "Name");
34 EditorGUI.PropertyField(
35 new Rect(
36 rect.x + 50,
37 rect.y + EditorGUIUtility.standardVerticalSpacing,
38 rect.width - 50,
39 EditorGUIUtility.singleLineHeight),
40 element.FindPropertyRelative("Name"),
41 GUIContent.none
42 );
43
44 //ON INIT
45 EditorGUI.LabelField(new Rect(
46 rect.x,
47 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
48 50,
49 EditorGUIUtility.singleLineHeight), "OnInit");
50 EditorGUI.PropertyField(new Rect(
51 rect.x + 50,
52 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
53 rect.width - 50,
54 3 * rect.y + 5 * EditorGUIUtility.singleLineHeight),
55 element.FindPropertyRelative("OnInit"),
56 GUIContent.none);
57
58 //ON DONE
59 EditorGUI.LabelField(new Rect(
60 rect.x,
61 rect.y + 7 * EditorGUIUtility.singleLineHeight,
62 50,
63 EditorGUIUtility.singleLineHeight), "OnDone");
64 EditorGUI.PropertyField(
65 new Rect(
66 rect.x + 50,
67 rect.y + 7 * EditorGUIUtility.singleLineHeight,
68 rect.width - 50,
69 3 * rect.y + 12 * EditorGUIUtility.singleLineHeight),
70 element.FindPropertyRelative("OnDone"),
71 GUIContent.none);
72
73 SerializedProperty indexProperty = element.FindPropertyRelative("index");
74 indexProperty.intValue = index;
75 }
76
77 private float ElementHeight(int index)
78 {
79 return (13 * EditorGUIUtility.singleLineHeight);
80 }
81
82 //Draws the header
83 void DrawHeader(Rect rect)
84 {
85 string name = "Sequences";
86 EditorGUI.LabelField(rect, name);
87 }
88
89
90 public override void OnInspectorGUI()
91 {
92 //base.OnInspectorGUI();
93 serializedObject.Update();
94 this.list.DoLayoutList();
95 serializedObject.ApplyModifiedProperties();
96 }
97 }
98using UnityEngine;
99using UnityEditor;
100using System;
101using UnityEngine.Events;
102
103[Serializable]
104public class Sequence
105{
106 public string Name;
107 public UnityEvent OnInit;
108 public UnityEvent OnDone;
109 private Module _module;
110 public int index;
111 private bool active;
112
113 //Called By The Sequence Manager At the start of a sequence
114 internal void Init(Module p_module)
115 {
116 Debug.Log($"sequence: {Name} with index: {index} has started");
117 active = true;
118 _module = p_module;
119 if(OnInit.HasNoListners())
120 {
121 Done();
122 }
123 else
124 {
125 OnInit.Invoke();
126 }
127 }
128
129
130 //Called Manually to Trigger the End of the Sequence
131 internal void Done()
132 {
133 if (!OnDone.HasNoListners())
134 {
135 OnDone.Invoke();
136 }
137 active = false;
138 Debug.Log($"sequence: {Name} with index: {index} is done");
139 _module.FinishedSequence(index);
140 }
141
142 //Check if active
143 internal bool GetActive()
144 {
145 return active;
146 }
147}
148
149using System;
150namespace UnityEngine
151{
152 [Serializable]
153 public class SequenceManager: MonoBehaviour
154 {
155
156 #region Properties
157 public Sequence[] Sequences;
158 #endregion
159 }
160}
161if(target is not SequenceManager manager)
162{
163 //target was not a SequenceManager, the manager variable will be null
164 return;
165}
166
and within ElementHeight()
:
1using UnityEngine;
2using UnityEditor;
3using UnityEditorInternal;
4using System;
5using UnityEngine.Events;
6
7[CustomEditor(typeof(SequenceManager))]
8public class SequenceManagerEditor : Editor
9{
10 SerializedProperty Sequences;
11
12 ReorderableList list;
13 private void OnEnable()
14 {
15 Sequences = serializedObject.FindProperty("Sequences");
16 list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
17 list.drawElementCallback = DrawListItems;
18 list.drawHeaderCallback = DrawHeader;
19 list.elementHeightCallback = ElementHeight;
20 }
21
22 //Draws the elements in the list
23 void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
24 {
25 SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
26
27
28 ////NAME
29 EditorGUI.LabelField(new Rect(
30 rect.x,
31 rect.y + EditorGUIUtility.standardVerticalSpacing,
32 50,
33 EditorGUIUtility.singleLineHeight), "Name");
34 EditorGUI.PropertyField(
35 new Rect(
36 rect.x + 50,
37 rect.y + EditorGUIUtility.standardVerticalSpacing,
38 rect.width - 50,
39 EditorGUIUtility.singleLineHeight),
40 element.FindPropertyRelative("Name"),
41 GUIContent.none
42 );
43
44 //ON INIT
45 EditorGUI.LabelField(new Rect(
46 rect.x,
47 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
48 50,
49 EditorGUIUtility.singleLineHeight), "OnInit");
50 EditorGUI.PropertyField(new Rect(
51 rect.x + 50,
52 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
53 rect.width - 50,
54 3 * rect.y + 5 * EditorGUIUtility.singleLineHeight),
55 element.FindPropertyRelative("OnInit"),
56 GUIContent.none);
57
58 //ON DONE
59 EditorGUI.LabelField(new Rect(
60 rect.x,
61 rect.y + 7 * EditorGUIUtility.singleLineHeight,
62 50,
63 EditorGUIUtility.singleLineHeight), "OnDone");
64 EditorGUI.PropertyField(
65 new Rect(
66 rect.x + 50,
67 rect.y + 7 * EditorGUIUtility.singleLineHeight,
68 rect.width - 50,
69 3 * rect.y + 12 * EditorGUIUtility.singleLineHeight),
70 element.FindPropertyRelative("OnDone"),
71 GUIContent.none);
72
73 SerializedProperty indexProperty = element.FindPropertyRelative("index");
74 indexProperty.intValue = index;
75 }
76
77 private float ElementHeight(int index)
78 {
79 return (13 * EditorGUIUtility.singleLineHeight);
80 }
81
82 //Draws the header
83 void DrawHeader(Rect rect)
84 {
85 string name = "Sequences";
86 EditorGUI.LabelField(rect, name);
87 }
88
89
90 public override void OnInspectorGUI()
91 {
92 //base.OnInspectorGUI();
93 serializedObject.Update();
94 this.list.DoLayoutList();
95 serializedObject.ApplyModifiedProperties();
96 }
97 }
98using UnityEngine;
99using UnityEditor;
100using System;
101using UnityEngine.Events;
102
103[Serializable]
104public class Sequence
105{
106 public string Name;
107 public UnityEvent OnInit;
108 public UnityEvent OnDone;
109 private Module _module;
110 public int index;
111 private bool active;
112
113 //Called By The Sequence Manager At the start of a sequence
114 internal void Init(Module p_module)
115 {
116 Debug.Log($"sequence: {Name} with index: {index} has started");
117 active = true;
118 _module = p_module;
119 if(OnInit.HasNoListners())
120 {
121 Done();
122 }
123 else
124 {
125 OnInit.Invoke();
126 }
127 }
128
129
130 //Called Manually to Trigger the End of the Sequence
131 internal void Done()
132 {
133 if (!OnDone.HasNoListners())
134 {
135 OnDone.Invoke();
136 }
137 active = false;
138 Debug.Log($"sequence: {Name} with index: {index} is done");
139 _module.FinishedSequence(index);
140 }
141
142 //Check if active
143 internal bool GetActive()
144 {
145 return active;
146 }
147}
148
149using System;
150namespace UnityEngine
151{
152 [Serializable]
153 public class SequenceManager: MonoBehaviour
154 {
155
156 #region Properties
157 public Sequence[] Sequences;
158 #endregion
159 }
160}
161if(target is not SequenceManager manager)
162{
163 //target was not a SequenceManager, the manager variable will be null
164 return;
165}
166var sequence = manager.Sequences[index];
167var size = sequence.OnInit.GetPersistentEventCount()
168 + sequence.OnDone.GetPersistentEventCount();
169
Edit - After trying out some things, I learned that you can get the array size through the serialized property. You can just use this in ElementHeight()
:
1using UnityEngine;
2using UnityEditor;
3using UnityEditorInternal;
4using System;
5using UnityEngine.Events;
6
7[CustomEditor(typeof(SequenceManager))]
8public class SequenceManagerEditor : Editor
9{
10 SerializedProperty Sequences;
11
12 ReorderableList list;
13 private void OnEnable()
14 {
15 Sequences = serializedObject.FindProperty("Sequences");
16 list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
17 list.drawElementCallback = DrawListItems;
18 list.drawHeaderCallback = DrawHeader;
19 list.elementHeightCallback = ElementHeight;
20 }
21
22 //Draws the elements in the list
23 void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
24 {
25 SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
26
27
28 ////NAME
29 EditorGUI.LabelField(new Rect(
30 rect.x,
31 rect.y + EditorGUIUtility.standardVerticalSpacing,
32 50,
33 EditorGUIUtility.singleLineHeight), "Name");
34 EditorGUI.PropertyField(
35 new Rect(
36 rect.x + 50,
37 rect.y + EditorGUIUtility.standardVerticalSpacing,
38 rect.width - 50,
39 EditorGUIUtility.singleLineHeight),
40 element.FindPropertyRelative("Name"),
41 GUIContent.none
42 );
43
44 //ON INIT
45 EditorGUI.LabelField(new Rect(
46 rect.x,
47 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
48 50,
49 EditorGUIUtility.singleLineHeight), "OnInit");
50 EditorGUI.PropertyField(new Rect(
51 rect.x + 50,
52 rect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 3,
53 rect.width - 50,
54 3 * rect.y + 5 * EditorGUIUtility.singleLineHeight),
55 element.FindPropertyRelative("OnInit"),
56 GUIContent.none);
57
58 //ON DONE
59 EditorGUI.LabelField(new Rect(
60 rect.x,
61 rect.y + 7 * EditorGUIUtility.singleLineHeight,
62 50,
63 EditorGUIUtility.singleLineHeight), "OnDone");
64 EditorGUI.PropertyField(
65 new Rect(
66 rect.x + 50,
67 rect.y + 7 * EditorGUIUtility.singleLineHeight,
68 rect.width - 50,
69 3 * rect.y + 12 * EditorGUIUtility.singleLineHeight),
70 element.FindPropertyRelative("OnDone"),
71 GUIContent.none);
72
73 SerializedProperty indexProperty = element.FindPropertyRelative("index");
74 indexProperty.intValue = index;
75 }
76
77 private float ElementHeight(int index)
78 {
79 return (13 * EditorGUIUtility.singleLineHeight);
80 }
81
82 //Draws the header
83 void DrawHeader(Rect rect)
84 {
85 string name = "Sequences";
86 EditorGUI.LabelField(rect, name);
87 }
88
89
90 public override void OnInspectorGUI()
91 {
92 //base.OnInspectorGUI();
93 serializedObject.Update();
94 this.list.DoLayoutList();
95 serializedObject.ApplyModifiedProperties();
96 }
97 }
98using UnityEngine;
99using UnityEditor;
100using System;
101using UnityEngine.Events;
102
103[Serializable]
104public class Sequence
105{
106 public string Name;
107 public UnityEvent OnInit;
108 public UnityEvent OnDone;
109 private Module _module;
110 public int index;
111 private bool active;
112
113 //Called By The Sequence Manager At the start of a sequence
114 internal void Init(Module p_module)
115 {
116 Debug.Log($"sequence: {Name} with index: {index} has started");
117 active = true;
118 _module = p_module;
119 if(OnInit.HasNoListners())
120 {
121 Done();
122 }
123 else
124 {
125 OnInit.Invoke();
126 }
127 }
128
129
130 //Called Manually to Trigger the End of the Sequence
131 internal void Done()
132 {
133 if (!OnDone.HasNoListners())
134 {
135 OnDone.Invoke();
136 }
137 active = false;
138 Debug.Log($"sequence: {Name} with index: {index} is done");
139 _module.FinishedSequence(index);
140 }
141
142 //Check if active
143 internal bool GetActive()
144 {
145 return active;
146 }
147}
148
149using System;
150namespace UnityEngine
151{
152 [Serializable]
153 public class SequenceManager: MonoBehaviour
154 {
155
156 #region Properties
157 public Sequence[] Sequences;
158 #endregion
159 }
160}
161if(target is not SequenceManager manager)
162{
163 //target was not a SequenceManager, the manager variable will be null
164 return;
165}
166var sequence = manager.Sequences[index];
167var size = sequence.OnInit.GetPersistentEventCount()
168 + sequence.OnDone.GetPersistentEventCount();
169var element = list.serializedProperty.GetArrayElementAtIndex(index);
170var size = element.FindPropertyRelative("OnInit.m_PersistentCalls.m_Calls").arraySize
171 + element.FindPropertyRelative("OnDone.m_PersistentCalls.m_Calls").arraySize;
172
QUESTION
How do I avoid async void?
Asked 2022-Feb-03 at 13:24Note: Turns out this issue is specific to Unity.
I read that async void
was to be avoided. I am trying to do so using Result
, but my application keeps locking up. How can I avoid using async void?
1public async void PrintNumberWithAwait()
2{
3 int number = await GetNumber();
4 Debug.Log(number); //Successfully prints "5"
5}
6
7public void PrintNumberWithResult()
8{
9 int number = GetNumber().Result;
10 Debug.Log(number); //Application Freezes
11}
12
13private async Task<int> GetNumber()
14{
15 await Task.Delay(1000);
16 return 5;
17}
18
I thought this was correct, but I must be missing something. How do I use async/await without having async void
?
I ran my tests separately with the following code (commented one out at a time):
1public async void PrintNumberWithAwait()
2{
3 int number = await GetNumber();
4 Debug.Log(number); //Successfully prints "5"
5}
6
7public void PrintNumberWithResult()
8{
9 int number = GetNumber().Result;
10 Debug.Log(number); //Application Freezes
11}
12
13private async Task<int> GetNumber()
14{
15 await Task.Delay(1000);
16 return 5;
17}
18PrintNumberWithAwait();
19PrintNumberWithResult();
20
ANSWER
Answered 2022-Jan-11 at 08:34You misunderstood what is meant by the async void
that is to be avoided.
It doesn't mean you should never use a task with no result attached. It just says the asynchronous methods that invoke them should return a Task
, not void
.
Simply take the signature of your async method from
1public async void PrintNumberWithAwait()
2{
3 int number = await GetNumber();
4 Debug.Log(number); //Successfully prints "5"
5}
6
7public void PrintNumberWithResult()
8{
9 int number = GetNumber().Result;
10 Debug.Log(number); //Application Freezes
11}
12
13private async Task<int> GetNumber()
14{
15 await Task.Delay(1000);
16 return 5;
17}
18PrintNumberWithAwait();
19PrintNumberWithResult();
20public async void PrintNumberWithAwait()
21
and replace void
with Task
1public async void PrintNumberWithAwait()
2{
3 int number = await GetNumber();
4 Debug.Log(number); //Successfully prints "5"
5}
6
7public void PrintNumberWithResult()
8{
9 int number = GetNumber().Result;
10 Debug.Log(number); //Application Freezes
11}
12
13private async Task<int> GetNumber()
14{
15 await Task.Delay(1000);
16 return 5;
17}
18PrintNumberWithAwait();
19PrintNumberWithResult();
20public async void PrintNumberWithAwait()
21public async Task PrintNumberWithAwait()
22{
23 int number = await GetNumber();
24 Debug.Log(number); //Successfully prints "5"
25}
26
27
Now calling methods have the option of awaiting the result, when and if they choose to. Either:
1public async void PrintNumberWithAwait()
2{
3 int number = await GetNumber();
4 Debug.Log(number); //Successfully prints "5"
5}
6
7public void PrintNumberWithResult()
8{
9 int number = GetNumber().Result;
10 Debug.Log(number); //Application Freezes
11}
12
13private async Task<int> GetNumber()
14{
15 await Task.Delay(1000);
16 return 5;
17}
18PrintNumberWithAwait();
19PrintNumberWithResult();
20public async void PrintNumberWithAwait()
21public async Task PrintNumberWithAwait()
22{
23 int number = await GetNumber();
24 Debug.Log(number); //Successfully prints "5"
25}
26
27await PrintNumberWithAwait();
28
Or
1public async void PrintNumberWithAwait()
2{
3 int number = await GetNumber();
4 Debug.Log(number); //Successfully prints "5"
5}
6
7public void PrintNumberWithResult()
8{
9 int number = GetNumber().Result;
10 Debug.Log(number); //Application Freezes
11}
12
13private async Task<int> GetNumber()
14{
15 await Task.Delay(1000);
16 return 5;
17}
18PrintNumberWithAwait();
19PrintNumberWithResult();
20public async void PrintNumberWithAwait()
21public async Task PrintNumberWithAwait()
22{
23 int number = await GetNumber();
24 Debug.Log(number); //Successfully prints "5"
25}
26
27await PrintNumberWithAwait();
28Task t = PrintNumberWithAwait();
29// Do other stuff
30// ...
31await t;
32
QUESTION
Converting Numbers from Base 10 to Base 60
Asked 2022-Jan-31 at 05:15Recently, I was reading about the Ancient Babylonian Civilization that used a number system with base 60 instead of base 10. Even with this number system at base 60, they were still able to approximate the square root of 2 — and that too, thousands of years ago!
I was curious about this, and wanted to see how numbers from our decimal system (base 10) can be converted into the sexagesimal system (base 60). Using the R programming language, I found this link in which an answer is provided on converting numbers from some base to a different base.
However, it seems here that the base can only be between 2 and 36 (I want base 60):
1base <- function(b, base = 10)
2{
3 base <- as.integer(base)
4 if(base > 36 | base < 2) stop("'base' must be between 2 and 36.")
5
6 structure(lapply(b, function(x)
7 {
8 n <- ceiling(log(x, base))
9 vec <- numeric()
10 val <- x
11
12 while(n >= 0)
13 {
14 rem <- val %/% base^n
15 val <- val - rem * base^n
16 vec <- c(vec, rem)
17 n <- n - 1
18 }
19
20 while(vec[1] == 0 & length(vec) > 1) vec <- vec[-1]
21 structure(x, base = base, representation = vec)
22 }), class = "base")
23}
24
The article that I linked to reads in the headline "One eighth equals seven and thirty in this strange base 60 world" - I would like to see this and convert "1/8" from the decimal system into "7 and 30" in the sexagesimal system.
Can someone please help me with this?
ANSWER
Answered 2022-Jan-30 at 20:41The code as given almost works. The limitation to bases < 36 is only there because the original author wanted to express the values with the symbols [0-9A-Z]. Removing that limitation and extending the algorithm to allow extra digits 'after the decimal point' (or 'after the sexagesimal point' in the case of base 60 :-) ) we get something that almost works (function definition below):
1base <- function(b, base = 10)
2{
3 base <- as.integer(base)
4 if(base > 36 | base < 2) stop("'base' must be between 2 and 36.")
5
6 structure(lapply(b, function(x)
7 {
8 n <- ceiling(log(x, base))
9 vec <- numeric()
10 val <- x
11
12 while(n >= 0)
13 {
14 rem <- val %/% base^n
15 val <- val - rem * base^n
16 vec <- c(vec, rem)
17 n <- n - 1
18 }
19
20 while(vec[1] == 0 & length(vec) > 1) vec <- vec[-1]
21 structure(x, base = base, representation = vec)
22 }), class = "base")
23}
24base(1/8, base = 60, digits = 6)
25[[1]]
26[1] 0.125
27attr(,"base")
28[1] 60
29attr(,"representation")
30[1] 7 29 59 59 59 59
31
32attr(,"class")
33[1] "base"
34
Instead of "7 30" we get "7 29 (59 repeating)", which is analogous to doing a decimal calculation that should be 0.2 and instead getting 0.1999....
This would presumably be fixable with an appropriate 'numeric fuzz' threshold.
The other thing that's missing from the code, now that it does fractional parts, is that the result should return information that tells you where the 'decimal' point is located (at the simplest, including the value of digits
in the output).
There are other aspects of the code that could be improved (e.g. pre-allocating vec
rather than building it up iteratively).
1base <- function(b, base = 10)
2{
3 base <- as.integer(base)
4 if(base > 36 | base < 2) stop("'base' must be between 2 and 36.")
5
6 structure(lapply(b, function(x)
7 {
8 n <- ceiling(log(x, base))
9 vec <- numeric()
10 val <- x
11
12 while(n >= 0)
13 {
14 rem <- val %/% base^n
15 val <- val - rem * base^n
16 vec <- c(vec, rem)
17 n <- n - 1
18 }
19
20 while(vec[1] == 0 & length(vec) > 1) vec <- vec[-1]
21 structure(x, base = base, representation = vec)
22 }), class = "base")
23}
24base(1/8, base = 60, digits = 6)
25[[1]]
26[1] 0.125
27attr(,"base")
28[1] 60
29attr(,"representation")
30[1] 7 29 59 59 59 59
31
32attr(,"class")
33[1] "base"
34base <- function(b, base = 10, digits = 0) {
35 base <- as.integer(base)
36 structure(lapply(b, function(x)
37 {
38 n <- ceiling(log(x, base))
39 vec <- numeric()
40 val <- x
41
42 while(n >= -1*digits ) {
43 rem <- val %/% base^n
44 val <- val - rem * base^n
45 vec <- c(vec, rem)
46 n <- n - 1
47 }
48
49 while(vec[1] == 0 & length(vec) > 1) vec <- vec[-1]
50 structure(x, base = base, representation = vec)
51 }), class = "base")
52}
53
QUESTION
React unity WebGL not working on React JS project
Asked 2022-Jan-24 at 23:38Hey there trying to make my Unity WebGL export work on my React JS project but for some reason is not working I'm using the following React Unity WebGL version (react-unity-webgl@7.x # For Unity 2018 and 2019 (Active LTS)) and I have followed the instructions accordingly to the version I'm using, someone ran into the same issue than me and asked a question in the GitHub and I followed what they said which is basically to put the unity folder in the public folder but still is not working. Anyone has any idea of how to fix this ?
This is how I have my folder structure:
And this is the 2 codes I have try (which they are basically the same):
Code A:
1import React from 'react'
2import Unity, { UnityContent } from "react-unity-webgl";
3
4let unityContent = new UnityContent(
5 "Megaman/Build/Megaman.json",
6 "Megaman/Build/UnityLoader.js"
7);
8
9function MegamanGame() {
10 return (
11 <div>
12 <h1>Is not working</h1>
13 <Unity unityContent={unityContent} />
14 </div>
15
16 )
17}
18
19export default MegamanGame
20
Code B:
1import React from 'react'
2import Unity, { UnityContent } from "react-unity-webgl";
3
4let unityContent = new UnityContent(
5 "Megaman/Build/Megaman.json",
6 "Megaman/Build/UnityLoader.js"
7);
8
9function MegamanGame() {
10 return (
11 <div>
12 <h1>Is not working</h1>
13 <Unity unityContent={unityContent} />
14 </div>
15
16 )
17}
18
19export default MegamanGame
20import Unity, { UnityContent } from "react-unity-webgl";
21export default function Megaman() {
22 let unityContent = new UnityContent(
23 "Megaman/Build/Megaman.json",
24 "Megaman/Build/UnityLoader.js"
25 );
26 return (
27 <div><Unity unityContent={unityContent} /></div>
28 );
29}
30
And this is how it renders
Take in mind I have try the path in 2 ways either "Megaman/Build/Megaman.json"
or just "/Build/Megaman.json"
ANSWER
Answered 2022-Jan-08 at 23:13you have to move your unitybuid folder as child of Public file, as this /public/Build/yourfiles.json
i am havinbg problems after that, it runs well on local host, but when building on a hosting sites, it doesnt loading for me.
QUESTION
Unity missing reference at Library/PackageCache/com.unity.collab-proxy@1.2.16
Asked 2022-Jan-22 at 20:35I just bought template products in Unity expecting to learn some logic
i just encountered errors of CS0246 when importing. and it seems all coming from one package in Library below is one of the error message.
Library/PackageCache/com.unity.collab-proxy@1.2.16/Editor/Collab/Presenters/CollabHistoryPresenter.cs(21,9): error CS0246: The type or namespace name 'BuildAccess' could not be found (are you missing a using directive or an assembly reference?)
i am a beginner and i don't know how to handle on this kind of error. can anyone kindly help me point out where and why this error happen?
ANSWER
Answered 2021-Jul-27 at 04:56Here are a few suggestions for tracking down the issue. Hard to tell what is missing.
- close unity and delete Library and obj folders. Then open Unity again. It will re-sync those folders.
- disable collaborate from services window in 2019- or window/collaborate in 2020+ and see if the issue gets resolved.
QUESTION
Why is my character's horizontal velocity lost when jumping?
Asked 2022-Jan-10 at 15:17GOAL
I'm relatively new to Unity and I want my character to be able to run and jump at the same time causing the character to go up diagonally.
PROBLEM
However after making some adjustments to give the character some acceleration, the jump seems to clear all existing velocity, meaning the character goes up and then to the side instead of both at the same time:
(I apologise if its a bit hard to see)
CODE
This is my character movement script:
1 Rigidbody2D rb;
2 BoxCollider2D bc;
3
4 [Header("Run")]
5 float xInput = 0f;
6 public float maxRunSpeed;
7 public float acceleration;
8 [Space]
9
10 [Header("Jump")]
11 public float jumpHeight;
12 public float lowJumpHeight;
13 public float fallSpeed;
14 public float airControl;
15
16 [Space]
17 public LayerMask groundLayer;
18 public bool onGround;
19
20 [Space]
21 public Vector2 bottomOffset;
22 public Vector2 boxSize;
23 public float coyoteTime;
24
25 void Start() {
26 // Gets a reference to the components attatched to the player
27 rb = GetComponent<Rigidbody2D>();
28 bc = GetComponent<BoxCollider2D>();
29 }
30
31 void Update() {
32 Jump();
33
34 // Takes input for running and returns a value from 1 (right) to -1 (left)
35 xInput = Math.Sign(Input.GetAxisRaw("Horizontal"));
36 }
37
38 // Applies a velocity scaled by runSpeed to the player depending on the direction of the input
39 // Increaces the velocity by accerleration until the max velocity is reached
40 void FixedUpdate() {
41 rb.velocity = Math.Abs(rb.velocity.x) < Math.Abs(xInput) * maxRunSpeed ? rb.velocity + new Vector2(acceleration * xInput, rb.velocity.y) * Time.deltaTime : new Vector2(xInput * maxRunSpeed, rb.velocity.y);
42 }
43
44 void Jump() {
45 // Checks whether the player is on the ground and if it is, replenishes coyote time, but if not, it starts to tick it down
46 coyoteTime = onGround ? 0.1f : coyoteTime - Time.deltaTime;
47 // Draws a box to check whether the player is touching objects on the ground layer
48 onGround = Physics2D.OverlapBox((Vector2)transform.position + bottomOffset, boxSize, 0f, groundLayer);
49
50 // Adds an upwards velocity to player when there is still valid coyote time and the jump button is pressed
51 if (Input.GetButtonDown("Jump") && coyoteTime > 0) {
52 rb.velocity = Vector2.up * jumpHeight;
53 }
54
55 // Increases gravity of player when falling down or when the jump button is let go mid-jump
56 if (rb.velocity.y < 0 ) {
57 rb.velocity += Vector2.up * Physics2D.gravity.y * (fallSpeed - 1) * Time.deltaTime;
58 } else if (rb.velocity.y > 0 && !Input.GetButton("Jump")) {
59 rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpHeight - 1) * Time.deltaTime;
60 }
61 }
62
Sorry for there being a lot of unecessary code, it's just im not sure what's causing the issue so i don't want to remove anything. Hopefully my comments make some sense?
ANSWER
Answered 2022-Jan-09 at 23:05This is happening because you're setting the velocity of your rigidbody directly with rb.velocity = Vector2.up * jumpHeight
. So that will wipe all existing velocity.
If you want to just add a force to the velocity rather than replacing it entirely, you can do that with methods like Rigidbody2D.AddForce.
QUESTION
How do I replace a switch statement over an enum with runtime-dynamic type-based generic dispatch in C#?
Asked 2021-Dec-30 at 16:43Background:
I am building an editor extension for Unity (although this question is not strictly unity related). The user can select a binary operation from a dropdown and the operation is performed on the inputs, as seen in the diagram:
The code is taken from a tutorial, and uses an enum here in combination with a switch statement here to achieve the desired behavior.
This next image demonstrates the relationship between the code and the behavior in the graph UI:
Problem
Based on my prior experience programming in other languages, and my desire to allow for user-extensible operations that don't require users to edit a switch statement in the core code, I would LIKE the resulting code to look something like this (invalid) C# code:
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25
Reference Behavior To be crystal clear about the kind of behavior I'm hoping to achieve, here is a reference implementation in Python.
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44
Specific Questions So ultimately this boils down to three interrelated questions:
What sort of type do I assign to
MathOperations
so that it can hold a collection of the subtypes ofGenericOperation
?How do I get the subtypes of
GenericOperation
?What type do I assign
operation
, which can be one of several types?
Work So Far
I have been looking into generics and reflection from some of the following sources, but so far none seem to provide exactly the information I'm looking for.
- https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics
- https://igoro.com/archive/fun-with-c-generics-down-casting-to-a-generic-type/
- Using enum as generic type parameter in C#
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generics-and-reflection
Edit: I edited the comments in the C# psuedocode to reflect that the primary confusion boils down to what the types should be for MathOperations
and operation
, and to note that the editor itself selects the operation
from the MathOperations
when the user clicks on the dropdown. I also changed the question so that they can be answered factually.
ANSWER
Answered 2021-Dec-30 at 16:43Usually I'd say your question is quite broad and the use case very tricky and requires a lot of not so trivial steps to approach. But I see you also have put quite an effort in research and your question so I'll try to do the same (little Christmas Present) ;)
In general I think generics is not what you want to use here. Generics always require compile time constant parameters.
As I am only on the phone and don't know I can't give you a full solution right now but I hope I can bring you into the right track.
1. Common Interface or base class
I think the simplest thing would rather be a common interface such as e.g.
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48
A common abstract
base class would of course do as well. (You could even go for a certain attribute on methods)
And then have some implementations such as e.g.
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64
2. Find all implementations using Reflection
You can then use Reflection (you already were on the right track there) in order to automatically find all available implementations of that interface like e.g. this
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73
3. Store/Serialize a selected type in Unity
Now you have all the types ...
However, in order to really use these within Unity you will need an additional special class that is [Serializable]
and can store a type e.g. like
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92
4. Interface type selection and drawing the drop-down
Then since you don't want to type the names manually you would need a special drawer for the drop down menu with the given types that implement your interface (you see we are connecting the dots).
I would probably use an attribute like e.g.
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102
You could then expose the field as e.g.
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102[Implements(typeof (ITwoFloatOperation))]
103public SerializableType operationType;
104
and then have a custom drawer. This depends of course on your needs. Honestly my editor scripting knowledge is more based on MonoBehaviour etc so I just hope you can somehow translate this into your graph thingy.
Something like e.g.
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102[Implements(typeof (ITwoFloatOperation))]
103public SerializableType operationType;
104 [CustomPropertyDrawer(typeof(ImplementsAttribute))]
105public class ImplementsDrawer : PropertyDrawer
106{
107 // Return the underlying type of s serialized property
108 private static Type GetType(SerializedProperty property)
109 {
110 // A little bit hacky we first get the type of the object that has this field
111 var parentType = property.serializedObject.targetObject.GetType();
112 // And then once again we use reflection to get the field via it's name again
113 var fi = parentType.GetField(property.propertyPath);
114 return fi.FieldType;
115 }
116
117 private static Type[] FindTypes (Type baseType)
118 {
119 var type = typeof(ITwoFloatOperation);
120 var types = AppDomain.CurrentDomain.GetAssemblies()
121 .SelectMany(s => s.GetTypes())
122 .Where(p => type.IsAssignableFrom(p));
123
124 return types.OrderBy(t => t.AssemblyQualifiedName).ToArray();
125 }
126
127 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
128 {
129 label = EditorGUI.BeginProperty(position, label, property);
130
131 var implements = attribute as ImplementsAttribute;
132
133 if (GetType(property) != typeof (SerializableType))
134 {
135 EditorGUI.HelpBox(position, MessageType.Error, "Implements only works for SerializableType!");
136 return;
137 }
138
139 var typeNameProperty = property.FindPropertyRelative("typeName");
140
141 var options = FindTypes (implements.baseType);
142
143 var guiOptions = options.Select(o => o.AssemblyQualifiedName).ToArray();
144
145 var currentType = string.IsNullOrWhiteSpace(typeNameProperty.stringValue) ? null : Type.GetType(typeNameProperty.stringValue);
146
147 var currentIndex = options.FindIndex(o => o == curtentType);
148
149 var newIndex = EditorGUI.Popup(position, label.text, currentIndex, guiOptions);
150
151 var newTypeName = newIndex >= 0 ? options[newIndex] : "";
152
153 property.stringValue = newTypeName;
154 EditorGUI.EndProperty();
155 }
156}
157
5. Using the type to create an instance
Once you somehow can store and get the desired type as a last step we want to use it ^^
Again the solution would be reflection and the Activator
which allows us to create an instance of any given dynamic type using Activator.CreateInstance
so once you have the field you would e.g. do
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102[Implements(typeof (ITwoFloatOperation))]
103public SerializableType operationType;
104 [CustomPropertyDrawer(typeof(ImplementsAttribute))]
105public class ImplementsDrawer : PropertyDrawer
106{
107 // Return the underlying type of s serialized property
108 private static Type GetType(SerializedProperty property)
109 {
110 // A little bit hacky we first get the type of the object that has this field
111 var parentType = property.serializedObject.targetObject.GetType();
112 // And then once again we use reflection to get the field via it's name again
113 var fi = parentType.GetField(property.propertyPath);
114 return fi.FieldType;
115 }
116
117 private static Type[] FindTypes (Type baseType)
118 {
119 var type = typeof(ITwoFloatOperation);
120 var types = AppDomain.CurrentDomain.GetAssemblies()
121 .SelectMany(s => s.GetTypes())
122 .Where(p => type.IsAssignableFrom(p));
123
124 return types.OrderBy(t => t.AssemblyQualifiedName).ToArray();
125 }
126
127 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
128 {
129 label = EditorGUI.BeginProperty(position, label, property);
130
131 var implements = attribute as ImplementsAttribute;
132
133 if (GetType(property) != typeof (SerializableType))
134 {
135 EditorGUI.HelpBox(position, MessageType.Error, "Implements only works for SerializableType!");
136 return;
137 }
138
139 var typeNameProperty = property.FindPropertyRelative("typeName");
140
141 var options = FindTypes (implements.baseType);
142
143 var guiOptions = options.Select(o => o.AssemblyQualifiedName).ToArray();
144
145 var currentType = string.IsNullOrWhiteSpace(typeNameProperty.stringValue) ? null : Type.GetType(typeNameProperty.stringValue);
146
147 var currentIndex = options.FindIndex(o => o == curtentType);
148
149 var newIndex = EditorGUI.Popup(position, label.text, currentIndex, guiOptions);
150
151 var newTypeName = newIndex >= 0 ? options[newIndex] : "";
152
153 property.stringValue = newTypeName;
154 EditorGUI.EndProperty();
155 }
156}
157var instance = (ITwoFloatOperation) Activator.CreateInstance(operationType.Type));
158var result = instance.GetResult(floatA, floatB);
159
Once all this is setup an working correctly ( ^^ ) your "users"/developers can add new operations as simple as implementing your interface.
Alternative Approach - "Scriptable Behaviors"
Thinking about it further I think I have another - maybe a bit more simple approach.
This option is maybe not what you were targeting originally and is not a drop-down but we will rather simply use the already existing object selection popup for assets!
You could use something I like to call "Scriptable Behaviours" and have a base ScriptableObject
like
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102[Implements(typeof (ITwoFloatOperation))]
103public SerializableType operationType;
104 [CustomPropertyDrawer(typeof(ImplementsAttribute))]
105public class ImplementsDrawer : PropertyDrawer
106{
107 // Return the underlying type of s serialized property
108 private static Type GetType(SerializedProperty property)
109 {
110 // A little bit hacky we first get the type of the object that has this field
111 var parentType = property.serializedObject.targetObject.GetType();
112 // And then once again we use reflection to get the field via it's name again
113 var fi = parentType.GetField(property.propertyPath);
114 return fi.FieldType;
115 }
116
117 private static Type[] FindTypes (Type baseType)
118 {
119 var type = typeof(ITwoFloatOperation);
120 var types = AppDomain.CurrentDomain.GetAssemblies()
121 .SelectMany(s => s.GetTypes())
122 .Where(p => type.IsAssignableFrom(p));
123
124 return types.OrderBy(t => t.AssemblyQualifiedName).ToArray();
125 }
126
127 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
128 {
129 label = EditorGUI.BeginProperty(position, label, property);
130
131 var implements = attribute as ImplementsAttribute;
132
133 if (GetType(property) != typeof (SerializableType))
134 {
135 EditorGUI.HelpBox(position, MessageType.Error, "Implements only works for SerializableType!");
136 return;
137 }
138
139 var typeNameProperty = property.FindPropertyRelative("typeName");
140
141 var options = FindTypes (implements.baseType);
142
143 var guiOptions = options.Select(o => o.AssemblyQualifiedName).ToArray();
144
145 var currentType = string.IsNullOrWhiteSpace(typeNameProperty.stringValue) ? null : Type.GetType(typeNameProperty.stringValue);
146
147 var currentIndex = options.FindIndex(o => o == curtentType);
148
149 var newIndex = EditorGUI.Popup(position, label.text, currentIndex, guiOptions);
150
151 var newTypeName = newIndex >= 0 ? options[newIndex] : "";
152
153 property.stringValue = newTypeName;
154 EditorGUI.EndProperty();
155 }
156}
157var instance = (ITwoFloatOperation) Activator.CreateInstance(operationType.Type));
158var result = instance.GetResult(floatA, floatB);
159public abstract class TwoFloatOperation : ScriptableObject
160{
161 public abstract float GetResult(float a, float b);
162}
163
And then multiple implementations (note: all these have to be in different files!)
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102[Implements(typeof (ITwoFloatOperation))]
103public SerializableType operationType;
104 [CustomPropertyDrawer(typeof(ImplementsAttribute))]
105public class ImplementsDrawer : PropertyDrawer
106{
107 // Return the underlying type of s serialized property
108 private static Type GetType(SerializedProperty property)
109 {
110 // A little bit hacky we first get the type of the object that has this field
111 var parentType = property.serializedObject.targetObject.GetType();
112 // And then once again we use reflection to get the field via it's name again
113 var fi = parentType.GetField(property.propertyPath);
114 return fi.FieldType;
115 }
116
117 private static Type[] FindTypes (Type baseType)
118 {
119 var type = typeof(ITwoFloatOperation);
120 var types = AppDomain.CurrentDomain.GetAssemblies()
121 .SelectMany(s => s.GetTypes())
122 .Where(p => type.IsAssignableFrom(p));
123
124 return types.OrderBy(t => t.AssemblyQualifiedName).ToArray();
125 }
126
127 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
128 {
129 label = EditorGUI.BeginProperty(position, label, property);
130
131 var implements = attribute as ImplementsAttribute;
132
133 if (GetType(property) != typeof (SerializableType))
134 {
135 EditorGUI.HelpBox(position, MessageType.Error, "Implements only works for SerializableType!");
136 return;
137 }
138
139 var typeNameProperty = property.FindPropertyRelative("typeName");
140
141 var options = FindTypes (implements.baseType);
142
143 var guiOptions = options.Select(o => o.AssemblyQualifiedName).ToArray();
144
145 var currentType = string.IsNullOrWhiteSpace(typeNameProperty.stringValue) ? null : Type.GetType(typeNameProperty.stringValue);
146
147 var currentIndex = options.FindIndex(o => o == curtentType);
148
149 var newIndex = EditorGUI.Popup(position, label.text, currentIndex, guiOptions);
150
151 var newTypeName = newIndex >= 0 ? options[newIndex] : "";
152
153 property.stringValue = newTypeName;
154 EditorGUI.EndProperty();
155 }
156}
157var instance = (ITwoFloatOperation) Activator.CreateInstance(operationType.Type));
158var result = instance.GetResult(floatA, floatB);
159public abstract class TwoFloatOperation : ScriptableObject
160{
161 public abstract float GetResult(float a, float b);
162}
163[CreateAssetMenu (fileName = "Add", menuName = "TwoFloatOperations/Add")]
164public class Add : TwoFloatOperation
165{
166 public float GetResult(float a, float b) => a + b;
167}
168
169[CreateAssetMenu (fileName = "Multiply", menuName = "TwoFloatOperations/Multiply")]
170public class Multiply : TwoFloatOperation
171{
172 public float GetResult(float a, float b) => a * b;
173}
174
175[CreateAssetMenu (fileName = "Power", menuName = "TwoFloatOperations/Power"]
176public class Power : TwoFloatOperation
177{
178 public float GetResult(float a, float b) Mathf.Pow(a, b);
179}
180
Then you create one instance of each vis the ProjectView
-> Right Click -> Create
-> TwoFloatOperations
Once you did this for each type you can simply expose a field of type
1... snip ...
2
3 // OperatorSelection.GetSelections() is automagically populated by inheritors of the GenericOperation class
4 // So it would represent a collection of types?
5 // so the confusion is primarily around what type this should be
6 public GenericOperations /* ?? */ MathOperations = GenericOperation.GetOperations();
7
8 // this gets assigned by the editor when the user clicks
9 // the dropdown, but I'm unclear on what the type should
10 // be since it can be one of several types
11 // from the MathOperations collection
12 public Operation /* ?? */ operation;
13
14 public override object GetValue(NodePort port)
15 {
16 float a = GetInputValue<float>("a", this.a);
17 float b = GetInputValue<float>("b", this.b);
18 result = 0f;
19 result = operation(a, b);
20 return result;
21 }
22
23... snip ...
24
25class GenericOperation:
26
27 @classmethod
28 def get_operations(cls):
29 return cls.__subclasses__()
30
31
32class AddOperation(GenericOperation):
33
34 def __call__(self, a, b):
35 return a + b
36
37
38if __name__ == '__main__':
39 op = AddOperation()
40 res = op(1, 2)
41 print(res) # 3
42 print(GenericOperation.get_operations()) # {<class '__main__.AddOperation'>}
43
44public interface ITwoFloatOperation
45{
46 public float GetResult(float a, float b);
47}
48public class Add : ITwoFloatOperation
49{
50 public float GetResult(float a, float b) => a + b;
51}
52
53public class Multiply : ITwoFloatOperation
54{
55 public float GetResult(float a, float b) => a * b;
56}
57
58public class Power : ITwoFloatOperation
59{
60 public float GetResult(float a, float b) Mathf.Pow(a, b);
61}
62
63... etc
64using System.Reflection;
65using System.Linq;
66
67...
68
69var type = typeof(ITwoFloatOperation);
70var types = AppDomain.CurrentDomain.GetAssemblies()
71 .SelectMany(s => s.GetTypes())
72 .Where(p => type.IsAssignableFrom(p));
73[Serializable]
74// See https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
75public class SerializableType : ISerializationCallbackReceiver
76{
77 private Type type;
78 [SerializeField] private string typeName;
79
80 public Type Type => type;
81
82 public void OnBeforeSerialize()
83 {
84 typeName = type != null ? type.AssemblyQualifiedName : "";
85 }
86
87 public void OnAfterDeserialize()
88 {
89 if(!string.NullOrWhiteSpace(typeName)) type = Type.GetType(typeName);
90 }
91}
92[AttributeUsage(AttributeTarget.Field)]
93public ImplementsAttribute : PropertyAttribute
94{
95 public Type baseType;
96
97 public ImplementsAttribute (Type type)
98 {
99 baseType = type;
100 }
101}
102[Implements(typeof (ITwoFloatOperation))]
103public SerializableType operationType;
104 [CustomPropertyDrawer(typeof(ImplementsAttribute))]
105public class ImplementsDrawer : PropertyDrawer
106{
107 // Return the underlying type of s serialized property
108 private static Type GetType(SerializedProperty property)
109 {
110 // A little bit hacky we first get the type of the object that has this field
111 var parentType = property.serializedObject.targetObject.GetType();
112 // And then once again we use reflection to get the field via it's name again
113 var fi = parentType.GetField(property.propertyPath);
114 return fi.FieldType;
115 }
116
117 private static Type[] FindTypes (Type baseType)
118 {
119 var type = typeof(ITwoFloatOperation);
120 var types = AppDomain.CurrentDomain.GetAssemblies()
121 .SelectMany(s => s.GetTypes())
122 .Where(p => type.IsAssignableFrom(p));
123
124 return types.OrderBy(t => t.AssemblyQualifiedName).ToArray();
125 }
126
127 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
128 {
129 label = EditorGUI.BeginProperty(position, label, property);
130
131 var implements = attribute as ImplementsAttribute;
132
133 if (GetType(property) != typeof (SerializableType))
134 {
135 EditorGUI.HelpBox(position, MessageType.Error, "Implements only works for SerializableType!");
136 return;
137 }
138
139 var typeNameProperty = property.FindPropertyRelative("typeName");
140
141 var options = FindTypes (implements.baseType);
142
143 var guiOptions = options.Select(o => o.AssemblyQualifiedName).ToArray();
144
145 var currentType = string.IsNullOrWhiteSpace(typeNameProperty.stringValue) ? null : Type.GetType(typeNameProperty.stringValue);
146
147 var currentIndex = options.FindIndex(o => o == curtentType);
148
149 var newIndex = EditorGUI.Popup(position, label.text, currentIndex, guiOptions);
150
151 var newTypeName = newIndex >= 0 ? options[newIndex] : "";
152
153 property.stringValue = newTypeName;
154 EditorGUI.EndProperty();
155 }
156}
157var instance = (ITwoFloatOperation) Activator.CreateInstance(operationType.Type));
158var result = instance.GetResult(floatA, floatB);
159public abstract class TwoFloatOperation : ScriptableObject
160{
161 public abstract float GetResult(float a, float b);
162}
163[CreateAssetMenu (fileName = "Add", menuName = "TwoFloatOperations/Add")]
164public class Add : TwoFloatOperation
165{
166 public float GetResult(float a, float b) => a + b;
167}
168
169[CreateAssetMenu (fileName = "Multiply", menuName = "TwoFloatOperations/Multiply")]
170public class Multiply : TwoFloatOperation
171{
172 public float GetResult(float a, float b) => a * b;
173}
174
175[CreateAssetMenu (fileName = "Power", menuName = "TwoFloatOperations/Power"]
176public class Power : TwoFloatOperation
177{
178 public float GetResult(float a, float b) Mathf.Pow(a, b);
179}
180public TwoFloatOperation operation;
181
and let Unity do all the reflection work to find instances which implement this in the assets.
You can simply click on the little dot next to the object field and Unity will list you all available options and you can even use the search bar to find one by name.
Advantage:
- No dirty, expensive and error prone reflection required
- Basically all based on already built-in functionality of the editor -> less worries with serialization etc
Disadvantage:
- This breaks a little with the actual concept behind
ScriptableObject
since usually there would be multiple instances with different settings, not only a single one - As you see your developers have to not only inherit a certain type but additionally add the
CreateAssetMenu
attribute and actually create an instance in order to be able to use it.
As said typing this on the phone but I hope this helps with your use case and gives you an idea of how I would approach this
QUESTION
Make the distance between neighbor notes the same (Theremin like app)
Asked 2021-Dec-24 at 13:44I'm making a Theremin-like app in Unity (C#).
I have horizontal Axis X, on which I can click (with a mouse or with a finger on a smartphone). This X-axis determines the frequency, which will be played. The user will specify the frequency range of the board (X-Axis), let's say from frequency 261.63 (note C4) to 523.25 (note C5).
I'll calculate x_position_ratio
which is a number between 0 and 1 determining, where did the user click on the X-axis (0 being on the most left (note C4 in this example), 1 on the most right (note C5))
From this, I will calculate the frequency to play by equation
1float freqRange = maxFreq - minFreq;
2float frequency = (x_position_ratio * freqRange) + minFreq;
3
And then play the frequency
. It works just fine.
If I draw the notes on the board (X-axis), we can see, that the higher is the frequency, the higher is the jump between the 2 notes.
1float freqRange = maxFreq - minFreq;
2float frequency = (x_position_ratio * freqRange) + minFreq;
3// Drawing just note A4 to demonstrate the code
4float a4 = 440.0f //frequency of note A4
5float x_position = (a4 - minFreq) / freqRange;
6
loc_x_position
indicating the position of the note on the X-axis between 0 to 1
Question:
I would like to make the jump, same, between 2 notes (Make it linear instead of exponential, if you understand what I mean). Found the equation on Wikipedia Piano_key_frequencies but it's for the keys. I want it to every frequency and I cannot figure out how to implement it in my 2 code examples I posted
ANSWER
Answered 2021-Dec-24 at 13:44I figured it out. Tried to plot it logarithmic to at least approximate the result.
I was inspired by this answer Plotting logarithmic graph Turns out this solution worked
To draw notes on the x-axis I used this:
1float freqRange = maxFreq - minFreq;
2float frequency = (x_position_ratio * freqRange) + minFreq;
3// Drawing just note A4 to demonstrate the code
4float a4 = 440.0f //frequency of note A4
5float x_position = (a4 - minFreq) / freqRange;
6minFreq = Mathf.Log10(minFreq);
7maxFreq = Mathf.Log10(maxFreq);
8float freqRange = maxFreq - minFreq;
9x_position = (Mathf.Log10(frequencyOfNoteToPlot) - minFreq) / freqRange;
10
and to calculate the frequency I just derived frequency from the equation for the x_position and ended up with this piece of code:
1float freqRange = maxFreq - minFreq;
2float frequency = (x_position_ratio * freqRange) + minFreq;
3// Drawing just note A4 to demonstrate the code
4float a4 = 440.0f //frequency of note A4
5float x_position = (a4 - minFreq) / freqRange;
6minFreq = Mathf.Log10(minFreq);
7maxFreq = Mathf.Log10(maxFreq);
8float freqRange = maxFreq - minFreq;
9x_position = (Mathf.Log10(frequencyOfNoteToPlot) - minFreq) / freqRange;
10frequency = Mathf.Pow(10, x_position * freqRange + minFreq);
11
One thing I still don't get is, why it doesn't matter, which base of the logarithm I use. Is it because I'm always getting ratio (value between 0 to 1)?
QUESTION
Extracting the measurement unit (degrees, metres, etc.) from spatial data in R
Asked 2021-Dec-21 at 15:05I would like to extract the unit of measurement (decimal degrees, metres, feet, etc.) from a spatial object in R. For example, if I have an SF data frame that uses the WGS84 co-ordinate reference system (EPSG:4326), I would like to be able to determine that the co-ordinates are specified in decimal degrees. Similarly, I'd like to be able to determine that UTM co-ordinates (e.g. EPSG:32615) are specified in metres.
I have tried using the st_crs()
function from the sf
package, which returns the co-ordinate reference system in well-known text format. However, I'm struggling to be certain that a regex that extracts the unit of measurement from that well-known text will operate reliably for a wide range of co-ordinate systems.
Is there an existing function that returns the measurement unit for a spatial object?
For example, the following code produces an SF data frame that uses the WGS84 co-ordinate system:
1library(sf)
2#> Linking to GEOS 3.8.1, GDAL 3.2.1, PROJ 7.2.1
3
4cities <- st_sf(city = "London", geometry = st_sfc(st_point(c(-0.1276, 51.5072))), crs = 4326)
5
6cities
7#> Simple feature collection with 1 feature and 1 field
8#> Geometry type: POINT
9#> Dimension: XY
10#> Bounding box: xmin: -0.1276 ymin: 51.5072 xmax: -0.1276 ymax: 51.5072
11#> Geodetic CRS: WGS 84
12#> city geometry
13#> 1 London POINT (-0.1276 51.5072)
14
Created on 2021-12-21 by the reprex package (v2.0.1)
I am ideally looking for a function that allows me to determine that the spatial unit for this dataset is decimal degrees, e.g. if the function was called st_crs_unit()
I would like to call st_crs_unit(cities)
and that function return the unit "degrees"
or similar.
st_crs()
produces information about the CRS in well-known text format, including that the co-ordinate system (CS[]
) uses the ANGLEUNIT
"degree"
for both axes, but the structure of this text varies considerably across co-ordinate systems, so I cannot be sure a regex trained on some systems will work for all.
1library(sf)
2#> Linking to GEOS 3.8.1, GDAL 3.2.1, PROJ 7.2.1
3
4cities <- st_sf(city = "London", geometry = st_sfc(st_point(c(-0.1276, 51.5072))), crs = 4326)
5
6cities
7#> Simple feature collection with 1 feature and 1 field
8#> Geometry type: POINT
9#> Dimension: XY
10#> Bounding box: xmin: -0.1276 ymin: 51.5072 xmax: -0.1276 ymax: 51.5072
11#> Geodetic CRS: WGS 84
12#> city geometry
13#> 1 London POINT (-0.1276 51.5072)
14st_crs(cities)
15#> Coordinate Reference System:
16#> User input: EPSG:4326
17#> wkt:
18#> GEOGCRS["WGS 84",
19#> DATUM["World Geodetic System 1984",
20#> ELLIPSOID["WGS 84",6378137,298.257223563,
21#> LENGTHUNIT["metre",1]]],
22#> PRIMEM["Greenwich",0,
23#> ANGLEUNIT["degree",0.0174532925199433]],
24#> CS[ellipsoidal,2],
25#> AXIS["geodetic latitude (Lat)",north,
26#> ORDER[1],
27#> ANGLEUNIT["degree",0.0174532925199433]],
28#> AXIS["geodetic longitude (Lon)",east,
29#> ORDER[2],
30#> ANGLEUNIT["degree",0.0174532925199433]],
31#> USAGE[
32#> SCOPE["Horizontal component of 3D system."],
33#> AREA["World."],
34#> BBOX[-90,-180,90,180]],
35#> ID["EPSG",4326]]
36
Created on 2021-12-21 by the reprex package (v2.0.1)
For example, if we transform the same data to use the UTM zone 30N co-ordinate system, the output from st_crs()
changes substantially.
1library(sf)
2#> Linking to GEOS 3.8.1, GDAL 3.2.1, PROJ 7.2.1
3
4cities <- st_sf(city = "London", geometry = st_sfc(st_point(c(-0.1276, 51.5072))), crs = 4326)
5
6cities
7#> Simple feature collection with 1 feature and 1 field
8#> Geometry type: POINT
9#> Dimension: XY
10#> Bounding box: xmin: -0.1276 ymin: 51.5072 xmax: -0.1276 ymax: 51.5072
11#> Geodetic CRS: WGS 84
12#> city geometry
13#> 1 London POINT (-0.1276 51.5072)
14st_crs(cities)
15#> Coordinate Reference System:
16#> User input: EPSG:4326
17#> wkt:
18#> GEOGCRS["WGS 84",
19#> DATUM["World Geodetic System 1984",
20#> ELLIPSOID["WGS 84",6378137,298.257223563,
21#> LENGTHUNIT["metre",1]]],
22#> PRIMEM["Greenwich",0,
23#> ANGLEUNIT["degree",0.0174532925199433]],
24#> CS[ellipsoidal,2],
25#> AXIS["geodetic latitude (Lat)",north,
26#> ORDER[1],
27#> ANGLEUNIT["degree",0.0174532925199433]],
28#> AXIS["geodetic longitude (Lon)",east,
29#> ORDER[2],
30#> ANGLEUNIT["degree",0.0174532925199433]],
31#> USAGE[
32#> SCOPE["Horizontal component of 3D system."],
33#> AREA["World."],
34#> BBOX[-90,-180,90,180]],
35#> ID["EPSG",4326]]
36st_crs(st_transform(cities, crs = 32630))
37#> Coordinate Reference System:
38#> User input: EPSG:32630
39#> wkt:
40#> PROJCRS["WGS 84 / UTM zone 30N",
41#> BASEGEOGCRS["WGS 84",
42#> DATUM["World Geodetic System 1984",
43#> ELLIPSOID["WGS 84",6378137,298.257223563,
44#> LENGTHUNIT["metre",1]]],
45#> PRIMEM["Greenwich",0,
46#> ANGLEUNIT["degree",0.0174532925199433]],
47#> ID["EPSG",4326]],
48#> CONVERSION["UTM zone 30N",
49#> METHOD["Transverse Mercator",
50#> ID["EPSG",9807]],
51#> PARAMETER["Latitude of natural origin",0,
52#> ANGLEUNIT["degree",0.0174532925199433],
53#> ID["EPSG",8801]],
54#> PARAMETER["Longitude of natural origin",-3,
55#> ANGLEUNIT["degree",0.0174532925199433],
56#> ID["EPSG",8802]],
57#> PARAMETER["Scale factor at natural origin",0.9996,
58#> SCALEUNIT["unity",1],
59#> ID["EPSG",8805]],
60#> PARAMETER["False easting",500000,
61#> LENGTHUNIT["metre",1],
62#> ID["EPSG",8806]],
63#> PARAMETER["False northing",0,
64#> LENGTHUNIT["metre",1],
65#> ID["EPSG",8807]]],
66#> CS[Cartesian,2],
67#> AXIS["(E)",east,
68#> ORDER[1],
69#> LENGTHUNIT["metre",1]],
70#> AXIS["(N)",north,
71#> ORDER[2],
72#> LENGTHUNIT["metre",1]],
73#> USAGE[
74#> SCOPE["Engineering survey, topographic mapping."],
75#> AREA["Between 6°W and 0°W, northern hemisphere between equator and 84°N, onshore and offshore. Algeria. Burkina Faso. Côte' Ivoire (Ivory Coast). Faroe Islands - offshore. France. Ghana. Gibraltar. Ireland - offshore Irish Sea. Mali. Mauritania. Morocco. Spain. United Kingdom (UK)."],
76#> BBOX[0,-6,84,0]],
77#> ID["EPSG",32630]]
78
Created on 2021-12-21 by the reprex package (v2.0.1)
Is there an existing R function that returns the measurement unit for a spatial object?
ANSWER
Answered 2021-Dec-21 at 15:05st_crs()
has a parameters
argument that returns a list of useful CRS parameters when TRUE
, including the units of the CRS. Here's an example with the built-in nc
data:
1library(sf)
2#> Linking to GEOS 3.8.1, GDAL 3.2.1, PROJ 7.2.1
3
4cities <- st_sf(city = "London", geometry = st_sfc(st_point(c(-0.1276, 51.5072))), crs = 4326)
5
6cities
7#> Simple feature collection with 1 feature and 1 field
8#> Geometry type: POINT
9#> Dimension: XY
10#> Bounding box: xmin: -0.1276 ymin: 51.5072 xmax: -0.1276 ymax: 51.5072
11#> Geodetic CRS: WGS 84
12#> city geometry
13#> 1 London POINT (-0.1276 51.5072)
14st_crs(cities)
15#> Coordinate Reference System:
16#> User input: EPSG:4326
17#> wkt:
18#> GEOGCRS["WGS 84",
19#> DATUM["World Geodetic System 1984",
20#> ELLIPSOID["WGS 84",6378137,298.257223563,
21#> LENGTHUNIT["metre",1]]],
22#> PRIMEM["Greenwich",0,
23#> ANGLEUNIT["degree",0.0174532925199433]],
24#> CS[ellipsoidal,2],
25#> AXIS["geodetic latitude (Lat)",north,
26#> ORDER[1],
27#> ANGLEUNIT["degree",0.0174532925199433]],
28#> AXIS["geodetic longitude (Lon)",east,
29#> ORDER[2],
30#> ANGLEUNIT["degree",0.0174532925199433]],
31#> USAGE[
32#> SCOPE["Horizontal component of 3D system."],
33#> AREA["World."],
34#> BBOX[-90,-180,90,180]],
35#> ID["EPSG",4326]]
36st_crs(st_transform(cities, crs = 32630))
37#> Coordinate Reference System:
38#> User input: EPSG:32630
39#> wkt:
40#> PROJCRS["WGS 84 / UTM zone 30N",
41#> BASEGEOGCRS["WGS 84",
42#> DATUM["World Geodetic System 1984",
43#> ELLIPSOID["WGS 84",6378137,298.257223563,
44#> LENGTHUNIT["metre",1]]],
45#> PRIMEM["Greenwich",0,
46#> ANGLEUNIT["degree",0.0174532925199433]],
47#> ID["EPSG",4326]],
48#> CONVERSION["UTM zone 30N",
49#> METHOD["Transverse Mercator",
50#> ID["EPSG",9807]],
51#> PARAMETER["Latitude of natural origin",0,
52#> ANGLEUNIT["degree",0.0174532925199433],
53#> ID["EPSG",8801]],
54#> PARAMETER["Longitude of natural origin",-3,
55#> ANGLEUNIT["degree",0.0174532925199433],
56#> ID["EPSG",8802]],
57#> PARAMETER["Scale factor at natural origin",0.9996,
58#> SCALEUNIT["unity",1],
59#> ID["EPSG",8805]],
60#> PARAMETER["False easting",500000,
61#> LENGTHUNIT["metre",1],
62#> ID["EPSG",8806]],
63#> PARAMETER["False northing",0,
64#> LENGTHUNIT["metre",1],
65#> ID["EPSG",8807]]],
66#> CS[Cartesian,2],
67#> AXIS["(E)",east,
68#> ORDER[1],
69#> LENGTHUNIT["metre",1]],
70#> AXIS["(N)",north,
71#> ORDER[2],
72#> LENGTHUNIT["metre",1]],
73#> USAGE[
74#> SCOPE["Engineering survey, topographic mapping."],
75#> AREA["Between 6°W and 0°W, northern hemisphere between equator and 84°N, onshore and offshore. Algeria. Burkina Faso. Côte' Ivoire (Ivory Coast). Faroe Islands - offshore. France. Ghana. Gibraltar. Ireland - offshore Irish Sea. Mali. Mauritania. Morocco. Spain. United Kingdom (UK)."],
76#> BBOX[0,-6,84,0]],
77#> ID["EPSG",32630]]
78library(sf)
79
80nc_4267 <- read_sf(system.file("shape/nc.shp", package="sf"))
81nc_3857 <- st_transform(nc_4267, 3857)
82
83st_crs(nc_4267, parameters = TRUE)$units_gdal
84#> [1] "degree"
85st_crs(nc_3857, parameters = TRUE)$units_gdal
86#> [1] "metre"
87
Note that for some purposes st_is_longlat()
might be sufficient:
1library(sf)
2#> Linking to GEOS 3.8.1, GDAL 3.2.1, PROJ 7.2.1
3
4cities <- st_sf(city = "London", geometry = st_sfc(st_point(c(-0.1276, 51.5072))), crs = 4326)
5
6cities
7#> Simple feature collection with 1 feature and 1 field
8#> Geometry type: POINT
9#> Dimension: XY
10#> Bounding box: xmin: -0.1276 ymin: 51.5072 xmax: -0.1276 ymax: 51.5072
11#> Geodetic CRS: WGS 84
12#> city geometry
13#> 1 London POINT (-0.1276 51.5072)
14st_crs(cities)
15#> Coordinate Reference System:
16#> User input: EPSG:4326
17#> wkt:
18#> GEOGCRS["WGS 84",
19#> DATUM["World Geodetic System 1984",
20#> ELLIPSOID["WGS 84",6378137,298.257223563,
21#> LENGTHUNIT["metre",1]]],
22#> PRIMEM["Greenwich",0,
23#> ANGLEUNIT["degree",0.0174532925199433]],
24#> CS[ellipsoidal,2],
25#> AXIS["geodetic latitude (Lat)",north,
26#> ORDER[1],
27#> ANGLEUNIT["degree",0.0174532925199433]],
28#> AXIS["geodetic longitude (Lon)",east,
29#> ORDER[2],
30#> ANGLEUNIT["degree",0.0174532925199433]],
31#> USAGE[
32#> SCOPE["Horizontal component of 3D system."],
33#> AREA["World."],
34#> BBOX[-90,-180,90,180]],
35#> ID["EPSG",4326]]
36st_crs(st_transform(cities, crs = 32630))
37#> Coordinate Reference System:
38#> User input: EPSG:32630
39#> wkt:
40#> PROJCRS["WGS 84 / UTM zone 30N",
41#> BASEGEOGCRS["WGS 84",
42#> DATUM["World Geodetic System 1984",
43#> ELLIPSOID["WGS 84",6378137,298.257223563,
44#> LENGTHUNIT["metre",1]]],
45#> PRIMEM["Greenwich",0,
46#> ANGLEUNIT["degree",0.0174532925199433]],
47#> ID["EPSG",4326]],
48#> CONVERSION["UTM zone 30N",
49#> METHOD["Transverse Mercator",
50#> ID["EPSG",9807]],
51#> PARAMETER["Latitude of natural origin",0,
52#> ANGLEUNIT["degree",0.0174532925199433],
53#> ID["EPSG",8801]],
54#> PARAMETER["Longitude of natural origin",-3,
55#> ANGLEUNIT["degree",0.0174532925199433],
56#> ID["EPSG",8802]],
57#> PARAMETER["Scale factor at natural origin",0.9996,
58#> SCALEUNIT["unity",1],
59#> ID["EPSG",8805]],
60#> PARAMETER["False easting",500000,
61#> LENGTHUNIT["metre",1],
62#> ID["EPSG",8806]],
63#> PARAMETER["False northing",0,
64#> LENGTHUNIT["metre",1],
65#> ID["EPSG",8807]]],
66#> CS[Cartesian,2],
67#> AXIS["(E)",east,
68#> ORDER[1],
69#> LENGTHUNIT["metre",1]],
70#> AXIS["(N)",north,
71#> ORDER[2],
72#> LENGTHUNIT["metre",1]],
73#> USAGE[
74#> SCOPE["Engineering survey, topographic mapping."],
75#> AREA["Between 6°W and 0°W, northern hemisphere between equator and 84°N, onshore and offshore. Algeria. Burkina Faso. Côte' Ivoire (Ivory Coast). Faroe Islands - offshore. France. Ghana. Gibraltar. Ireland - offshore Irish Sea. Mali. Mauritania. Morocco. Spain. United Kingdom (UK)."],
76#> BBOX[0,-6,84,0]],
77#> ID["EPSG",32630]]
78library(sf)
79
80nc_4267 <- read_sf(system.file("shape/nc.shp", package="sf"))
81nc_3857 <- st_transform(nc_4267, 3857)
82
83st_crs(nc_4267, parameters = TRUE)$units_gdal
84#> [1] "degree"
85st_crs(nc_3857, parameters = TRUE)$units_gdal
86#> [1] "metre"
87st_is_longlat(nc_4267)
88#> [1] TRUE
89st_is_longlat(nc_3857)
90#> [1] FALSE
91
Community Discussions contain sources that include Stack Exchange Network