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

Read and Modify a SceneReference field in a custom Editor through SerializedProperty #108

Closed
lucypero opened this issue Nov 25, 2024 · 9 comments
Assignees

Comments

@lucypero
Copy link

lucypero commented Nov 25, 2024

Is it possible to read and modify a SceneReference field in a custom Editor through SerializedProperty, without wrapping the SceneReference in a ScriptableObject?

Related: #68

We've been wrapping scene references in scriptable objects just to do this, but I don't think it should be necessary

SerializedProperty destinationScene = serializedObject.FindProperty("destinationScene");
destinationScene.objectReferenceValue // Throws error "type is not a supported pptr value"

Maybe I don't know how to use SerializedProperty well.

@starikcetin starikcetin self-assigned this Nov 25, 2024
@lucypero
Copy link
Author

using destinationScene.boxedValue works, except when you want to apply the changes with:

        serializedObject.ApplyModifiedProperties();

It throws an error saying "Unsupported type SceneReference" :(

@starikcetin
Copy link
Owner

starikcetin commented Nov 26, 2024

Hi @lucypero. I am in the process of writing a readme section with examples for how to access and manipulate a SceneReference through editor code (both custom editors and editor windows). It can take me a couple of days.

In the meantime however, to get you started quickly, I will share a summary here:

If you have a field that is directly accessible in the editor script (public or internal with [InternalsVisibleTo]), then you can (and probably should) just reach out to it directly through your data store in an editor window, or through the target field provided to you in a custom editor.

The reason objectReferenceValue does not work is because it is for UnityEngine.Object types, not System.Object types. SceneReference class is a plain old C# object, therefore it can't be used with API that requires UnityEngine.Object. I haven't experimented with boxedValue yet, but it looks like exactly like what you want here because it operates on System.Object.

If you are on a version that does not have boxedValue, or if it turns out the boxedValue is not what I think it is, and you have a field that is not directly accessible (eg. private) then you would tap into reflection to reach out for the field. See an example:

private SerializedProperty _privateSceneRefProp;
private FieldInfo _privateSceneRefField;

private SceneReference PrivateSceneRef
{
    get => (SceneReference)_privateSceneRefField.GetValue(target);
    set => _privateSceneRefField.SetValue(target, value);
}

private void OnEnable()
{
    _privateSceneRefProp = serializedObject.FindProperty("privateSceneRef");
    _privateSceneRefField = target.GetType().GetField(_privateSceneRefProp.name, BindingFlags.Instance | BindingFlags.NonPublic);
}

Afterwards you can use _privateSceneRefProp for drawing and PrivateSceneRef for access and manipulation.

@lucypero
Copy link
Author

lucypero commented Nov 26, 2024

Thank you for the response.

If you have a field that is directly accessible in the editor script (public or internal with [InternalsVisibleTo]), then you can (and probably should) just reach out to it directly through your data store in an editor window, or through the target field provided to you in a custom editor.

Yeah, that's what I'm doing now. Doing something like this works

                Undo.RecordObject(target, "Modify Destination Scene");
                theTargetComponent.DestinationScene = destinationSceneRef; // setting the SceneReference
                Scene currentScene = theTargetComponent.gameObject.scene;
                EditorSceneManager.MarkSceneDirty(currentScene);

I was wondering if I could also do it through serializedObject

I haven't experimented with boxedValue yet, but it looks like exactly like what you want here because it operates on System.Object.

As i said here, I tried with boxedValue but the serialized object refuses to apply the changes. It throws an error saying "Unsupported type SceneReference". I don't know if what you said after this addresses this. I don't think so. Correct me if I'm wrong.

@lucypero
Copy link
Author

Is this an insurmountable limitation of Unity's SerializedObject? It just seems odd that it wouldn't support SceneReference, as it's a competely serializable type. I don't understand why it has to be a Unity Object.

@starikcetin
Copy link
Owner

starikcetin commented Nov 26, 2024

Is this an insurmountable limitation of Unity's SerializedObject? It just seems odd that it wouldn't support SceneReference, as it's a competely serializable type. I don't understand why it has to be a Unity Object.

I share the same frustration. Unity's editor code has many 'gotcha' moments where it just doesn't support things.

The problem here stems from how SerializedProperty provides access to the underlying data. If it was up to me, I would have designed the API to be generic, something along the lines of serializedProperty.GetValue<Foo>() and serializedProperty.SetValue(new Foo()). But Unity decided to hardcode different fields for different data types, such as colorValue, objectReferenceValue, floatValue, etc. Among those, there used to be none that would just give you a System.Object, which made it impossible to accomplish what you are trying to do without using reflection.

It seems boxedValue was introduced to fill that gap - and thank you for bringing it into my attention, I wasn't aware of it. It operates on System.Object, and I expect it to return the instance directly for reference types, and as the name implies return a boxed version for value types. Since SceneReference is a class, these should not be any boxing happening.

As i said here, I tried with boxedValue but the serialized object refuses to apply the changes. It throws an error saying "Unsupported type SceneReference". I don't know if what you said after this addresses this. I don't think so. Correct me if I'm wrong.

I will experiment with boxedValue soon and try to understand why it might give such an error. I will mention you then.

@lucypero
Copy link
Author

lucypero commented Nov 26, 2024

I appreciate it. Here's an update.

Even without using serializedObject, it will log an error if the component has a SceneReference field:

image

What logs this error is this call:

            EditorUtility.SetDirty(theComponentWithSceneReference);

Calling this function is necessary for the editor to register the changes made in the inspector.

What's interesting is that everything seems to be working well. Even the scene reference field can be modified and is serialized correctly. It just logs that error.

I'm trying to find a way to not log that error, but I can't seem to do it.

Edit: It doesn't log this error on newly created components. It seems like the error is logged due to outdated serialization. So, forcing reserialization on old components should fix everything.

@starikcetin
Copy link
Owner

@lucypero Thank you for the updates. I saw other people reporting this error on the Unity forums and they reached the same conclusion.

I don't think fixing outdated serialization is within our scope. It would be a very invasive piece of editor code as we would need to operate on the asset containing the SceneReference.

I will keep this issue open to track the documentation change. Let me know if there are any other problems with your use case.

@lucypero
Copy link
Author

lucypero commented Nov 28, 2024

Ok. To wrap it up, this is what works for sure:

// `theTargetComponent` being the component that holds a SceneReference field that you want to modify and serialize
Undo.RecordObject(theTargetComponent, "Modify Destination Scene");
theTargetComponent.DestinationScene = destinationSceneRef; // setting the SceneReference
EditorUtility.SetDirty(theTargetComponent);

@starikcetin
Copy link
Owner

Usage in Editor Code section has been added to the README file. It will be included in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants