ScriptableObject .asset, valeur non sauvegardée en quittant

Questions à propos du scripting. Hors Shader, GUI, Audio et Mobile.
Katasa
Messages : 3
Inscription : 03 Sep 2014 14:14

ScriptableObject .asset, valeur non sauvegardée en quittant

Message par Katasa » 22 Déc 2014 17:58

Bonjour,

Je me permets de poster sur ce forum dans l'espoir d'avoir une réponse, ou une piste, à un, voir deux, de mes problèmes. Pour expliquer rapidement mon code, j'ai une classe qui hérite de ScriptableObject, un custom inspector qui agit sur la classe précédement citée, et une classe permettant de sauvegarder les ScriptableObject en .asset

Le code permettant de créer les .asset à partir de la classe ScriptableObject "World". Le code provient d'un "live training" disponible sur le site de Unity.

Code : Tout sélectionner

public static class CustomAssetUtility
{
    [MenuItem("Assets/Gameobject/Custom Assets/World")]
	public static void CreateWorld () {
		var world = ScriptableObject.CreateInstance(typeof(World));

		AssetDatabase.CreateAsset(world, "Assets/new_world.asset");
        AssetDatabase.SaveAssets();
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = world;
		// Print the path of the created asset
		Debug.Log(AssetDatabase.GetAssetPath(world));
	}
}

Ma classe "World" qui hérite de ScriptableObject, qui contient un int worldX, et un tableau de Cube (définit juste en dessous). Le tableau est initialisé dans un autre bout de code que je n'ai pas montré car inutile pour ma question.

Code : Tout sélectionner

[System.Serializable]
public class World : ScriptableObject
{
	[SerializeField]
	private int _worldX;
	[SerializeField]
	private Cube[] _cube;

	#region Get/Set
	public int worldX {get; set;}
	public Cube[] cube {get; set;}
	#endregion
}

[System.Serializable]
public class Cube
{
	public bool is_destroyable;
}
Le custom inspector. Dans ce code, j'utilise 2 méthodes différentes pour intéragir avec le ScriptableObject (à terme, je n'aurai plus qu'une seule méthode, mais c'est une des questions que je veux vous poser)

Code : Tout sélectionner

[CustomEditor(typeof(World)), CanEditMultipleObjects]
public class WorldInspector : Editor
{
    SerializedObject world_serialize;
    World world_class;

    // properties
    private SerializedProperty _worldX;
    private SerializedProperty test;

    void Awake()
    {
        world_class = (World)target;
        Debug.Log(world_class.worldX);
    }

    void Init_ScriptableObject_Properties()
    {
        test = world_serialize.FindProperty("cube");
        if (test.isArray)
        {
            Debug.Log("yes");
            for (int i = 0; i < test.arraySize; i++)
                Debug.Log(test.GetArrayElementAtIndex(i).FindPropertyRelative("is_destroyable");
        }
    }

    void OnEnable()
    {
        world_class = (World)target;
        world_serialize = new SerializedObject(target);

        Init_ScriptableObject_Properties();

        Debug.Log(world_class.worldX);
    }

    // define the world size
    void gui_world_size()
    {
        world_class.worldX = EditorGUILayout.IntField(world_class.worldX);
        EditorUtility.SetDirty(world_class);
    }

    public override void OnInspectorGUI()
    {
        gui_world_size();
    }
}
Maintenant, je vais vous exposer mes problèmes :

- Si je change la valeur de mon int worldX via l'inspector (par exemple : 30), et que je recompile mon code, la valeur dans l'inspector ne change pas, elle est toujours égale à 30. En revanche, si je quitte Unity et que je le relance, la valeur de mon int retombe à 0 (c'est en tout cas ce que m'affiche le débug.log de la fonction Awake() du custom inspector, et l'inspector). Pourquoi alors que je fais un SetDirty ?

- On m'a conseillé d'utiliser la seconde méthode pour interagir avec les ScriptableObject (world_serialize = new SerializedObject(target);), mais je me retrouve avec le problème suivant : Comment accéder à mon tableau et aux valeurs contenues dans chacune des classes de ce tableau ? J'ai déjà fait un essai dans la fonction "void Init_ScriptableObject_Properties()" visible dans le custom inspector, mais ça ne fonctionne pas. Une idée ?

Je vous remercie d'avoir pris le temps de lire, et je vous remercie d'avance pour vos éventuelles réponses.

Bonne journée/soirée, et passez de bonnes fêtes !

Avatar de l’utilisateur
Loulou
Messages : 239
Inscription : 23 Nov 2011 01:04
Localisation : Paris
Contact :

Re: ScriptableObject .asset, valeur non sauvegardée en quitt

Message par Loulou » 24 Déc 2014 16:50

Salut

Alors il faut faire attention tu mélanges les deux façons de faire des scripts Editor.

La première est en utilisant la variable target qui permet de manipuler une variable typées et donc de pouvoir accéder et modifier tous ses membres (je penses a ton problème de tableau) comme une instance classique. C'est dans ce cas la que le SetDirty est utile. Mais attention le undo et la multi édition ne sont pas gérées automatiquement.

Code : Tout sélectionner

[CustomEditor(typeof(World))]
public class WorldInspector : Editor
{
    World world_class;

    void OnEnable()
    {
        world_class = (World)target;
    }

    void gui_world_size()
    {
        world_class.worldX = EditorGUILayout.IntField(world_class.worldX);
        if (GUI.changed)
                EditorUtility.SetDirty(world_class);
    }

    public override void OnInspectorGUI()
    {
        gui_world_size();
    }
}
La seconde est un peu plus "tricky". Il faut manipuler des serializedObject et serializedProperty a la place des membres de la classe en question. D’où la manipulation délicate d'un tableau. Et la le SetDirty est remplacé par serializedObject.Update() et serializedObject.ApplyModifiedProperties()

Code : Tout sélectionner

[CustomEditor(typeof(World))]
public class WorldInspector : Editor
{
    private SerializedProperty _worldX;
	
	void OnEnable()
    {
        _worldX = serializedObject.FindProperty("_worldX");
    }

    void gui_world_size()
    {
        EditorGUILayout.PropertyField(_worldX, new GUIContent("Wordl X");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update ();
        gui_world_size();
        serializedObject.ApplyModifiedProperties ();
    }
}
Une dernier chose, il me semble que cette ligne est inutile, la classe Editor comportant déjà une variable serializedObject

Code : Tout sélectionner

world_serialize = new SerializedObject(target);
En résumé il est effectivement recommandé d'utiliser la seconde méthode pour ne pas avoir a gérer le undo et le multi editing manuellement. Mais elle est plus contraignante.
Commence par régler ton problème de sauvegarde. Pour les array en serializedProperty je suis preneur si quelqu'un a déjà approfondi le sujet. N'ayant que survolé le problème, je vais me renseigner un peu plus avant de proposer une solution.

Katasa
Messages : 3
Inscription : 03 Sep 2014 14:14

Re: ScriptableObject .asset, valeur non sauvegardée en quitt

Message par Katasa » 24 Déc 2014 23:59

Bonsoir et merci de ta réponse.

Je viens de me rendre compte que l'on se connaissait, puisque c'est justement toi qui m'a conseillé d'utiliser la deuxième méthode (mercredi soir d'il y a 2 semaines) ;)

Alors dans le code que je présente, je mélange volontairement les 2 méthodes juste pour vous montrer comment j'utilise chacune d'elles. Bien sûr, à terme, une des deux méthodes sera supprimée de mon code.

Concernant l'accès au tableau avec la deuxième manière, j'ai trouvé ce post : http://forum.unity3d.com/threads/workin ... ipt.97356/ Je n'ai pas encore testé (préparation des fêtes tout ça tout ça...) mais ça a l'air d'être une bonne piste.

Par contre, concernant la sauvegarde avec la première méthode, ça ne fonctionne pas. J'ai effectué les tests suivants :

- Je supprime temporairement le CustomInspector, je modifie la valeur de mon worldX, je quitte Unity et je le relance, ma valeur est bien sauvegardée.

- J'ajoute le "if (GUI.changed)" qu'il me manquait dans mon CustomInspector et refait l'opération ci-dessus (sans la suppression du CustomInspector bien sûr). Mon worldX vaut 0 à la ré-ouverture de Unity

- Je commente mon CustomInspector, je crée un nouveau script et copie/colle ton code car je me dis que dans le mien il doit y avoir quelque chose que je n'arrive malheureusement pas à voir et je refais la même manipulation qu'au-dessus (avec un CTRL + S avant de quitter, au cas où), mais non...en revenant dans Unity, mon worldX vaut de nouveau 0...

Si ça se trouve c'est une erreur toute bête, mais je ne comprends vraiment pas ce qui cloche.

Merci d'avoir prix le temps de me répondre :)

Joyeux Noël, et à très bientôt !

Katasa
Messages : 3
Inscription : 03 Sep 2014 14:14

Re: ScriptableObject .asset, valeur non sauvegardée en quitt

Message par Katasa » 29 Déc 2014 05:50

Bonsoir !

J'ai trouvé une solution pour accéder aux valeurs de mon tableau de classes avec la "seconde méthode". Je suis cependant toujours preneuse de la solution à mon premier problème, histoire de ne pas mourir bête.

Voici la ligne de code qui "corrige" le second problème (et au passage Loulou, je te confirme que "serializeObject" suffit, c'est effectivement inutile de faire "world_serialize = new SerializedObject(target)")

Code : Tout sélectionner

Debug.Log("element " + serializedObject.FindProperty("_cube").GetArrayElementAtIndex(0).FindPropertyRelative("is_destroyable").boolValue);
"_cube" est mon tableau unidimensionnel, 0 est "l'index 0" dans mon tableau, et "is_destroyable" est une variable contenue dans ma classe.

J'ai également testé avec des int, string et vector3. Ca fonctionne parfaitement !

Mon problème venait entre autre du fait que :

- je doutais des fonctions à utiliser
- j'avais un get/set pour mon tableau, et que naïve comme je suis, je croyais que le set copiait dans ce "_cube" un autre tableau que je remplissais ailleurs. C'est la raison pour laquelle le FindPropertyRelative me renvoyait une erreur de non référence que je ne comprenais pas. _cube était toujours vide.

Je ne sais pas comment ça fonctionne avec plusieurs dimensions. J'en profite pour montrer ce lien (http://www.dotnetperls.com/flatten-array et http://www.dotnetperls.com/jagged-array) qui parle d'optimisation de tableau et qui m'a été très utile pour transformer un tableau à 3 dimensions (ou une liste) en un tableau unidimensionnel.

Bonne soirée et merci pour l'aide :)

Répondre

Revenir vers « Scripting »