1

私は C# XNA ゲームに取り組んでおり、線形補間の結果に基づいてスプライトのアルファ レベルを更新する次のコードがあります。

class AlphaProcess : GameProcess {

    Sprite sprite;
    public Sprite Sprite {
        get { return sprite; }
        set { sprite = value; }
    }

    float start;
    public float Start {
        get { return start; }
        set { start = value;}
    }

 ...<snip>...

    public override void Update(float elapsed) {
        if (FirstUpdate) {
            Start = sprite.Alpha;
            FirstUpdate = false;
        }

        Time += elapsed;
        if (Time >= Duration)
            Finished = true;

        sprite.Alpha = MathHelper.Lerp(start,end,Time/Duration);
    }
}

'Start' (float)、'FirstUpdate' (bool)、'Time' (float)、'Duration' (float) はすべてクラス AlphaProcess のフィールドです。「スプライト」は、スケール、位置、方向、回転などの詳細を含むクラスです。

「Update」メソッドは 1 秒間に 60 回呼び出され、指定された期間にわたってスプライトの新しいアルファ値を計算します。完了すると、GameProcess はキューから削除されます。

このコードは問題なく動作しますが、非常に具体的です。私が持っている Sprite クラスには、一定期間 (回転、位置 (イージング インとイージング アウト)) を lerp するのに便利な多くのプロパティが含まれています。これらの変数の多くは float でもあります。異なる変数に対して、ほとんど同じことを行う複数の「GameProcess」拡張クラスを作成するのはばかげているようです。ただし、特定のスプライトではなく、変更されている浮動小数点値へのポインターを保持できるように、これを最適にリファクタリングする方法を考えることができず、それはアルファ/回転/スケール/任意の値です。

SO に関する C# 参照 / 値 / パラメータの受け渡しに関する質問を経験しましたが、C# では参照をフィールドとして格納できないことを知っています。Update を呼び出す「GameProcessQueue」は、更新中の「GameProcess」の内部について何も知りません。クラスを一般化/抽象化して別のクラスのフィールドを更新できるように、これを行うよりスマートな方法はありますか (ここのように、「スプライト」の「アルファ」フィールドを使用)?

4

3 に答える 3

2

基本的に、コールバック関数を Update クラスに渡します。C# の用語では、デリゲート関数を使用します。次のようなプロパティをクラスに追加します

public delegate SetThis(float value) { get; set; }

それを設定するには、クライアントコードで次のようにします

myAlphaProcess.SetThis = ( x => sprite.Alpha = x; )

Update() 関数では、

SetThis(MathHelper.Lerp(start,end,Time/Duration));

このコンピューターにはコンパイラがないため、この回答の構文はおおよそのものですが、これがあなたのやり方です。適切な Google 検索は、「ラムダ C#」または「デリゲート C#」です。

于 2012-04-10T16:23:38.753 に答える
2

あなたの主な問題は、AlphaProcess クラスの凝集度が低いことです。これには、Tweening、Lerping、Sprite Alpha Value の取得/設定の 3 つの主な役割があります。設計を改善するには、これらを別々の分離されたクラスに分割する必要があります。

目標: AlphaProcess を TweenProcess に変換します。

スプライトを渡す代わりに、操作したいフロートを取得および設定できるインターフェイスを渡します。そして、Lerp を直接呼び出す代わりに、インターフェイスまたはデリゲートを Tween 関数に渡します。 (私はデリゲートの代わりにインターフェイスを使用しました。デリゲートはゲームのカクつきの原因となるガベージを作成することが多いためです。)

トゥイーン クラスは次のとおりです。

interface IFloatPropertySource
{
    float FloatProperty { get; set; }
}

interface ITweenFunction
{
    float Tween(float start, float end, float t);
}

class TweenProcess : GameProcess 
{
    float start;
    IFloatPropertySource floatSource;
    ITweenFunction tweenFunction;

    public TweenProcess(IFloatPropertySource floatSource, ITweenFunction tweenFunction)
    {
        this.floatSource = floatSource;
        this.tweenFunction = tweenFunction;
    }

    public override void Update(float elapsed) {
        if (FirstUpdate) {
            start = floatSource.FloatProperty;
            FirstUpdate = false;
        }

        Time += elapsed;
        if (Time >= Duration)
            Finished = true;

        floatSource.FloatProperty = tweenFunction.Tween(start, end, Time / Duration);
    }
}

アルファを取得/設定するクラスは次のとおりです。

class SpriteAlphaSource : IFloatPropertySource
{
    Sprite sprite;

    public SpriteAlphaSource(Sprite sprite)
    {
        this.sprite = sprite;
    }

    public float FloatProperty
    {
        get
        {
            return sprite.Alpha;
        }
        set
        {
            sprite.Alpha = value;
        }
    }
}

このクラスを避けようとしているのは知っています。しかし、リフレクションを介するなど、それを回避するすべての方法は非常に高価です。スプライトだけでなく多くのレンダリング可能要素を含むプロジェクトでは、スプライトから分離し、代わりに IRenderable などの基本インターフェイスで動作させたい場合があります。

lerping メカニズムは次のとおりです。

class Lerp : ITweenFunction
{
    public float Tween(float start, float end, float t)
    {
        return MathHelper.Lerp(start, end, t);
    }
}
于 2012-04-11T14:23:58.063 に答える
1

これは、その問題に対する一般的なアプローチになる可能性があります...おそらく、TweenManagerを実行し、Tweenablesをコレクションに追加して、アクティブなTweenablesのみを更新する必要があります...

これは正しいはずですが、私はそれをチェックしていません...おそらくジェネリックは問題を引き起こします...計算では、ジェネリックデリゲートでは避ける必要があります... :)

Tweenable<float> Alpha = new Tweenable(defaultvalue);

public class Tweenable<T>
{
     public T  Value {
           get { return Function(elapsed, source, target-source, duration);} 
           private set { source = value; elapsed = 0;}
     }

     public TweeningFunction<T> Function;

     float elapsed, duration;
     T source, target;

     public void Update(float Elapsed)
     {
          if (elapsed<duration) 
          {
              elapsed+= Elapsed;
              if (elapsed > duration) elapsed = duration;
          }
     }

     public void Start(T Source, T Target, float Duration)
     {
         ...
     }
}

 public delegate T TweeningFunction( float timeElapsed, 
                                           T start, 
                                           T change, 
                                           float duration );


public static partial class Tweening {    
        public static class Quartic {
            public static float EaseIn( float t, float b, float c, float d ) {
                return c * (t /= d) * t * t * t + b;
            }
            public static float EaseOut( float t, float b, float c, float d ) {
                return -c * ((t = t / d - 1) * t * t * t - 1) + b;
            }
            public static float EaseInOut( float t, float b, float c, float d ) {
                if ( (t /= d / 2) < 1 ) {
                    return c / 2 * t * t * t * t + b;
                }
                return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
            }
        }
    }
于 2012-04-10T17:12:44.830 に答える