forked from Shardstone/trail-into-darkness
changed directory structure
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace Jovian.InspectorTools {
|
||||
public class RequiredAttribute : PropertyAttribute, IPropertyCondition {
|
||||
|
||||
public enum Scope {
|
||||
|
||||
None,
|
||||
SelfOrChild,
|
||||
SceneOnly,
|
||||
SceneOrParentPrefabOnly
|
||||
// ProjectOnly
|
||||
|
||||
/*
|
||||
* ProjectOnly has a quirk I can't figure out. If you reference a prefab in the project then it works fine.
|
||||
* However if you also have an instance of that prefab if the same scene/prefab then it will become a reference to that instance
|
||||
* instead of the one in the project. Even the icon changes in Unity. I think this may be a Unity Bug.
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
public readonly Scope scope;
|
||||
|
||||
public RequiredAttribute() {
|
||||
this.scope = Scope.None;
|
||||
}
|
||||
|
||||
public RequiredAttribute(Scope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public bool DoesPropertyMeetCondition(SerializedProperty property, out string description) {
|
||||
description = string.Empty;
|
||||
|
||||
if (property.propertyType != SerializedPropertyType.ObjectReference) {
|
||||
description = $"{property.name} is not an ObjectReference";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasValue = property.objectReferenceValue != null;
|
||||
|
||||
if (scope == Scope.None) {
|
||||
if (!hasValue) {
|
||||
description = $"{property.name} requires a value";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (scope == Scope.SelfOrChild) {
|
||||
if (!hasValue) {
|
||||
description = $"{property.name} requires a value";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetTargetGameObject(property, out GameObject parentGameObject)) {
|
||||
description = $"{property.name} does not have a GameObject parent";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetGameObject(property.objectReferenceValue, out GameObject childGameObject)) {
|
||||
description = $"{property.name} has a non-GameObject value";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsObjectSelfOrChild(parentGameObject, childGameObject)) {
|
||||
description = $"{property.name} value is not itself or it's child";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (scope == Scope.SceneOnly) {
|
||||
if (property.serializedObject.targetObject is ScriptableObject) {
|
||||
description = $"{property.name} SceneOnly is not supported on ScriptableObjects";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetTargetGameObject(property, out GameObject thisGameObject)) {
|
||||
description = $"{property.name} does not have a GameObject parent";
|
||||
return false;
|
||||
}
|
||||
|
||||
// scene is invalid - this is a prefab in the project.
|
||||
if (thisGameObject.scene.IsValid() == false) {
|
||||
// scene isn't valid, can't check
|
||||
description = $"{property.name} has a reference to GameObject not in a Scene";
|
||||
return true;
|
||||
}
|
||||
|
||||
PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
// scene is valid - prefab stage exists - this is a prefab
|
||||
if (prefabStage != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hasValue) {
|
||||
description = $"{property.name} requires a value";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (scope == Scope.SceneOrParentPrefabOnly) {
|
||||
if (property.serializedObject.targetObject is ScriptableObject) {
|
||||
description = $"{property.name} SceneOrParentPrefabOnly is not supported on ScriptableObjects";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetTargetGameObject(property, out GameObject thisGameObject)) {
|
||||
description = $"{property.name} does not have a GameObject parent";
|
||||
return false;
|
||||
}
|
||||
|
||||
// scene is invalid - this is a prefab in the project.
|
||||
if (thisGameObject.scene.IsValid() == false) {
|
||||
// scene isn't valid, can't check
|
||||
description = $"{property.name} has a reference to GameObject not in a Scene";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasTargetGameObject = TryGetGameObject(property.objectReferenceValue, out GameObject targetGameObject);
|
||||
|
||||
PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
// scene is valid - prefab stage exists - this is a prefab
|
||||
if (prefabStage != null) {
|
||||
// if the prefab open has this requirement on its root object
|
||||
if (prefabStage.prefabContentsRoot == thisGameObject) {
|
||||
if (hasTargetGameObject) {
|
||||
// if the value assigned is a gameObject
|
||||
if (!IsObjectSelfOrChild(thisGameObject, targetGameObject)) {
|
||||
// then fail if it's within the prefab
|
||||
description = $"{property.name} requires an object external to its prefab / hierarchy";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true; // if we don't have a target then it is valid (only inside this prefab of ourselves)
|
||||
}
|
||||
}
|
||||
|
||||
// if the prefab open is a different prefab, then we're valid if a value is assigned
|
||||
if (!IsObjectSelfOrChild(thisGameObject, targetGameObject)) {
|
||||
description = $"{property.name} requires an object external to its prefab / hierarchy";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// not in a prefab, value must be set to an object not within the scope of this attribute
|
||||
if (!(hasValue && !IsObjectSelfOrChild(thisGameObject, targetGameObject))) {
|
||||
description = $"{property.name} requires an object external to its prefab / hierarchy";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
See note above why this is not enabled
|
||||
|
||||
if (scope == Scope.ProjectOnly) {
|
||||
if (!hasValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isPersistent = EditorUtility.IsPersistent(property.objectReferenceValue);
|
||||
bool objectHasAssetPath = !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(property.objectReferenceValue));
|
||||
bool hasNoScene = false;
|
||||
bool isPrefabRoot = false;
|
||||
bool isInAssetDatabase = AssetDatabase.Contains(property.objectReferenceValue);
|
||||
|
||||
if (TryGetGameObject(property.objectReferenceValue, out GameObject targetGameObject)) {
|
||||
isPrefabRoot = PrefabUtility.IsOutermostPrefabInstanceRoot(targetGameObject);
|
||||
if (targetGameObject.scene.IsValid() == false) {
|
||||
// scene isn't valid, can't check
|
||||
//return true;
|
||||
hasNoScene = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if the target has a path, then it is an asset and that is valid!
|
||||
return objectHasAssetPath;
|
||||
}*/
|
||||
|
||||
if (!hasValue) {
|
||||
description = $"{property.name} requires a value";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetTargetGameObject(SerializedProperty property, out GameObject gameObject) {
|
||||
return TryGetGameObject(property.serializedObject.targetObject, out gameObject);
|
||||
}
|
||||
|
||||
private static bool TryGetGameObject(Object obj, out GameObject gameObject) {
|
||||
if (obj == null) {
|
||||
gameObject = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
gameObject = obj switch {
|
||||
Component component => component.gameObject,
|
||||
GameObject targetGameObject => targetGameObject,
|
||||
_ => null
|
||||
};
|
||||
return gameObject != null;
|
||||
}
|
||||
|
||||
private static bool IsObjectSelfOrChild(GameObject parentObject, GameObject queryObject) {
|
||||
if (parentObject == null || queryObject == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentObject == queryObject || queryObject.transform.IsChildOf(parentObject.transform);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace Jovian.InspectorTools.Internal {
|
||||
[CustomPropertyDrawer(typeof(RequiredAttribute))]
|
||||
public class RequiredAttributePropertyDrawer : PropertyDrawer {
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
if (property.propertyType != SerializedPropertyType.ObjectReference) {
|
||||
Debug.LogError($"Required should only be used on Unity Object references. Target='{property.serializedObject.targetObject.name}.{property.propertyPath}'");
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
return;
|
||||
}
|
||||
|
||||
RequiredAttribute requiredAttribute = (RequiredAttribute)attribute;
|
||||
|
||||
Color guiColor = GUI.color;
|
||||
|
||||
if (!requiredAttribute.DoesPropertyMeetCondition(property, out string text)) {
|
||||
RequiredUtil.LayoutRequired(ref position, text, false);
|
||||
RequiredUtil.DrawRequired(ref position, text, false);
|
||||
}
|
||||
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUI.PropertyField(position, property, GUIContent.none);
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
|
||||
GUI.color = guiColor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user