概要
オブジェクト指向プログラミングにかかせないインターフェイスをクラスで代用するコード例です。
実装の強制ができなかったりと限界があるので、大人しくHeliScriptがインターフェイスに対応するのを待った方いいかもしれません。
メリット
- 使用クラスで実装クラス詳細を知らなくてよくなります
- 新しく実装クラスを追加しても、実装側に変更を加えなくてもよくなります
NPCダイアログのコード例
概観
DialogueService: このクラスを、ユースケースを実現する適当なクラス(ComponentClassなど)で使う想定
Diaogue: 選択肢なしのダイアログ
ChoiceDialogue: 選択肢付きのダイアログ
IDialogue: 上記ダイアログのインターフェイス(もどき)
DialogueData: セリフの生データをここに記述する
①インターフェイスと実装クラス(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();
}
}