0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HeliScriptで疑似interfaceを作る

Last updated at Posted at 2025-03-24

概要

オブジェクト指向プログラミングにかかせないインターフェイスをクラスで代用するコード例です。
実装の強制ができなかったりと限界があるので、大人しくHeliScriptがインターフェイスに対応するのを待った方いいかもしれません。

メリット

  • 使用クラスで実装クラス詳細を知らなくてよくなります
  • 新しく実装クラスを追加しても、実装側に変更を加えなくてもよくなります

NPCダイアログのコード例

概観

DialogueService: このクラスを、ユースケースを実現する適当なクラス(ComponentClassなど)で使う想定

Diaogue: 選択肢なしのダイアログ

ChoiceDialogue: 選択肢付きのダイアログ

IDialogue: 上記ダイアログのインターフェイス(もどき)

DialogueData: セリフの生データをここに記述する

image-20241118-013820.png

①インターフェイスと実装クラス(IDialogue/Dialogue/ChoiceDialogue)

IDialogue

  • 通常のinterfaceに記述するのはID()とLine()の部分になります
  • コンストラクターでDialogueかChoiceDialogueのどちらか一方のみ注入できるようにしています
  • AsDialogue()など、ダウンキャスト用のメソッドを用意してください
/// <summary>
/// ダイアログの疑似インターフェイス
/// </summary>
class IDialogue
{
    Dialogue _Dialogue;
    ChoiceDialogue _ChoiceDialogue;

    public void Construct(Dialogue dialogue, ChoiceDialogue choiceDialogue)
    {
        if (_Dialogue !== null && _ChoiceDialogue !== null)
        {
            hsSystemWriteLine("エラー: 依存性注入できるインスタンスは一つだけです");
            return;
        }
        _Dialogue = dialogue;
        _ChoiceDialogue = choiceDialogue;
    }

    // ---以下、インターフェイスの中身---

    public int ID()
    {
        int id = -1;
        if (_Dialogue !== null)
        {
            id = _Dialogue.ID();
        }
        else
        {
            id = _ChoiceDialogue.ID();
        }
        return id;
    }

    public string Line()
    {
        string line = "";
        if (_Dialogue !== null)
        {
            line = _Dialogue.Line();
        }
        else
        {
            line = _ChoiceDialogue.Line();
        }
        return line;
    }

    //---以下、ダウンキャスト用---

    public bool IsChoiceDialogue()
    {
        return _ChoiceDialogue !== null;
    }

    public Dialogue AsDialogue()
    {
        return _Dialogue;
    }

    public ChoiceDialogue AsChoiceDialogue()
    {
        return _ChoiceDialogue;
    }
}

Dialogue/ChoiceDialogue

interfaceに定義したID()とLine()を実装してください。

限界
実装の強制ができないため、注意してください

class Dialogue //:IDialogue
{
    int _ID;
    public int ID() { return _ID; }

    string _Line;
    public string Line() { return _Line; }

    int _NextDialogueId;

    public void Construct(int id, int nextDialogueId, string line)
    {
        _ID = id;
        _NextDialogueId = nextDialogueId;
        _Line = line;
    }

    public int GetNextDialogueId()
    {
        return _NextDialogueId;
    }
}

class ChoiceDialogue //:IDialogue
{
    int _ID;
    public int ID() { return _ID; }

    string _Line;
    public string Line() { return _Line; }

    list<int> _NextDialogueIds;

    public void Construct(int id, list<int> nextDialogueIds, string line)
    {
        _ID = id;
        _NextDialogueIds = nextDialogueIds;
        _Line = line;
    }

    public int GetNextDialogueIdByChoice(int choiceOrder)
    {
        return _NextDialogueIds[choiceOrder];
    }
}

①インターフェイスと実装クラス(IDialogue/Dialogue/ChoiceDialogue)

DialogueData

メリット
実装クラスでなく、IDialogueのリストを保持している点

/// <summary>
/// NPC**1人分**のダイアログデータ
/// 設定がしやすいようにJsValで設定できるようにしている
/// </summary>
class DialogueData
{
    list<IDialogue> _IDialogues;
    public list<IDialogue> Value() { return _IDialogues; }

    public DialogueData()
    {
        _IDialogues = new list<IDialogue>(); 

        // keyがダイアログIDのJsVal
        JsVal val = new JsVal()
        {
            "0": {
                "type": "statement",
                "line": "こんにちは",
                "nextId": 1
            },
            "1": {
                "type": "choice",
                "line": "どうしますか?",
                "nextIds": [2, 1]
            },
            "2": {
                "type": "statement",
                "line": "終了です",
                "nextId": -1
            }
        };
        SetDialogueData(val);
    }

    void SetDialogueData(JsVal val)
    {
        list<JsProp> dialogues = val.GetPropertyList();
        for (int i = 0; i < dialogues.Count; i++)
        {
            JsVal val = dialogues[i].GetValue();

            if (val.GetProperty("type").GetStr() == "statement")
            {
                AddDialogue(i, val);
            }
            else
            {
                AddChoiceDialogue(i, val);
            }

        }
    }

    void AddDialogue(int id, JsVal val)
    {
        Dialogue unit = new Dialogue();
        unit.Construct(
            id,
            val.GetProperty("nextId").GetNum(),
            val.GetProperty("line").GetStr()
        );
        IDialogue idialogue = new IDialogue();
        idialogue.Construct(unit, null);
        _IDialogues.Add(idialogue);
    }

    void AddChoiceDialogue(int id, JsVal val)
    {
    
        ChoiceDialogue unit = new ChoiceDialogue();
        list<int> nextIds = new list<int>();
        JsVal ids = val.GetProperty("nextIds");
        for (int i = 0; i < ids.GetPropertyCount(); i++)
        {
            int nextId = int(ids.At(i).GetNum());
            nextIds.Add(nextId);
        }
        string line = val.GetProperty("line").GetStr();
        unit.Construct(id, nextIds, line);

        IDialogue idialogue = new IDialogue();
        idialogue.Construct(null, unit);
        _IDialogues.Add(idialogue);
    }
}

DialogueService

メリット

  • 各実装クラスを知らなくてもよくなっています
  • if文の無駄な分岐処理がなくなっています
class DialogueService
{
    /// <summary>
    /// 全NPCのダイアログのリスト
    /// </summary>
    list<IDialogue> _IDialogues;

    public DialogueService()
    {
        DialogueData data = new DialogueData();
        _IDialogues = data.Value();
    }

    /// <summary>
    /// ダイアログIDからセリフを取得する
    /// </summary>
    public string GetLine(int dialogueId)
    {
        string line = "";
        for (int i = 0; i < _IDialogues.Count(); i++)
        {
            IDialogue idialogue = _IDialogues[i];
            if (idialogue.ID() == dialogueId)
            {
                line = idialogue.Line();
            }
        }
        return line;
    }

    /// <summary>
    /// ダイアログIDからダイアログを取得する
    /// </summary>
    public IDialogue GetDialogue(int dialogueId)
    {
        for (int i = 0; i < _IDialogues.Count(); i++)
        {
            IDialogue idialogue = _IDialogues[i];
            if (idialogue.ID() == dialogueId)
            {
                return idialogue;
            }
        }
        return null;
    }

    /// <summary>
    /// 次のダイアログIDを取得する
    /// </summary>
    public int GetNextDialogueId(int dialogueId)
    {
        IDialogue idialogue = GetDialogue(dialogueId);
        if (idialogue.IsChoiceDialogue())
        {
            return idialogue.AsChoiceDialogue().GetNextDialogueIdByChoice(0);
        }
        return idialogue.AsDialogue().GetNextDialogueId();
    }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?