コンテンツにスキップ

カスタムアクターの実装 ​

アクターは、名前、外観、可視性、およびトランスフォーム(位置、回転、スケール)によって定義されるシーンエンティティです。外観、可視性、およびトランスフォームを時間の経過とともに非同期に変更できます。アクターの例には、キャラクター、背景、テキストプリンター、選択肢ハンドラーなどがあります。

アクターは IActor インターフェースとその派生インターフェースによって表されます。

  • ICharacterActor
  • IBackgroundActor
  • ITextPrinterActor
  • IChoiceHandlerActor

各アクターインターフェースは複数の実装を持つことができます。たとえば、キャラクターアクターには現在、11個の組み込み実装があります:プレースホルダー、スプライト、ダイススプライト、ユニバーサル、レイヤー、汎用、ビデオ、Live2D、Spine、ナレーター、一時的。

アクターの実装は、Naninovel -> Configuration エディターメニューからアクセスできる構成マネージャーで選択できます。すべてのアクターに使用されるデフォルトの実装を変更したり、アクターごとに特定の実装を設定したりできます。デフォルトの実装を変更するには Default Metadata プロパティを使用し、個別の実装を設定するにはアクターの構成の Implementation ドロップダウンを使用します。

cover
cover

Implementation ドロップダウンには、特定のアクターインターフェースを実装するすべての型が含まれています。独自のカスタム実装を追加することもでき、それらもリストに表示されます。独自のアクター実装を作成する際の参考として、Naninovel/Runtime/Actor スクリプトを参照してください。アクターをシーンにスポーンすることを想定している場合は、基本インターフェースの要件の大半を満たす組み込みの抽象実装 MonoBehaviourActor の使用を検討してください。

カスタムアクター実装を作成するときは、互換性のあるパブリックコンストラクターがあることを確認してください。

cs
public ActorImplementationType (string id, ActorMetadata metadata) { }

— ここで id はアクターのIDであり、metadata はアクターのメタデータ(リソースにアクターレコードが存在する場合)またはデフォルトのメタデータのいずれかです。特定のアクターインターフェースを実装する場合、対応する特定のメタデータ(たとえば、ICharacterActor 実装の場合は CharacterMetadata)を要求することができます。

例

すべての組み込みアクター実装は同じアクターAPIに基づいて作成されているため、独自の実装を追加する際の参考にすることができます。ソースはNaninovelパッケージの Runtime/Actor ディレクトリにあります。

アクターリソース ​

実装の型に ActorResources 属性を適用して、カスタムアクターのリソースとして使用できるアセットと、エディターメニューで複数のリソースを割り当てることができるかどうかを指定します。複数のリソースが許可されていない場合(デフォルト)、アクターIDのみを指定して、利用可能な唯一のリソースをロードできます。例:

cs
var resource = await resourceLoader.Load(actorId);

複数のリソースが許可されている場合は、フルパスを指定します。たとえば、CubeBackground という名前のリソースを割り当てたとします。

cover

— リソースをロードするには、次を使用します。

cs
var resource = await resourceLoader.Load($"{actorId}/CubeBackground");

カスタムメタデータ ​

組み込み実装とカスタム実装のどちらでも、アクターメタデータに独自のデータを追加できます。

カスタムデータを注入するには、新しいC#クラスを作成し、CustomMetadata<TActor> を継承します。ここで TActor は、データを関連付けるアクター実装の型です。以下は、CustomCharacterImplementation のキャラクターにカスタムデータを追加する例です。

cs
using Naninovel;
using UnityEngine;

public class MyCharacterData : CustomMetadata<CustomCharacterImplementation>
{
    public int MyCustomInt;
    [Range(0f, 100f), Tooltip("Tooltip for my custom range.")]
    public float MyCustomRange = 50f;
    [ColorUsage(false, true)]
    public Color MyCustomColor = Color.white;
}

対応する実装のアクターを選択すると、作成したカスタムデータクラスのシリアル化可能なフィールドが、Naninovelエディターメニューに自動的に表示されます。

cover

実行時にカスタムデータにアクセスするには、ActorMetadata インスタンスの GetCustomData<TData>() メソッドを使用します。ここで TData はカスタムデータクラスの型です。例:

cs
var charsConfig = Engine.GetConfiguration<CharactersConfiguration>();
var myCharMeta = charsConfig.GetMetadataOrDefault("CharId");
var myCharData = myCharMeta.GetCustomData<MyCharacterData>();
Debug.Log(myCharData.MyCustomInt);

カスタムメタデータエディター ​

プロパティドロワー ↗ を介してカスタムメタデータエディターをカスタマイズすることが可能です。以下は、編集対象のフィールドの上に追加のラベルを挿入するプロパティドロワーを追加する例です。

cs
// シリアル化されたフィールドに適用する属性を作成します。
// `PropertyAttribute` から継承することを忘れないでください。
public class ExtraLabelAttribute : PropertyAttribute
{
    public readonly string LabelText;

    public ExtraLabelAttribute (string labelText)
    {
        LabelText = labelText;
    }
}

// 対象のフィールドを描画するときに使用されるカスタムエディターを作成します。
// `UnityEditor` APIを使用するため、スクリプトは「Editor」フォルダーに置いてください。
[CustomPropertyDrawer(typeof(ExtraLabelAttribute))]
public class ExtraLabelPropertyDrawer : PropertyDrawer
{
    public override void OnGUI (Rect rect, SerializedProperty prop, GUIContent label)
    {
        var extraLabelAttribute = attribute as ExtraLabelAttribute;

        rect.height = EditorGUIUtility.singleLineHeight;
        EditorGUI.LabelField(rect, extraLabelAttribute.LabelText);

        rect.y += EditorGUIUtility.singleLineHeight +
                  EditorGUIUtility.standardVerticalSpacing;
        EditorGUI.PropertyField(rect, prop);
    }

    public override float GetPropertyHeight (SerializedProperty prop, GUIContent label)
    {
        return EditorGUIUtility.singleLineHeight * 2 +
               EditorGUIUtility.standardVerticalSpacing;
    }
}

// これで、属性がシリアル化されたフィールドに追加のラベルを適用できます。
public class MyCharacterData : CustomMetadata<CustomCharacterImplementation>
{
    [ExtraLabel("Text from my custom property drawer")]
    public string MyCustomProperty;
}

上記の実装により、カスタムキャラクターデータは次のように描画されます。

cover

ヒント

組み込み構成エディター全体をオーバーライドすることも可能です。詳細と例については、カスタム構成 ガイドを参照してください。

カスタム状態 ​

カスタムアクターの状態の型をオーバーライドまたは拡張するには、アクターのマネージャーもオーバーライド する必要があります。状態のシリアル化と管理対象アクターへの適用は、マネージャーで行われるためです。

注意

これは、組み込みの IActor 派生インターフェース(キャラクター、背景、テキストプリンター、選択肢ハンドラー)のいずれかを実装するカスタムアクターに当てはまります。カスタムアクターを IActor から直接継承した場合は、カスタム状態を使用するために組み込みマネージャーをオーバーライドする必要はありません。カスタム状態の型を指定した独自のマネージャーを作成するだけで済みます。

他のシステム(例:Naninovel外部のさまざまなゲームメカニクスのためのUI、ゲームオブジェクト、またはコンポーネント)にカスタム状態を追加する場合は、状態管理ガイド を参照してください。

以下は、最後に選択肢が追加された時刻を格納する LastChoiceTime フィールドを追加して選択肢ハンドラーの状態を拡張する例です。この時刻は、カスタム選択肢ハンドラーが表示されたときにコンソールに出力されます。

cs
// 最後の選択肢の時刻をシリアル化する拡張状態。
public class MyChoiceHandlerState : ChoiceHandlerState
{
    // このフィールドはシリアル化可能であり、ゲームのセーブ・ロードをまたいで保持されます。
    public string LastChoiceTime;

    // このメソッドはゲームをセーブするときに呼び出されます。
    // アクターから必要なデータを取得し、シリアル化可能なフィールドに保存します。
    public override void OverwriteFromActor (IChoiceHandlerActor actor)
    {
        base.OverwriteFromActor(actor);
        if (actor is MyCustomChoiceHandler myCustomChoiceHandler)
            LastChoiceTime = myCustomChoiceHandler.LastChoiceTime;
    }

    // このメソッドはゲームをロードするときに呼び出されます。
    // シリアル化されたデータを取り出し、アクターに適用します。
    public override async Awaitable ApplyToActor (IChoiceHandlerActor actor)
    {
        await base.ApplyToActor(actor);
        if (actor is MyCustomChoiceHandler myCustomChoiceHandler)
            myCustomChoiceHandler.LastChoiceTime = LastChoiceTime;
    }
}

// 最後の選択肢の時刻を使用するカスタム選択肢ハンドラー実装。
public class MyCustomChoiceHandler : UIChoiceHandler
{
    public string LastChoiceTime { get; set; }

    public MyCustomChoiceHandler (string id, ChoiceHandlerMetadata meta,
        EmbeddedAppearanceLoader<GameObject> loader) : base(id, meta, loader) { }

    public override void AddChoice (Choice choice)
    {
        base.AddChoice(choice);
        LastChoiceTime = DateTime.Now.ToShortTimeString();
    }

    public override Awaitable ChangeVisibility (bool visible, Tween tween,
        AsyncToken token = default)
    {
        Debug.Log($"Last choice time: {LastChoiceTime}");
        return base.ChangeVisibility(visible, tween, token);
    }
}

// 拡張状態を使用するように組み込み選択肢ハンドラーマネージャーをオーバーライドします。
// 重要なのは、ジェネリック型に `MyChoiceHandlerState` を指定することです。
// 他の変更は、インターフェースの要件を満たすためだけのものです。
[InitializeAtRuntime(@override: typeof(ChoiceHandlerManager))]
public class MyChoiceHandlerManager : ActorManager<IChoiceHandlerActor,
    MyChoiceHandlerState, ChoiceHandlerMetadata,
    ChoiceHandlersConfiguration>, IChoiceHandlerManager
{
    public MyChoiceHandlerManager (ChoiceHandlersConfiguration config)
        : base(config) { }

    public Awaitable<IChoiceHandlerActor> AddActor (string actorId,
        ChoiceHandlerState state)
    {
        return base.AddActor(actorId, state as MyChoiceHandlerState);
    }

    ChoiceHandlerState IActorManager<IChoiceHandlerActor,
        ChoiceHandlerState, ChoiceHandlerMetadata,
        ChoiceHandlersConfiguration>.GetActorState (string actorId)
    {
        return base.GetActorState(actorId);
    }
}

これで、カスタム選択肢ハンドラーは最後に選択肢が追加された時刻を保持し、その選択肢がセーブスロットからロードした以前のゲームセッションで追加されたものであっても、コンソールにログ出力するようになります。この方法で、組み込みのアクター状態に加えて任意の量のカスタムデータを保存できます。サポートされているシリアル化可能なデータ型については、Unityのシリアル化ガイド ↗ を参照してください。