Reputation: 11325
What am I trying to do and achieve :
I have this package first person exploration kit in my project that I bought downloaded and imported : https://assetstore.unity.com/packages/templates/systems/first-person-exploration-kit-60434
Now to make an object in the to be interactable and I'm talking about picking up an object or doing any action yet with it but only to be interactable meaning when you get close to the object it will highlight the object or in my case it will rotate another object but the idea is to make an object to be interactable.
For this you need the object to have either SkinnedMeshRenderer component or just MeshRenderer and Box Collider and one of the script from the package for iteractable.
My goal is to make it easier to convert an object to be interactable. Instead searching for object/s you want to be interactable and manual adding to it the script or box collider or checking if it's having mesh renderer or skinned mesh renderer I want to make it automatic as much as possible.
The idea is that you will select in the Hierarchy some objects can be 1 or 100 make right click and select Make Object Interactable.
It will loop over the selection of gameobjects will search what objects have the mesh renderer or skinned mesh renderer if at all could be the parent selected object or it's children once it found that the object have any mesh renderer component it will get the parent in any case and make the parent object interactable.
The reason I need it to have a mesh renderer or skinned mesh renderer is to be able to get the object size and when adding to it a box collider to set the box collider size to fit automatic to the object size.
That's why I need to make it all in editor mode not in runtime and I also want to make that the script will automatic detect changes in interactable objects for example if I removed any of the components from interactable object like box collider, mesh renderer , the interactable script then update the list and the drawn list in the inspector and remove the item or if I delete an interactable object or if there are already some objects that are interactable and I'm adding mono script again to a gameobject if for some reason I removed it then automatic detect all the interactable already objects and update the list/s.
Each interactable object I did that will be tagged as "Interactable Item" to be easier to detect already interactable objects.
Now it's showing missing and I want that it will remove it from the list.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using Whilefun.FPEKit;
[CustomEditor(typeof(InteractableObjects))]
public class InteractableObjectsEditor : Editor
{
private static List<GameObject> interactedobjects = new List<GameObject>();
private static bool interacted = false;
[MenuItem("GameObject/Make Object Interactable", false, 30)]
public static void GeneratePickupItems()
{
if (Selection.gameObjects.Length > 0)
{
interactedobjects.Clear();
for (int i = 0; i < Selection.gameObjects.Length; i++)
{
var renderer = TraverseHierarchy(Selection.gameObjects[i].transform, i);
if (Selection.gameObjects[i].GetComponent<FPEInteractableActivateScript>() == null)
{
if (Selection.gameObjects[i].GetComponent<BoxCollider>() == null)
{
AddBoxCollider(renderer, i);
}
else
{
DestroyImmediate(Selection.gameObjects[i].GetComponent<BoxCollider>());
AddBoxCollider(renderer, i);
}
Selection.gameObjects[i].AddComponent<FPEInteractableActivateScript>();
}
Selection.gameObjects[i].tag = "Interactable Item";
interactedobjects.Add(Selection.gameObjects[i]);
}
interacted = true;
}
}
private static Renderer TraverseHierarchy(Transform root, int childIndex)
{
Renderer renderer = new Renderer();
if (root.childCount == 0 || root.childCount > 0)
{
var skinnedMesh = root.GetComponent<SkinnedMeshRenderer>();
if (skinnedMesh) return skinnedMesh;
var mesh = root.GetComponent<MeshRenderer>();
if (mesh) return mesh;
}
if (root.childCount > 0)
{
foreach (Transform child in root)
{
// Deal with child
if (child.GetComponent<SkinnedMeshRenderer>() != null)
{
renderer = Selection.gameObjects[childIndex].GetComponentInChildren<SkinnedMeshRenderer>(true);
}
if (child.GetComponent<MeshRenderer>() != null)
{
renderer = Selection.gameObjects[childIndex].GetComponentInChildren<MeshRenderer>(true);
}
}
}
return renderer;
}
private static void AddBoxCollider(Renderer renderer, int index)
{
Selection.gameObjects[index].AddComponent<BoxCollider>();
var bounds = renderer.bounds;
var size = bounds.size;
var center = bounds.center;
var boxCollider = Selection.gameObjects[index].GetComponent<BoxCollider>();
size = boxCollider.transform.InverseTransformVector(size);
center = boxCollider.transform.InverseTransformPoint(center);
boxCollider.size = size;
boxCollider.center = center;
}
InteractableObjects _target;
private ReorderableList _myList;
public void OnEnable()
{
_myList = new ReorderableList(serializedObject, serializedObject.FindProperty("interactableObjects"))
{
draggable = false,
displayAdd = false,
displayRemove = false,
drawHeaderCallback = rect => EditorGUI.LabelField(rect, ""/*"My Reorderable List"*/, EditorStyles.boldLabel),
drawElementCallback = (rect, index, isActive, isFocused) =>
{
if (index > _myList.serializedProperty.arraySize - 1) return;
var element = _myList.serializedProperty.GetArrayElementAtIndex(index);
var color = GUI.color;
EditorGUI.BeginDisabledGroup(true);
{
GUI.color = Color.green;
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 20, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
}
EditorGUI.EndDisabledGroup();
GUI.color = color;
}
};
}
public override void OnInspectorGUI()
{
var list = serializedObject.FindProperty("interactableObjects");
serializedObject.Update();
if (interacted)
{
interacted = false;
foreach (var newEntry in interactedobjects)
{
// check if already contains this item
bool alreadyPresent = false;
for (var i = 0; i < list.arraySize; i++)
{
if ((GameObject)list.GetArrayElementAtIndex(i).objectReferenceValue == newEntry)
{
alreadyPresent = true;
break;
}
}
if (alreadyPresent) continue;
// Otherwise add via the serializedProperty
list.arraySize++;
list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = newEntry;
}
interactedobjects.Clear();
}
_myList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
public object GetParent(SerializedProperty prop)
{
var path = prop.propertyPath.Replace(".Array.data[", "[");
object obj = prop.serializedObject.targetObject;
var elements = path.Split('.');
foreach (var element in elements.Take(elements.Length - 1))
{
if (element.Contains("["))
{
var elementName = element.Substring(0, element.IndexOf("["));
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue(obj, elementName, index);
}
else
{
obj = GetValue(obj, element);
}
}
return obj;
}
public object GetValue(object source, string name)
{
if (source == null)
return null;
var type = source.GetType();
var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (f == null)
{
var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p == null)
return null;
return p.GetValue(source, null);
}
return f.GetValue(source);
}
public object GetValue(object source, string name, int index)
{
var enumerable = GetValue(source, name) as IEnumerable;
var enm = enumerable.GetEnumerator();
while (index-- >= 0)
enm.MoveNext();
return enm.Current;
}
}
And mono script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Whilefun.FPEKit;
public class InteractableObjects : MonoBehaviour
{
public List<GameObject> interactableObjects = new List<GameObject>();
}
If I delete interactable objects from the hierarchy then this is how it looks like in the inspector of the list :
When I delete interactable objects it should update the list and the drawing in the editor script and instead showing Missing just showing only the item that is interactable object.
Upvotes: 0
Views: 740
Reputation: 11325
This is working but I'm not sure if it's the right way to do it :
In the mono script:
I added attribute to the script to make it run in editor mode. Then in the Update I'm checking for null items and remove them.
Is that fine way to do it ?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Whilefun.FPEKit;
[ExecuteInEditMode]
public class InteractableObjects : MonoBehaviour
{
public List<GameObject> interactableObjects = new List<GameObject>();
private void Update()
{
for(int i = 0; i < interactableObjects.Count; i++)
{
if(interactableObjects[i] == null)
{
interactableObjects.RemoveAt(i);
}
}
}
}
Upvotes: 0
Reputation: 41
There are a couple ways you could do this: 1: every time an interactable object is deleted check the whole list for null objects and remove them. 2. Use the OnDestroy method with listScript.SendMessage("delete object function", thisGameObject); in order to send the object reference to the script with the list for removal.
Upvotes: 1