1

我正在开发一个 C# XNA 游戏,并且有以下代码根据线性插值的结果更新 Sprite 的 alpha 级别:

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 类的字段。“Sprite”是一个包含诸如比例、位置、方向、旋转等细节的类。

'Update' 方法每秒调用 60 次,计算指定时间段内精灵的新 alpha 值。完成后,GameProcess 将从队列中移除。

这段代码工作正常,但它非常具体。我拥有的 Sprite 类包含许多属性,可以在固定的时间段内轻松处理(旋转、位置(用于缓入和缓出))。其中许多变量也是浮点数。创建多个“GameProcess”扩展类做几乎相同的事情似乎很愚蠢,只是在不同的变量上。但是,我想不出如何最好地重构它,这样我就可以有一个指向被修改的浮点值的指针,而不是特定的精灵,它是 alpha/rotation/scale/任何值。

我已经完成了关于 SO 的 C# 引用/值/参数传递问题,并且知道在 C# 中您不能将引用存储为字段。调用 Update 的“GameProcessQueue”对正在更新的“GameProcess”的内部结构一无所知。有没有更聪明的方法来做这件事,所以我可以概括/抽象类来更新另一个类的字段(就像这里,'Sprite'的'Alpha'字段)?

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));

这个答案中的语法是近似的,因为我在这台计算机上没有编译器,但这就是你的做法。好的谷歌搜索将是“lambda C#”或“delegate C#”。

于 2012-04-10T16:23:38.753 回答
2

您的主要问题是您的 AlphaProcess 类的内聚力较低。它具有三个主要角色,Tweening、Lerping 和获取/设置 Sprite Alpha 值。为了改进您的设计,您需要将它们拆分为单独的解耦类。

目标:将您的 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);
    }
}

获取/设置 alpha 的类是:

class SpriteAlphaSource : IFloatPropertySource
{
    Sprite sprite;

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

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

我知道你有点想避免上这门课。但是围绕它的所有方法,比如通过反射,都非常昂贵。在具有更多可渲染对象而不仅仅是 sprite 的项目中,您可能希望将其与 sprite 分离,并使其在像 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 回答