2

ゲーム エンジン用のアニメーション コンポーネントを作成しようとしています。アニメーション コンポーネントは、任意のゲーム オブジェクトの任意のメンバーの値を変更 (アニメーション化) する必要があります。問題は、メンバーは通常値型ですが、アニメーション コンポーネントは、変更できるようにそれらへの何らかの参照を必要とすることです。

最初はリフレクションを使おうと思ったのですが、リフレクションが遅すぎます。役立つ可能性のあるC#の他の手法(ポインター、Reflection.Emit、式ツリー、動的メソッド/オブジェクト、デリゲート、ラムダ式、クロージャー...)について読みましたが、これらのことを十分に知りません。問題を解決することができます。

アニメーション コンポーネントには、ランダム オブジェクトのメンバーへの参照を取得して保存し、その値を時間の経過とともにアニメーション化できるメソッドがあります。このように:StartSomeAnimation(ref memberToAnimate) 他のパラメータ(アニメーションの長さなど)がありますが、問題はメンバーを渡すことです。memberToAnimateへの参照は、アニメーション コンポーネントによってフレームごとに更新できるように (値型であっても) 格納する必要があります。

私が自分で解決策に最も近いのは、ラムダ式と Action<> Func<> デリゲートを使用することです (以下の例を参照)。メンバーを直接変更するよりも約 4 倍遅くなります + ガベージ割り当てがいくらか増えます。しかし、上記の例のような単純なメソッド シグネチャを作成することはまだできません。

class Program
{
    static void Main(string[] args)
    {
        GameItem g = new GameItem();
        Console.WriteLine("Initialized to:" + g.AnimatedField);
        g.StartSomeAnimation();
        // NOTE: in real application IntializeAnim method would create new animation object 
        // and add it to animation component that would call update method until 
        // animation is complete
        Console.WriteLine("Animation started:" + g.AnimatedField);
        Animation.Update();
        Console.WriteLine("Animation update 1:" + g.AnimatedField);
        Animation.Update();
        Console.WriteLine("Animation update 2:" + g.AnimatedField);
        Animation.Update();
        Console.WriteLine("Animation update 3:" + g.AnimatedField);
        Console.ReadLine();
    }
}
class GameItem
{
    public int AnimatedField;// Could be any member of any GameItem class
    public void StartSomeAnimation()
    {
        // Question: can creation of getter and setter be moved inside the InitializeAnim method?
        Animation.IntializeAnim(
            () => AnimatedField, // return value of our member
            (x) => this.AnimatedField = x); // set value of our member
    }
}

class Animation // this is static dumb class just for simplicity's sake
{
    static Action<int> setter;
    static Func<int> getter;

    // works fine, but we have to write getters and setters each time we start an animation
    public static void IntializeAnim(Func<int> getter, Action<int> setter)
    {
        Animation.getter = getter;
        Animation.setter = setter;
    }

    // Ideally we would need to pass only a member like this,
    // but we get an ERROR: cannot use ref or out parameter inside an anonymous method lambda expression or query expression
    public static void IntializeAnim(ref int memberToAnimate)
    {
        Animation.getter = () => memberToAnimate;
        Animation.setter = (x) => memberToAnimate = x;
    }

    public static void Update()
    {
        // just some quick test code that queries and changes the value of a member that we animate
        int currentValue = getter();
        if (currentValue == 0)
        {
            currentValue = 5;
            setter(currentValue);
        }
        else
            setter(currentValue + currentValue);
    }
}

編集:うまくいけば、質問をもう少し明確にするために、より完全な例が追加されました。ゲーム アーキテクチャではなく、ラムダ式を使用してクロージャを作成する方法に注目してください。現在、メンバーごとにアニメーション化したいため、新しいアニメーションを開始するたびに 2 つのラムダ式を記述する必要があります ( IntializeAnimメソッド)。アニメーションの開始を簡素化できますか? IntializeAnimメソッドが現在どのように呼び出されているかを見てください。

class Program
{
    static bool GameRunning = true;
    static void Main(string[] args)
    {
        // create game items
        Lamp lamp = new Lamp();
        GameWolrd.AddGameItem(lamp);
        Enemy enemy1 = new Enemy();
        Enemy enemy2 = new Enemy();
        GameWolrd.AddGameItem(enemy1);
        GameWolrd.AddGameItem(enemy2);

        // simple game loop
        while (GameRunning)
        {
            GameWolrd.Update();
            AnimationComponent.Update();
        }
    }
}

static class GameWolrd
{
    static List<IGameItem> gameItems;
    public static void Update()
    {
        for (int i = 0; i < gameItems.Count; i++)
        {
            IGameItem gameItem = gameItems[i];
            gameItem.Update();
        }
    }
    public static void AddGameItem(IGameItem item)
    {
        gameItems.Add(item);
    }
}

static class AnimationComponent
{
    static List<IAnimation> animations;
    public static void Update()
    {
        for (int i = 0; i < animations.Count; i++)
        {
            IAnimation animation = animations[i];
            if (animation.Parent == null ||
                animation.Parent.IsAlive == false ||
                animation.IsFinished)
            {// remove animations we don't need
                animations.RemoveAt(i);
                i--;
            }
            else // update animation
                animation.Update();
        }
    }
    public static void AddAnimation(IAnimation anim)
    {
        animations.Add(anim);
    }
}

interface IAnimation
{
    void Update();
    bool IsFinished;
    IGameItem Parent;
}

/// <summary>
/// Game items worry only about state changes. 
/// Nice state transitions/animations logics reside inside IAnimation objects
/// </summary>
interface IGameItem
{
    void Update();
    bool IsAlive;
}

#region GameItems
class Lamp : IGameItem
{
    public float Intensity;
    public float ConeRadius;
    public bool IsAlive;
    public Lamp()
    {
        // Question: can be creation of getter and setter moved 
        //           inside the InitializeAnim method?
        SineOscillation.IntializeAnim(
            () => Intensity, // getter
            (x) => this.Intensity = x,// setter
            parent: this,
            max: 1,
            min: 0.3f,
            speed: 2);
        // use same animation algorithm for different member
        SineOscillation.IntializeAnim(
            () => ConeRadius, // getter
            (x) => this.ConeRadius = x,// setter
            parent: this,
            max: 50,
            min: 20f,
            speed: 15); 
    }
    public void Update()
    {}
}
class Enemy : IGameItem
{
    public float EyesGlow;
    public float Health;
    public float Size;
    public bool IsAlive;
    public Enemy()
    {
        Health = 100f;
        Size = 20;
        // Question: can creation of getter and setter be moved 
        //           inside the InitializeAnim method?
        SineOscillation.IntializeAnim(
            () => EyesGlow, // getter
            (x) => this.EyesGlow = x,// setter
            parent: this,
            max: 1,
            min: 0.5f,
            speed: 0.5f);
    }
    public void Update()
    {
        if (GotHitbyPlayer)
        {
            DecreaseValueAnimation.IntializeAnim(
            () => Health, // getter
            (x) => this.Health = x,// setter
            parent: this,
            amount: 10,
            speed: 1f);
            DecreaseValueAnimation.IntializeAnim(
            () => Size, // getter
            (x) => this.Size = x,// setter
            parent: this,
            amount: 1.5f,
            speed: 0.3f);
        }
    }
}
#endregion

#region Animations
public class SineOscillation : IAnimation
{
    Action<float> setter;
    Func<float> getter;
    float max;
    float min;
    float speed;
    bool IsFinished;
    IGameItem Parent;

    // works fine, but we have to write getters and setters each time we start an animation
    public static void IntializeAnim(Func<float> getter, Action<float> setter, IGameItem parent, float max, float min, float speed)
    {
        SineOscillation anim = new SineOscillation();
        anim.getter = getter;
        anim.setter = setter;
        anim.Parent = parent;
        anim.max = max;
        anim.min = min;
        anim.speed = speed;
        AnimationComponent.AddAnimation(anim);
    }

    public void Update()
    {
        float calcualtedValue = // calculate value using sine formula (use getter if necessary)
        setter(calcualtedValue);
    }
}

public class DecreaseValueAnimation : IAnimation
{
    Action<float> setter;
    Func<float> getter;
    float startValue;
    float amount;
    float speed;
    bool IsFinished;
    IGameItem Parent;

    // works fine, but we have to write getters and setters each time we start an animation
    public static void IntializeAnim(Func<float> getter, Action<float> setter, IGameItem parent, float amount, float speed)
    {
        DecreaseValueAnimation anim = new DecreaseValueAnimation();
        anim.getter = getter;
        anim.setter = setter;
        anim.Parent = parent;
        anim.amount = amount;
        anim.startValue = getter();
        anim.speed = speed;
        AnimationComponent.AddAnimation(anim);
    }

    public void Update()
    {
        float calcualtedValue = getter() - speed;
        if (calcualtedValue <= startValue - amount)
        {
            calcualtedValue = startValue - amount;
            this.IsFinished = true;
        }
        setter(calcualtedValue);
    }
} 
#endregion
4

2 に答える 2

0

インターフェイスを作成できます。

interface IGameItem
{
    int AnimatedField { get; set; }
}

class GameItem : IGameItem
{
    public int AnimatedField { get; set; }
}

class Animation
{
    public IGameItem Item { get; set; }

    public void Update()
    {
        if (Item.AnimatedField == 0)
        {
            Item.AnimatedField = 5;
        }
        else
        {
            Item.AnimatedField = Item.AnimatedField + Item.AnimatedField;
        }
    }
}

スーパーアニメーション エンジンを実行すると、次のようになります。

class Program
{
    static void Main(string[] args)
    {

        GameItem g = new GameItem() { AnimatedField = 1 };

        Animation a = new Animation() { Item = g };

        a.Update();

        Console.WriteLine(g.AnimatedField);

        a.Update();

        Console.WriteLine(g.AnimatedField);

        a.Update();

        Console.WriteLine(g.AnimatedField);

        Console.ReadLine();
    }
}

ただし、パブリック セッターをすべての人に公開することは、適切な方法ではないことに注意してください。各クラスには、それによって完全に使用されるインターフェースを提供する必要があります。インターフェイス分離の原則とその他の SOLID の原則についてお読みください。

更新:

別のオプションは、アイテム自体をアニメーション化する方法を知ることです。

interface IAnimatable
{
    void Animate();
}

class IntegerItem : IAnimatable
{
    int _n;
    public IntegerItem(int n)
    {
        _n = n;
    }

    public void Animate()
    {
        Console.WriteLine(_n);
    }
}

class AnimationSequencer
{
    public void Update(IAnimatable item)
    {
        item.Animate();
    }
}
于 2013-07-18T12:05:02.373 に答える