0

これにはゲーム開発プロジェクトが含まれていますが、実際には、データをコーディングして他のデータにマッピングすることを目的としています。これが、ここに投稿することにした理由です。

外部インベントリ アイテム データ ストレージに使用している形式:

[ID:IT_FO_TROUT]
[Name:Trout]
[Description:A raw trout.]
[Value:10]

[3DModel:null]
[InventoryIcon:trout]

[Tag:Consumable]
[Tag:Food]
[Tag:Stackable]

[OnConsume:RestoreHealth(15)]
[OnConsume:RestoreFatigue(15)]

質問は、最後の 2 つのOnConsumeプロパティに集中しています。基本的に、2 つの特性は、アイテムが消費されると、消費者の健康が 15 ポイント上昇し、疲労も同様に上昇することを意味します。これは、バックグラウンドで、2 つの異なるメソッドを呼び出します。

void RestoreHealth(Character Subject, int Amount);
void RestoreFatigue(Character Subject, int Amount);

メソッドをファイル内の対応する文字列にどのようにマッピングしますか? これは私が考えたことです:

  1. アイテムが消費されるたびに、文字列 (イベント) のリストがItem イベント マネージャーに渡されます。マネージャーは各文字列を解析し、適切なメソッドを呼び出します。設定は非常に簡単で、これはあまり頻繁に行われる操作ではないため、パフォーマンスへの影響はそれほど大きくない可能性があります (文字列のサイズも小さく (最大 10 ~ 15 文字)、O(n) 時間で解析されます)。 )。

  2. インベントリ アイテム(クラス) は、初期化時に文字列イベントを 1 回だけ解析します。各文字列イベントは、辞書。これは、私が考えることができるパフォーマンスの点で最も効率的な方法ですが、他のことを行うのが非常に困難になります。ディクショナリ内のすべての値は、同じ種類のデリゲートでなければなりません。これは私が維持できないことを意味します

    a) RestoreHealth(int)

    b) SummonMonster(ポジション、カウント)

    同じ辞書にあり、呼び出し可能なメソッドの種類ごとに新しいデータ構造を設定する必要があります。これは膨大な量の作業です。

両方の方法を改善するために、頭に浮かんだいくつかの方法:

  1. アイテムのOnConsumeイベントが 2 回解析されないように、アイテム イベント マネージャー内である種の一時キャッシュを使用できますか? ただし、2) で発生した問題と同じ問題が発生する可能性があります。map<InventoryItem,List<delegate>>

  2. .NET ライブラリ内のハッシュテーブルデータ構造により、任意の種類のオブジェクトを任意の時点でキーおよび/または値にすることができます (ディクショナリとは異なります)。これを使用して、文字列 A をデリゲート Xにマップ し、同じ構造内で文字列 B をデリゲート Yにマップすることもできます。これを行うべきではない理由はありますか?この方法によってもたらされるトラブルを予測できますか?

私も反省の方法で何かを考えていましたが、それに関しては私は正確に経験していません。そして、毎回文字列を解析する方が速いと確信しています。

編集

アレクセイ・ラガの答えを念頭に置いた私の最終的な解決策。イベントの種類ごとにインターフェイスを使用します。

public interface IConsumeEvent    
{    
    void ApplyConsumeEffects(BaseCharacter Consumer);   
}

サンプル実装者 (特定のイベント):

public class RestoreHealthEvent : IConsumeEvent
{    
    private int Amount = Amount;

    public RestoreHealthEvent(int Amount)
    {
        this.Amount = Amount;
    }

    public void ApplyConsumeEffects(BaseCharacter Consumer)   
    {
        Consumer.Stats.AlterStat(CharacterStats.CharStat.Health, Amount);    
    }    
}

パーサーの内部 (イベントの特殊性を気にする唯一の場所 - データ ファイル自体を解析しているため):

RestoreHealthEvent ResHealthEv = new RestoreHealthEvent (Value);
NewItem.ConsumeEvents.Add (ResHealthEv );

キャラクターがアイテムを消費すると:

foreach (IConsumeEvent ConsumeEvent in Item.ConsumeEvents)
{
    //We're inside a parent method that's inside a parent BaseCharacter class; we're consuming an item right now.
    ConsumeEvent.ApplyConsumeEffects(this);
}
4

2 に答える 2

1

代わりに、それらを「コマンド」クラスに一度だけ「マップ」してみませんか?

例えば、

[OnConsume:RestoreHealth(15)]
[OnConsume:RestoreFatigue(15)]

次のように定義できるコマンド クラスRestoreHealthにマッピングできます。RestoreFatigue

public sealed class RestoreHealth : ICommand {
    public int Value { get; set; }
    //whatever else you need
}

public sealed class SummonMonster : ICommand {
    public int Count {get; set; }
    public Position Position { get; set; }
}

この時点では、コマンドをパラメーターの単なるラッパーと見なしてください ;) したがって、複数のパラメーターを渡す代わりに、常にそれらをラップして 1 つだけを渡します。また、少しのセマンティクスも提供します。

各アイテムが消費されたときに「送信」する必要があるコマンドにインベントリ アイテムをマッピングできるようになりました。

次のような単純な「バス」インターフェースを実装できます。

public interface IBus {
    void Send(ICommand command);
    void Subscribe(object subscriber);
}

のインスタンスを取得し、必要に応じてそのメソッドIBusを呼び出すだけです。Send

これを行うことで、「定義」(をする必要があるか)とロジック(アクションを実行する方法)の懸念を分離できます。

受信部分と反応部分では、コマンドを「処理」できるすべてのメソッドを見つけてSubscribe、インスタンスに問い合わせるメソッドを実装しsubscriberます (これも 1 回だけ)。IHandle<T> where T: ICommandハンドラーでいくつかのインターフェースを考え出すか、慣例 (Handleの引数を 1 つだけ受け入れてICommandを返す任意のメソッドvoid) でそれらを見つけるか、またはあなたに合ったものを見つけることができます。

これは基本的に、コマンドごとであることを除いて、あなたが話していた「デリゲート/アクション」リストの同じ部分です:

map<CommandType, List<action>>

すべてのアクションが1 つのパラメーター ( ICommand) のみを受け入れるようになったため、それらすべてを同じリストに簡単に保持できます。

何らかのコマンドを受信すると、IBus実装は指定されたコマンド タイプのアクションのリストを取得し、指定されたコマンドをパラメーターとして渡してこれらのアクションを呼び出すだけです。

それが役に立てば幸い。

高度な: さらに一歩進めることができます:ConsumeItemコマンドがあります:

public sealed void ConsumeItem: ICommand {
    public InventoryItem Item { get; set; }
}

InventoryItem と Commands の間のマップを保持するクラスが既にあるため、このクラスはプロセス マネージャーになることができます。

  1. コマンドにサブスクライブしますConsumeItem(バス経由)
  2. そのHandleメソッドでは、指定されたインベントリ アイテムのコマンドのリストを取得します。
  3. これらのコマンドをバスに送信します。

これで、これら 3 つの懸念事項が明確に分離されました。

  1. インベントリ アイテムを消費している間は、「知っている」だけでコマンドIBusを送信するだけで、ConsumeItem次に何が起こるかは気にしません。
  2. 「ConsumeInventoryManager」(名前は何でも) は、IBus', subscribes forConsumeItem` コマンドについても認識しており、各アイテムが消費されるときに何をする必要があるかを「認識」しています (コマンドのリスト)。これらのコマンドを送信するだけで、誰がどのように処理するかは気にしません。
  3. ビジネス ロジック (キャラクター、モンスターなど) は、意味のあるコマンド ( 、 など) を処理するだけで、それらRestoreHealthDieどこから (そしてなぜ) 出てきたかは気にしません。

幸運を :)

于 2013-01-27T03:42:45.040 に答える
0

私のアドバイスは、リフレクションを使用することです。つまり、指定された名前に基づいて目的のメソッドを呼び出すメソッドを定義します。これが実際の例です:

class Program
{
    static void Main(string[] args)
    {
        SomeClass someInstance = new SomeClass();
        string name = Console.ReadLine();
        someInstance.Call("SayHello", name);
    }
}

class SomeClass
{
    public void SayHello(string name)
    {
        Console.WriteLine(String.Format("Hello, {0}!", name));
    }

    public void Call(string methodName, params object[] args)
    {
        this.GetType().GetMethod(methodName).Invoke(this, args);
    }
}

次の条件が満たされていれば、この方法で行うことができます。

  1. 呼び出しが可能であること、つまり、指定された名前のメソッドが存在し、パラメーターの数と型が正しいことを完全に確信している

  2. 指定された名前のメソッドはオーバーロードされていません。System.Reflection.AmbiguousMatchException

  3. Call派生時にメソッドを使用するすべてのクラスのスーパークラスが存在します。そのクラスでこのメソッドを定義する必要があります

条件 1. と 2. が満たされていることを保証するには*Type.GetMethodメソッドの名前だけでなく、パラメーターの数と型も考慮した、より具体的なバージョンを使用して、そのようなメソッドがあることを確認できます。それを呼び出す前に; メソッドは次のCallようになります (*またはとしてマークされたパラメーターを持つメソッドでは機能しません )。out ref

public void Call(string methodName, params object[] args)
{
    //get the method with the specified name and parameter list
    Type[] argTypes = args.Select(arg => arg.GetType()).ToArray();
    MethodInfo method = this.GetType().GetMethod(methodName, argTypes);

    //check if the method exists and invoke it
    if (method != null)
        method.Invoke(this, args);
}

REMARK :MethodInfo.Invoke メソッドは実際には を返すため object、戻り値の型を指定し、適切なキャストまたは結果を目的の型に変換する他のメソッドと一緒にキーワードを使用することで、何らかの値を返すメソッドを定義できます。 かどうかを確認します。Call return

条件 3. が満たされない場合は、拡張メソッドを記述します。ジェネリック値を返す拡張メソッドの例を次に示します。ほとんどの場合、これで十分であり ( orでは機能しません)、.NET Framework で可能なほぼすべてのオブジェクトで機能するはずです (反例を指摘してくれてありがとう): ref out

public static class Extensions
{
    //invoke a method with the specified name and parameter list
    // and return a result of type T
    public static T Call<T>(this object subject, string methodName, params object[] args)
    {
        //get the method with the specified name and parameter list
        Type[] argTypes = args.Select(arg => arg.GetType()).ToArray();
        MethodInfo method = subject.GetType().GetMethod(methodName, argTypes);

        //check if the method exists
        if (method == null)
            return default(T); //or throw an exception

        //invoke the method and get the result
        object result = method.Invoke(subject, args);

        //check if something was returned
        if (result == null)
            return default(T); //or throw an exception
        //check if the result is of the expected type (or derives from it)
        if (result.GetType().Equals(typeof(T)) || result.GetType().IsSubclassOf(typeof(T)))
            return (T)result;
        else
            return default(T); //or throw an exception
    }

    //invoke a void method more conveniently
    public static void Call(this object subject, string methodName, params object[] args)
    {
        //invoke Call<object> method and ignore the result
        subject.Call<object>(methodName, args);
    }
}

someObject.Call<string>("ToString")たとえば、代わりにを使用できるはずですsomeObject.ToString()。最後に、この時点で強くお勧めします。

  1. object可能であればより具体的な型を使用する

  2. より洗練された一意の名前を使用してくださいCall- 一部のクラスに同じシグネチャが定義されたメソッドがある場合、不明瞭になる可能性があります

  3. 共分散分散を調べて、より有用な知識を得る

于 2013-01-30T15:07:46.293 に答える