added more utilty packges

This commit is contained in:
Sebastian Bularca
2026-03-29 18:59:24 +02:00
parent ee97b2fec3
commit 71b432e253
131 changed files with 7674 additions and 54 deletions

View File

@@ -0,0 +1,280 @@
using System;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
namespace InspectorToolkit {
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 InspectorToolkit.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