Unityでシリアライズ可能なDateTimeを作ってみた

時間をエディタ上で操作したい、ということはままあると思う。

・プログラムではDateTimeで扱いたい
・ただそのままだとDateTimeはシリアライズ出来ないので、文字列で定義してDateTimeへパースする
・もしくはエディタ拡張で時間操作可能なGUIを表示する、等

ずっとなんかスマートに出来ないかなぁ、と思ってたので、「エディタ上では文字列で操作出来て」「プログラムではDateTimeをそのまま使える」クラスを作ってみた。

プログラム

using UnityEngine;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif

[Serializable]
public class SerializableDateTime : ISerializationCallbackReceiver
{
    const string Format = "yyyy-MM-dd HH:mm:ss";
    
    public DateTime Value;
    
    [SerializeField] string text;

    public SerializableDateTime() : this(new DateTime()) { }
    
    public SerializableDateTime(DateTime value)
    {
        Set(value);
    }
    
    public static implicit operator SerializableDateTime(DateTime value)
    {
        return new SerializableDateTime(value);
    }
    
    void Set(DateTime value)
    {
        Value = value;
        text  = ToString();
    }

    public void OnAfterDeserialize() 
    {
        DateTime newValue;
        if(DateTime.TryParse(text, out newValue))
        {
            Set(newValue);
        }
        else
        {   
            Set(Value);
        }
    }

    public void OnBeforeSerialize() { }

    public override string ToString()
    {
        return Value.ToString(Format);
    }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(SerializableDateTime))]
class SerializableDateTimeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PropertyField(position, property.FindPropertyRelative("text"), label);
    }
}
#endif

原理としては、文字列のシリアライズ変数(text)を定義して、エディタ拡張でそれを操作するGUIを表示する。
textの値がエディタ上で変更された場合、クラスのシリアライズ処理が呼ばれるので、そのコールバック内でDateTimeの値を更新する。
ISerializationCallbackReceiver がシリアライズ時に呼ばれるコールバックインターフェイス

使用例

f:id:okamura0510:20180722170345g:plain:w420

using UnityEngine;
using System;

public class Test : MonoBehaviour
{
    [SerializeField] SerializableDateTime startTime = new DateTime(2018, 3, 9);
    [SerializeField] SerializableDateTime endTime   = new DateTime(2018, 9, 2);
    
    void Start()
    {
        DateTime t0 = startTime.Value;
        DateTime t1 = endTime.Value;
    }
}

型変換演算子(implicit)を定義してるので、定義時にDateTimeをそのまま使えるようにしてる。 ValueがDateTimeの値。

エディタ上では時間にパース可能な文字列が指定された場合は変更される。
変な文字列を入力した場合は、変更不可能(変更前の値に戻す)。

最後に

全然関係ないけど、最近Go言語を触ってて、これが結構いい感じ♪
GAEとGo言語でアプリのサーバを作ってみようかと考えてるこの頃(´・ω・`)