Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements "RequiredType" validation attribute #346

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion Assets/NaughtyAttributes/Samples/DemoScene/DemoScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641258, b: 0.5748172, a: 1}
m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
Expand Down Expand Up @@ -364,6 +364,98 @@ MonoBehaviour:
readOnlyFloat: 3.14
nest2:
readOnlyString:
--- !u!1 &325876183
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 325876184}
- component: {fileID: 325876185}
m_Layer: 0
m_Name: RequiredType
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &325876184
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 325876183}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1148579784}
m_RootOrder: 33
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &325876185
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 325876183}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ab31f5807f7c4f1bafb31e268ebf7b16, type: 3}
m_Name:
m_EditorClassIdentifier:
gameObjectMustHaveRigidbody: {fileID: 0}
transformMustHaveRigidbody: {fileID: 0}
gameObjectMustHaveInterface: {fileID: 0}
gameObjectMustHaveComponent: {fileID: 0}
shouldNotShowInfoMessageWhenEmpty: {fileID: 0}
gameObjectMustHaveMultipleType: {fileID: 0}
--- !u!1 &341717952
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 341717953}
- component: {fileID: 341717954}
m_Layer: 0
m_Name: RequiredTypeTestObject2InheritsAll
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &341717953
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 341717952}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1148579784}
m_RootOrder: 35
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &341717954
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 341717952}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3b286cc1b60e4afe9a9a6b50a0210ab0, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &369789276
GameObject:
m_ObjectHideFlags: 0
Expand Down Expand Up @@ -943,6 +1035,9 @@ Transform:
- {fileID: 1706612702}
- {fileID: 369789277}
- {fileID: 1463483878}
- {fileID: 325876184}
- {fileID: 1333094825}
- {fileID: 341717953}
m_Father: {fileID: 0}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
Expand Down Expand Up @@ -1183,6 +1278,49 @@ MonoBehaviour:
int1: 0
nest2:
vector2: {x: 0.25, y: 0.75}
--- !u!1 &1333094824
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1333094825}
- component: {fileID: 1333094826}
m_Layer: 0
m_Name: RequiredTypeTestObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1333094825
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1333094824}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1148579784}
m_RootOrder: 34
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1333094826
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1333094824}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f1057b85cb7f4211be24e47f29b7f98d, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1380469384
GameObject:
m_ObjectHideFlags: 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace NaughtyAttributes
{
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class RequiredTypeAttribute : ValidatorAttribute
{
public Type[] RequiredTypes { get; private set; }

public bool ShowInfoMessageWhenEmpty { get; private set; }

public RequiredTypeAttribute(params Type[] requiredTypes)
{
RequiredTypes = requiredTypes;
ShowInfoMessageWhenEmpty = true;
}

public RequiredTypeAttribute(bool showInfoMessageWhenEmpty, params Type[] requiredTypes)
{
RequiredTypes = requiredTypes;
ShowInfoMessageWhenEmpty = showInfoMessageWhenEmpty;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ static ValidatorAttributeExtensions()
_validatorsByAttributeType[typeof(MaxValueAttribute)] = new MaxValuePropertyValidator();
_validatorsByAttributeType[typeof(RequiredAttribute)] = new RequiredPropertyValidator();
_validatorsByAttributeType[typeof(ValidateInputAttribute)] = new ValidateInputPropertyValidator();
_validatorsByAttributeType[typeof(RequiredTypeAttribute)] = new RequiredTypePropertyValidator();
}

public static PropertyValidatorBase GetValidator(this ValidatorAttribute attr)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Text;
using UnityEditor;
using UnityEngine;

namespace NaughtyAttributes.Editor
{
public class RequiredTypePropertyValidator : PropertyValidatorBase
{
public override void ValidateProperty(SerializedProperty property)
{
RequiredTypeAttribute requiredTypeAttribute = PropertyUtility.GetAttribute<RequiredTypeAttribute>(property);

if (requiredTypeAttribute == null)
{
return;
}

if (property.propertyType != SerializedPropertyType.ObjectReference)
{
NaughtyEditorGUI.HelpBox_Layout(requiredTypeAttribute.GetType().Name + " works only on reference types",
MessageType.Warning,
context: property.serializedObject.targetObject
);
return;
}

if (property.objectReferenceValue == null)
{
if (requiredTypeAttribute.ShowInfoMessageWhenEmpty)
{
StringBuilder infoMessage = new StringBuilder();
infoMessage.AppendLine(property.name + " must have ");

foreach (var baseType in requiredTypeAttribute.RequiredTypes)
{
infoMessage.AppendLine("\"" + baseType.FullName + "\"");
}

infoMessage.Append(requiredTypeAttribute.RequiredTypes.Length > 1
? " or one of their derived types"
: " or derived type");

NaughtyEditorGUI.HelpBox_Layout(infoMessage.ToString(), MessageType.Info,
context: property.serializedObject.targetObject);
}

return;
}

if (HasGameObject(property.objectReferenceValue, out GameObject gameObject))
{
bool hasValidationError = false;
StringBuilder errorMessage = new StringBuilder();

foreach (var baseType in requiredTypeAttribute.RequiredTypes)
{
var hasComponent = gameObject.GetComponent(baseType);
if (!hasComponent)
{
hasValidationError = true;
errorMessage.AppendLine(property.name + " must have \"" + baseType.FullName + "\" or derived type");
}
}

if (hasValidationError)
{
NaughtyEditorGUI.HelpBox_Layout(errorMessage.ToString(), MessageType.Error,
context: property.serializedObject.targetObject);
}
}
else
{
NaughtyEditorGUI.HelpBox_Layout(
requiredTypeAttribute.GetType().Name + " works only on types has GameObject",
MessageType.Warning,
context: property.serializedObject.targetObject
);
}
}

private bool HasGameObject(Object obj, out GameObject gameObject)
{
if (obj is GameObject go)
{
gameObject = go;
return true;
}

if (obj is Component component)
{
gameObject = component.gameObject;
return gameObject != null;
}

gameObject = null;
return false;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions Assets/NaughtyAttributes/Scripts/Test/RequiredTypeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using UnityEngine;

namespace NaughtyAttributes.Test
{
public interface IRequiredTypeTestInterface
{
}

public interface IRequiredTypeTestInterface2
{
}

public class RequiredTypeTest : MonoBehaviour
{
[RequiredType(typeof(Rigidbody))]
public GameObject gameObjectMustHaveRigidbody;

[RequiredType(typeof(Rigidbody))]
public Transform transformMustHaveRigidbody;

[RequiredType(typeof(IRequiredTypeTestInterface))]
public GameObject gameObjectMustHaveInterface;

[RequiredType(typeof(RequiredTypeTestTestObject))]
public GameObject gameObjectMustHaveComponent;

[RequiredType(showInfoMessageWhenEmpty: false, typeof(IRequiredTypeTestInterface))]
public GameObject shouldNotShowInfoMessageWhenEmpty;

[RequiredType(typeof(IRequiredTypeTestInterface), typeof(IRequiredTypeTestInterface2))]
public GameObject gameObjectMustHaveMultipleType;

[RequiredType(typeof(RequiredTypeTestTestObject))]
public RequiredTypeTest componentMustHaveAnotherComponent;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using UnityEngine;

namespace NaughtyAttributes.Test
{
public class RequiredTypeTestTestObject : MonoBehaviour, IRequiredTypeTestInterface
{
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using UnityEngine;

namespace NaughtyAttributes.Test
{
public class RequiredTypeTestTestObject2InheritsAll : RequiredTypeTestTestObject, IRequiredTypeTestInterface2
{
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.