Search Unity

Use Object Picker to pick an object from scene view

Discussion in 'Editor & General Support' started by EngineArtist, Jun 2, 2018.

  1. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Hi,

    Is there currently any way to assign a public GameObject-field in the Unity editor by simply picking an object in the scene view? This would, in my opinion, be the most straight-forward and usable way in many level design -related cases.

    The Object Picker lets you pick an object from a list (either Assets or Scene), but that's not very helpful if you have hundreds of objects named 'Door'. What you want is to be able to pick the Door-object you see in your scene view. If this functionality doesn't already exist, then I'd like to make the suggestion to add it to the Object Picker as default functionality (not removing the old way, which is useful in certain cases too).

    Cheers
     
  2. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Bump. Would be really appreciated!
     
  3. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    98% sure you could do this with a custom editor script - sounds like a minor coding challenge.

    I'm not sure that you could get it to work with all script components, but I know (again 98%) you could do as a one-off for each script component you'd need to apply this to. You'd have to customize it for each script class - that would probably be trivial, but it would be a separate editor script for each class.

    Basically:
    1. the editor script creates an inspector button that you put next to your public door field.
    2. on press you then do a loop waiting for a click in the scene view.
    3. you do gui raycast to get a hit test to get a reference to the object hit.
    4. this is the 2% I'm not sure about: getting that reference into your public field.

    And, you'd need a button for each public field - I think anyway.

    Someone with more knowledge can tell us if this is possible to do in a generic way - say extend the functionality of the object picker. Or if I am completely off base! o_O
     
  4. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    I made a CustomPropertyDrawer for GameObject, but there's a weird bug that if you have a Component with a List of GameObjects and you try to set one of them using the picker, the first element of the list is always set instead of the one you actually wanted.

    The logic is simple. When the eyedropper-button is pressed, I store a reference to the GameObjectPropertyDrawer we're currently drawing on screen. Once a GameObject is clicked in the SceneView, that GameObject is set to the field the stored GameObjectPropertyDrawer is responsible for. If there's a better way to do stunts like these, I'd be very happy to know! :)

    Here's the code:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5.  
    6. public class PropertyDrawerUtils {
    7.     public static Dictionary<string, Texture2D> icons = new Dictionary<string, Texture2D>();
    8.     public static bool objectPickerMode = false;
    9.     public static GameObjectPropertyDrawer pickedDrawer = null;
    10.     public static GameObject pickedObject = null;
    11.     public static bool callbackRegistered = false;
    12.  
    13.     public static void UpdateCallback(SceneView view) {
    14.         if (PropertyDrawerUtils.objectPickerMode) {
    15.             var cur = Event.current;
    16.             switch (cur.type) {
    17.                 case EventType.Layout: {
    18.                     HandleUtility.AddDefaultControl(0);
    19.                     break;
    20.                 }
    21.                 case EventType.MouseUp: {
    22.                     PropertyDrawerUtils.pickedObject = HandleUtility.PickGameObject(Event.current.mousePosition, true);
    23.                     PropertyDrawerUtils.objectPickerMode = false;
    24.                     foreach (var c in Selection.activeGameObject.GetComponents<Component>()) {
    25.                         EditorUtility.SetDirty(c);
    26.                     }
    27.                     break;
    28.                 }
    29.             }
    30.         }
    31.     }
    32.  
    33.     public static Texture2D GetIcon(string name) {
    34.         Texture2D result = null;
    35.         if (!icons.TryGetValue(name, out result)) {
    36.             var guids = AssetDatabase.FindAssets(name);
    37.             if (guids.Length > 0) {
    38.                 result = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(guids[0]));
    39.                 icons[name] = result;
    40.             }
    41.         }
    42.         return result;
    43.     }
    44. }
    45.  
    46.  
    47. [CustomPropertyDrawer(typeof(GameObject))]
    48. public class GameObjectPropertyDrawer: PropertyDrawer {
    49.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    50.         Debug.Log(this == PropertyDrawerUtils.pickedDrawer);
    51.         if (!PropertyDrawerUtils.callbackRegistered) {
    52.             PropertyDrawerUtils.callbackRegistered = true;
    53.             SceneView.duringSceneGui -= PropertyDrawerUtils.UpdateCallback;
    54.             SceneView.duringSceneGui += PropertyDrawerUtils.UpdateCallback;
    55.         }
    56.         if (!PropertyDrawerUtils.objectPickerMode && PropertyDrawerUtils.pickedDrawer == this) {
    57.             property.objectReferenceValue = PropertyDrawerUtils.pickedObject;
    58.             PropertyDrawerUtils.pickedObject = null;
    59.             PropertyDrawerUtils.pickedDrawer = null;
    60.         }
    61.         EditorGUI.BeginProperty(position, label, property);
    62.         property.objectReferenceValue = EditorGUI.ObjectField(new Rect(position.x, position.y, position.width - 16f, position.height), label, property.objectReferenceValue, typeof(GameObject), true);
    63.         var pickerRect = new Rect(position.x + position.width - 16f, position.y, position.height, position.height);
    64.         if (GUI.Button(pickerRect, "")) {
    65.             PropertyDrawerUtils.objectPickerMode = !PropertyDrawerUtils.objectPickerMode;
    66.             if (PropertyDrawerUtils.objectPickerMode) {
    67.                 PropertyDrawerUtils.pickedDrawer = this;
    68.             }
    69.             else {
    70.                 PropertyDrawerUtils.pickedDrawer = null;
    71.             }
    72.         }
    73.         GUI.DrawTexture(pickerRect, PropertyDrawerUtils.GetIcon("object_picker_16"), ScaleMode.ScaleAndCrop, true);
    74.         EditorGUI.EndProperty();
    75.     }
    76. }
    All help is much appreciated! Also, if people want, I can make a Package Manager -package of this on GitHub.

    Kind regards,
    Miika Vihersaari
     
    guycalledfrank and Gordon_G like this.
  5. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    well, as of the past week I have been beating my head against the wall with a custom inspector, so I feel your pain. Finally I cracked one nut just a couple of hours ago, and it's like victory! Now to the next one.

    I'm working on doing something similar to what you are doing - level design, but probably not the display in the inspector part of it, but selecting sets of objects in the scene and having the inspector update some variables to run some path finding on. I'm creating a dynamic sidewalk generation tool between houses in the scene using rectilinear paths. I'll be able to drag the houses around in the editor, and have the sidewalk meshes update in real time. (I hope - what may seem like a "minor coding challenge" has now taken a week. But hey, much of that was just dealing with everything I did not know!)

    OK, the list in the inspector is updated from the variables you have created in the base class that your custom editor "extends". Why not update those variables directly once you have selected your object in the scene, and let the editor update the list for you?

    That's essentially what I am looking at doing in order to pick the nodes (front doors) on the houses that the sidewalks will be generated between. So this is good to see what you are doing!

    I think that if you can link the inspector field you clicked on with the variable in the base class, then you can update that variable in the base class, and then the inspector will show you what you want.

    Good problem! I mean, with what you have going on now, if you choose a second item for the same list, it overwrites the first list item?
     
    Last edited: Feb 20, 2020
  6. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Well, it's not an editor actually, but a property drawer. A custom one that overrides the default property drawer for GameObject-fields. So instead of drawing a GameObject-field with the little object picker -button, mine draws that plus an eyedropper-button that lets you pick a GameObject straight from the editor scene view. Every GameObject-field in the editor is replaced with this. The image attached shows what I mean.

    And the bug is thus: In the image you see an example List<GameObject> with three elements. If I click on the second or third eyedropper, the first field gets assigned instead of the one I clicked. The next time I do this the second field is populated. This is so weird. Is there some magic going on with
    PropertyDrawerUtils.pickedDrawer == this
    ? I mean it's a straight-forward reference comparison but stuff might be going on that breaks my assumption.

    Kind regards,
    Miika Vihersaari
     

    Attached Files:

  7. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Hi Miika,

    OK, i had this thought but I deleted it in my previous message, because i wasn't sure if it had to do with the behavior you described. But now that you've clarified it seems like maybe it does:

    If you are adding items to a list using List.Add( object ), it is going to populate the list from first to last. It seems like you want to add an item at a specific index?

    Even if your not using List.Add to populate the list directly, maybe that is what the property drawer is doing for you - I'm speculating because I don't know what the rest of your code is doing.
     
    Last edited: Feb 21, 2020
  8. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    That's all the code there is. The TestComponent you see in the image is just a
    Code (CSharp):
    1. public class TestComponent: MonoBehaviour {
    2.     public List<GameObject> gameObjects;
    3. }
    for testing the CustomPropertyDrawer. Also, I'm not adding items to the list using List.Add(object). I'm not doing anything to it in code. I'm adding all the elements in the editor. You can try it and see how it works, you just need to copy all the code I pasted for the CustomPropertyDrawer. Then, make a MonoBehaviour with a List of GameObjects and attach said MonoBehaviour to an empty GameObject to reproduce the bug. You can put a couple cubes in the scene which you can pick with the eyedropper. Oh, you'll also need the icon for the eyedropper, lemme attach it to this message. Make sure you place it in the same folder with that code and name it 'object_picker_16.png'.

    Kind regards,
    Miika Vihersaari
     

    Attached Files:

  9. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Understood.

    I don't know that it's a bug.

    I ran a test of this and it turns out that with a list, the default inspector uses List.Insert( object, index ) behind the scenes to populate the list. Check out the following scene setup with debug output and code:

    upload_2020-2-21_17-25-16.png

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [ExecuteInEditMode]
    6. public class TestList : MonoBehaviour
    7. {
    8.     [SerializeField]
    9.     public List<GameObject> gameObjects;
    10.  
    11.     void Update()
    12.     {
    13.         Debug.Log( "List size = " + gameObjects.Capacity );
    14.         for( int i = 0; i < gameObjects.Capacity; i++ ) {
    15.             Debug.Log( "At index = " + i + " > " + gameObjects[i].name );
    16.         }
    17.     }
    18. }
    So, I haven't been over your code with a fine-toothed comb - if I could! I'm still learning, but it seems to me that that's what you need - a way to pass the specific index of the field you want to populate, just as the default behavior seems to.

    Now, maybe that's not possible with a custom picker and it defaults to a List.Add type of behavior where every item is added to the top of the list. I don't know.

    But does it really matter? In most cases I can think of the specific order of game objects in an inspector list doesn't matter because I am just iterating over the list to extract whatever information I've given them to identify specific objects.

    It seemed like you wanted a more convenient method of populating the inspector, and it seems like its working. The only problem for you is if order matters.
     
  10. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Yeah that's true. In many cases the order doesn't really matter, but in this particular case I'm using it for the order is very important and the bug is making the tool kinda useless. I'm attaching a screenshot of a Signaller-component. It has a list of outputs, and each output has a target GameObject. In this example there are two outputs, named 'Interact' and 'Collide'. If I click the eyedropper for 'Collide' and choose a GameObject from the scene, it assigns it to the GameObject-field in 'Interact', which is the wrong field.

    So inside the CustomPropertyDrawer I somehow need to know if the current field being drawm is actually the one we have chosen to assign. It seems that it always assigns the first field being rendered. This is also a question for Unity devs: What's the proper way to identify a PropertyDrawer inside its OnGUI-method?
     

    Attached Files:

  11. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Okay, finally got it to work using workaround, which I call the old 'counting ID' trick. Not the oldest one in the book, but pretty old. It works.

    Just to recap why I'm making this convenience tool for assigning GameObject-fields: In level design you sometimes have lots and lots of objects, which have the same name. For example doors or lights. There can be hundreds of them. As a level designer, when assigning a GameObject-field, I don't want to pick a door from a list of a hundred 'Doors'. I want to be able to point at a door in the scene view and assign it that way - very straight-forward and handy. That's why I made this tool, and I want to share it in the hopes that it will be of use to others as well :) Unity devs, feel free to integrate if you guys see value.

    As a reminder, this replaces all GameObject-fields, even ones on Unity's built-in Components. It's self-contained, and by that I mean that no modifications to external code is required. Just drop it in your project along with that eyedropper-icon and it works out of the box, boom.

    Here's the updated, working code for now (will give a github Package Manager link once I've done that):
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5.  
    6. public class PropertyDrawerUtils {
    7.     public static Dictionary<string, Texture2D> icons = new Dictionary<string, Texture2D>();
    8.     public static bool objectPickerMode = false;
    9.     public static int pickedDrawerID = 0;
    10.     public static GameObject pickedObject = null;
    11.     public static bool callbackRegistered = false;
    12.     public static int currentPropertyDrawerID = 0;
    13.  
    14.     public static void ResetPropertyDrawerID() {
    15.         currentPropertyDrawerID = 0;
    16.     }
    17.  
    18.     public static void UpdateCallback(SceneView view) {
    19.         if (PropertyDrawerUtils.objectPickerMode) {
    20.             var cur = Event.current;
    21.             switch (cur.type) {
    22.                 case EventType.Layout: {
    23.                     HandleUtility.AddDefaultControl(0);
    24.                     break;
    25.                 }
    26.                 case EventType.MouseUp: {
    27.                     PropertyDrawerUtils.pickedObject = HandleUtility.PickGameObject(Event.current.mousePosition, true);
    28.                     PropertyDrawerUtils.objectPickerMode = false;
    29.                     foreach (var c in Selection.activeGameObject.GetComponents<Component>()) {
    30.                         EditorUtility.SetDirty(c);
    31.                     }
    32.                     break;
    33.                 }
    34.             }
    35.         }
    36.     }
    37.  
    38.     public static Texture2D GetIcon(string name) {
    39.         Texture2D result = null;
    40.         if (!icons.TryGetValue(name, out result)) {
    41.             var guids = AssetDatabase.FindAssets(name);
    42.             if (guids.Length > 0) {
    43.                 result = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(guids[0]));
    44.                 icons[name] = result;
    45.             }
    46.         }
    47.         return result;
    48.     }
    49. }
    50.  
    51.  
    52. [CustomPropertyDrawer(typeof(GameObject))]
    53. public class GameObjectPropertyDrawer: PropertyDrawer {
    54.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    55.         ++PropertyDrawerUtils.currentPropertyDrawerID;
    56.         if (!PropertyDrawerUtils.callbackRegistered) {
    57.             PropertyDrawerUtils.callbackRegistered = true;
    58.             EditorApplication.update -= PropertyDrawerUtils.ResetPropertyDrawerID;
    59.             EditorApplication.update += PropertyDrawerUtils.ResetPropertyDrawerID;
    60.             SceneView.duringSceneGui -= PropertyDrawerUtils.UpdateCallback;
    61.             SceneView.duringSceneGui += PropertyDrawerUtils.UpdateCallback;
    62.         }
    63.         if (!PropertyDrawerUtils.objectPickerMode && PropertyDrawerUtils.pickedDrawerID == PropertyDrawerUtils.currentPropertyDrawerID) {
    64.             property.objectReferenceValue = PropertyDrawerUtils.pickedObject;
    65.             PropertyDrawerUtils.pickedObject = null;
    66.             PropertyDrawerUtils.pickedDrawerID = 0;
    67.         }
    68.         EditorGUI.BeginProperty(position, label, property);
    69.         property.objectReferenceValue = EditorGUI.ObjectField(new Rect(position.x, position.y, position.width - position.height, position.height), label, property.objectReferenceValue, typeof(GameObject), true);
    70.         var pickerRect = new Rect(position.x + position.width - position.height, position.y, position.height, position.height);
    71.         if (GUI.Button(pickerRect, "")) {
    72.             PropertyDrawerUtils.objectPickerMode = !PropertyDrawerUtils.objectPickerMode;
    73.             if (PropertyDrawerUtils.objectPickerMode) {
    74.                 PropertyDrawerUtils.pickedDrawerID = PropertyDrawerUtils.currentPropertyDrawerID;
    75.             }
    76.             else {
    77.                 PropertyDrawerUtils.pickedDrawerID = 0;
    78.             }
    79.         }
    80.         GUI.DrawTexture(pickerRect, PropertyDrawerUtils.GetIcon("object_picker_16"), ScaleMode.ScaleAndCrop, true);
    81.         EditorGUI.EndProperty();
    82.     }
    83. }
    Have a great weekend! :)

    Kind regards,
    Miika Vihersaari
     
    guycalledfrank, Zullar and Gordon_G like this.
  12. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Hey Miika, glad to hear you solved it!

    I think it was the only way to do it : I gather you had to associate each respective eyedropper button with the field(s) you want to populate? Because the inspector has no way to know otherwise. A button is just a button with no connection to anything. It's up to the code design to connect things together.

    When you use the default picker, or when you drag an object in the hierarchy or asset folder to an inspector object field, those connections have been codded internally.

    I'm really glad you've shared your code! As I am doing something similar - as I wrote, I have a scene of rooms with doors, and I want to connect a given door in one room to a door in another in order to create sidewalks between, and to redraw the sidewalks dynamically, avoiding other rooms in Scene View as you rearrange rooms. That's my goal anyway.

    As I envision it, each door will connect to any number or doors in other rooms. As I also envision limited number of rooms and doors, so I don't think I'll need to use/adapt your picker. I'm thinking a checkbox matrix of door-connections will do it, but I'm just at this point now in coding this custom inspector. I may use your code yet..

    But either way looking at your code has helped me to understand customPropertyDrawers. Thanks again!
     
  13. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    By the way, I caught this in your line of code earlier and was wondering what you are using this for:

    EditorUtility.SetDirty(c);


    Are you using it to force the inspector to update?

    If so, evidently there could be issues using SetDirty for that purpose.

    Check out this thread. I was having problems last night getting my CustomEditor and enclosed CustomPropertyDrawerer to update as I drag rooms around in the scene, and answer #6 helped me to fix it:

    https://answers.unity.com/questions/333181/how-do-you-force-a-custom-inspector-to-redraw.html

    Cheers!
     
  14. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Heysh,

    Yup that's correct, I used it for forcing an inspector redraw. Thank you for that link! I replaced the
    SetDirty
    with
    UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
    . This made the code a bit clearer and more self-explanatory, which is always good. I made a Package Manager -package, here's the github-link: https://github.com/EngineArtist/GameObjectPicker.git
    In Unity's Package Manager, click the plus-icon and select 'Add package from git URL' and paste that link there.

    Your project sounds intriguing! I'd be very interested in seeing sneak peek, if you don't mind sharing a screenshot?

    The door situation you mention sounds like you have it under control, although in the future if you have dozens of rooms with lots of doors, it might start to become tedious picking the doors from the hierarchy, unless you name each room. But yeah, feel free to use the Augmented Object Picker ;)
     
    Gordon_G likes this.
  15. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Great! - thanks for the link.

    Well, I don't have a fully working system yet. I'm just now to the point where I'm coding picker to link up the doors between the rooms, and then code the A* Path finding to create of list of positions for the point on each path. The paths will be rectilinear. Once you're happy with the paths, there will be a button on the inspector to bake those into meshes, though I'm going to give dynamic mesh generation a shot, as you drag the rooms around. I also want an option for allowing paths to cross or not.

    Here you can see a screen shot where I have the grid nodes represented as game objects that do collision testing on the doors' colliders, to determine if a door is over a valid grid location for the path finding algorithm. The green nodes are walkable; red nodes are unwalkable, and the blue are door nodes. Al this is working perfectly, and as I drag the parent rooms around the door nodes are identified and recorded in a dictionary of Door class, one element for each door.

    RectilinearPaths.png

    To code the connections between doors, I'm thinking a grid of check boxes is going to be easiest with door "exits" on side and door "entrances" along the top, so that each door exit can be connected to any number of other doors' entrances. But I don't know - I do know there will be some problems:

    For one, I need to make sure that no door connects to itself, and also that once, for example, you connect the exit of Door A to the entrance of Door B, you cannot also connect the exit Door B to the entrance of Door A - because the path finding needs a defined start and end target... So, the inspector validation is going to be a nightmare, and doing a matrix with more than say a dozen or so rooms may suck big itme - your picker idea may be a better option.

    Can you think of any other ways of doing it?

    Anyway, this started out as a kind of a self imposed coding challenge from someone who was asking for help in reddit. I thought I could knock it off in a few days and is going on over a week now, due to all of the learning and roadblocks, like what you had been dealing with!

    I also spent the first 2 days coding up a rectilinear connector system without using path finding or a grid - similar to line connectors you see in mind-mapping and graphing programs - but I decided after i got it working between two rooms with some obstacles that it was getting to difficult to have paths line up with each other, or to decide if paths cross. With a grid or "graph" system that part is easy. So I backtracked to using a grid and path finding idea.

    (Also, like, a couple of days ago freaking Visual Studio out of the blue wiped out my main Controller class and I lost the whole thing. And I it was not my error: I was working and suddenly noticed, looking at the VS tab names, that I had two Door.cs names up there. I clicked on each and there was my Controller class code in one, and the Door class code in the other. It's like VS somehow got confused. Then I made the mistake of quitting Unity and when I opened my project up my Controller.cs file was completely gone - nowhere in the project folder, or trash, nada, kaput. What-the...??? Fortunately it isn't that big, and I knew it by heart so it took less than hour to rebuild. Now I'm making a folder backup with every major change. Anyway, /rant over! )

    Thanks for any ideas, Miika & best,
    Gordon
     
    Last edited: Feb 23, 2020
  16. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Sorry for the slow reply!

    When you have the matrix of connections, is it something like the following, where 0 to 5 are doors and X means a connection?

    Code (CSharp):
    1.   0 1 2 3 4 5
    2. 0 . . . . . .
    3. 1 X . . . . .
    4. 2 . . . . . .
    5. 3 . X . . . .
    6. 4 X . . X . .
    7. 5 . . . . . .
    If so, then the connections on the diagonal axis mean that a door connects to itself, so they can be disallowed:

    Code (CSharp):
    1.   0 1 2 3 4 5
    2. 0 # . . . . .
    3. 1 . # . . . .
    4. 2 . . # . . .
    5. 3 . . . # . .
    6. 4 . . . . # .
    7. 5 . . . . . #
    Also, you mentioned that if the exit of a door A is connected to the entrance of a door B, then we can't allow the exit of B to be connected to the entrance of A. This can be easily checked by checking the 'mirror square', mirrored along the diagonal. For example, the two X's are each others' mirrors in the following diagram:

    Code (CSharp):
    1.   0 1 2 3 4 5
    2. 0 # . . . . .
    3. 1 . # . . . .
    4. 2 . . # . . X
    5. 3 . . . # . .
    6. 4 . . . . # .
    7. 5 . . X . . #
    But now I have an additional question: What's the difference between a door's exit and its entrance? Do they need to be denoted separately, or will a simple 'connection' suffice?

    By the way, I made two more tools. This time for GameObject signalling and arbitrary attribute storing:
    - https://github.com/EngineArtist/GameObjectAttributes.git
    - https://github.com/EngineArtist/GameObjectInputOutput.git

    GameObject InputOutput lacks proper documentation, but the idea is that GameObjects should have a unified way of signalling each other and responding to signals. For example, if you have a lever in one of your rooms, and you want it to open a door once the player flips it, this package allows you to add an output "Used" to the lever, and an input "Open" to the door, and then connect the output from the lever to the input of the door. But then you could connect the lever's output to any input of any GameObject, so you're not limited to doors.

    It's a very modular, flexible and powerful tool. And it's only one MonoBehaviour. No complicated, tangled up contraptions. I aim for a simple design that enables as much as possible. The more I've done programming, the more I've noticed that simpler designs tend to have more power and potential, and tend to be more modular, if that makes sense. Yet, what's paradoxical is that the simpler the design, the more work it needs, even though there's less code. You just keep looking at it from as many different perspectives and angles as you can, and try to understand the thing you're building. As a result of this long and difficult process usually comes out something that fits nicely into many places, and clicks into those places like a seat belt. A very satisfying click!

    Kind regards,
    Miika Vihersaari
     
    Gordon_G likes this.
  17. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Hey, Miika, thanks for your reply. Your diagram is what I figured out, and it ended up being pretty easy.

    in the inspector I draw the checkbox matrix within a disabled block. First I check to see if the door connection is disabled, and if it is I don't draw the button in the current state.

    upload_2020-2-27_16-28-24.png


    I wanted to use bools to hold the door connection value, but I need 3 values, connected, not connected and disabled. I tried using a Nullable bool ( bool?[] ) but those are not seriaIizable, so I ended up using an int array with -1 = disabled, 0 = false and 1= true .

    Then when the user clicks on a button it's easy because i have drawn those checkboxes in a loop, all I need to do is pass the row and column index to a function that sets the door with the column and row reversed to -1 (disabled).

    For example, if you click on checkbox for door 0 in column for door 5, the indexes are 0,5 and therefore in the door array the door with index 5,0 is disabled.

    It works perfectly.
     
  18. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    But now I have an additional question: What's the difference between a door's exit and its entrance? Do they need to be denoted separately, or will a simple 'connection' suffice?

    By the way, I made two more tools. This time for GameObject signalling and arbitrary attribute storing:
    - https://github.com/EngineArtist/GameObjectAttributes.git
    - https://github.com/EngineArtist/GameObjectInputOutput.git

    [/QUOTE]

    Sorry I wanted to address your points in my previous reply.

    I keep a dictionary of doors, with a string door ID as the key. Each door is connected to a graph node i and j value in the node array, (if it collides with one with one of the physical nodes ) do the structure of each dictionary door element, in summary:

    Door Class
    • string id: this door ID < this is just the name of the door: door_0, door_1, ..
    • string exit : a graph node ID < this is like wise the name of the node: node_0,0 , node_2,5
    • List<string> doorEntrances : list of other door IDs

    I do use a second array to manage the door connections and that is what is used to draw the inspector grid. Those elements hold the values -1,0,1. When I click on a checkbox in the inspector for particular row for a door, that column door ID is either added or removed from that door's rowEntrances list, and the connections array is updated for the current door to 0 or 1. The reverse connection is updated to -1, and then the inspector is updated to disable the reverse connections.

    So, one doors exit is connected to another doors entrance through the doors dictionary. It's just a conceptual way of hooking things together.

    When I come to doing the path finding, I loop through the doors dictionary. For each door the path's start node = door's exit node, and then a nested loop through the doorEntrances list, and for each door_ID there I set the path's end node to a lookup in the door dictionary for its respective exit node. I start drawing each path in this nested loop, as I aquire the end path nodes.

    I hope that make sense!

    I am now rewriting this part of it, because I am having problems with serialization making copies of my graph node elements.

    For example, I have a node of physical objects to represent the graph node locations with collision detectors that activate when they hit a room or door. If it is a door I linked the door exit in the doors dictionary to the actual graph node element in the graph nodes array.

    But this ended up making a copy of the graph node object. I discovered this when I used those for the start node and end node in path finding, and comparing it with the graph nodes array. The comparison in A* was failing. The reason was those were two different objects, and somehow serialization of the serializable door dictionary in my inspector was making a copy of the nodes. I was pulling my hair out on that one for a day before I discovered the issue.

    I'm still grappling with serialzaiton and persistence inspector.. If I recompile the inspector, or if I quit - I have to recreate the nodes and reconnect the doors (I have inspector button to do all that). I have one Vector 2 in the inspector that will just not remember its values, and one variable in the inspector I use for an Editor.style comes up null at random. So I am sure I don't have the inspector set up quite right, but also rewriting links to objects as string IDs rather than real links will take care of most of it.

    Have you experience anything like that?

    Thanks for the links to your projects! And thanks for your feedback!
     
  19. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Awesome! Seems you have that matrix nailed down!

    I've had several cases where the Inspector won't remember a value in a custom field, and the solutions were different in each case. In your case, is the Vector2 on a GameObject Component? If so, have you tried flagging the entire GameObject as 'modified' by calling
    EditorUtility.SetDirty
    ? Can you show me the editor code that handles (reads, writes) that Vector2?
     
  20. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Hi Miika, sorry, but I already created a struct equivalent of a Vector2Int (did I say that's what it was?) and a custom property drawer to handle it. So problem solved.

    I'm on to another dream for the moment. I'm doing a interactive visualization of the Unity camera with 2 cameras in a split screen. Camera represented in the right pane the provides the view on the left. I have an interactive RenderTexture taken from the left camera on the movable near plane. You can obit around the camera in the right view like in editor scene view, and also rotate the left camera (seen on the right) on the x-axis. Clicking in the left pane shows RayCasts in the right view. I found this runtime gizmo drawing library that does all the lines for me, so you don't need gizmos on for it to work.

    I'm almost done with it and I want to get back to this level design project next week!

    upload_2020-3-3_17-51-15.png