近期发布
专辑列表

Windows Phone 游戏合集
JQueryElement
IEBrowser
WPXNA

使用 Movie 类在 XNA 中分割图片并逐帧播放动画,WPXNA(四)

平方已经开发了一些 Windows Phone 上的一些游戏,算不上什么技术大牛。在这里分享一下经验,仅为了和各位朋友交流经验。平方会逐步将自己编写的类上传到托管项目中,没有什么好名字,就叫 WPXNA 吧,最后请高手绕道而行吧,以免浪费时间。(为了突出重点和减少篇幅,有些示例代码可能不够严谨。)

电影序列

平方将一个完整的动作称为一个电影序列,比如:玩家奔跑的动作。因此,平方定义了 MovieSequence 类,他包含了一个动作所需要的信息。下面是 MovieSequence 中包含的字段:

private readonly List<Point> frames = new List<Point> ( );
internal bool IsLooped;
private readonly bool isLoop;
private int currentFrameIndex;
internal Point CurrentFrame;
internal readonly string Name;
internal readonly int FrameCount;
internal readonly int Rate;

字段 frames 表示帧的位置,这些帧将组成整个动作,而所有这些帧是在同一个图片当中的,我们来看一下示例中的图片。

我们使用 (x, y) 这样的坐标来表示帧的位置,那么 (1, 1) 就表示黄色的小鸟,而 (4, 1) 表示黑色的小鸟。将这些坐标组合在一起就成为一个完成的动作。

字段 isLoop 表示是否可以循环播放。字段 IsLooped 表示序列是否已经循环一次。

字段 currentFrameIndex 表示当前帧的索引,从 1 开始。CurrentFrame 表示当前帧的位置。FrameCount 表示序列中包含了多少帧。而 Rate 字段表示了播放速度,Rate 的值越小,则播放速度越快。字段 Name 表示序列的名称。

Next 方法会使序列进入下一帧,Reset 方法会重新设置序列的信息。

internal static void Reset ( MovieSequence sequence )
{
    sequence.IsLooped = false;
    sequence.currentFrameIndex = 1;

    sequence.CurrentFrame = sequence.frames[sequence.currentFrameIndex - 1];
}

internal static bool Next ( MovieSequence sequence )
{
    bool isEnded = sequence.currentFrameIndex == sequence.frames.Count;

    if ( isEnded )
    {
        sequence.IsLooped = true;

        if ( !sequence.isLoop )
            return isEnded;

        sequence.currentFrameIndex = 1;
    }
    else
        sequence.currentFrameIndex++;

    sequence.CurrentFrame = sequence.frames[sequence.currentFrameIndex - 1];
    return isEnded;
}

在 Next 方法中,我们根据 isLoop 字段来决定是否在播放所有的帧之后,循环播放。如果不能循环,则将停留在最后一帧。

电影

我们将多个 MovieSequence 组成一个 Movie,下面是 Movie 类的一些字段和事件。

internal event EventHandler<MovieEventArgs> Ended;

internal readonly int Width;
internal readonly int Height;

private readonly float renderWidth;
private readonly float renderHeight;

private readonly int rate;
private int currentRate;
private int currentFrameCount;
private MovieSequence currentSequence;
internal string CurrentSequenceName;
private float rotation = 0;
internal int Rotation
{
    set
    {
        this.rotation = Calculator.Radian ( value );
    }
}
private Vector2 rotationLocation;
private Rectangle frameRectangle;

字段 Width,Height 表示电影的大小,而 renderWidth,renderHeight 表示绘制电影时的大小,也就是确定如何分割图像,默认情况下他们相同。

虽然每一个 MovieSequence 都包含了自己的 Rate 字段,但 Movie 还是定义了一个默认的 rate,当 MovieSequence 没有给出 Rate 时,将使用默认的 rate 字段。而 currentRate,currentSequence, CurrentSequenceName 字段表示了当前 MovieSequence 的信息。

属性 Rotation 表示了 Movie 的旋转信息,可以设置为某一个角度,但最终会转化为弧度。

字段 rotationLocation 表示在什么位置旋转电影,如果设置了 Movie 的角度。字段 frameRectangle 表示一个矩形,也就是绘制整个图片的某一个部分。

事件 Ended 将在某一个电影序列播放完毕时触发。

方法 Play 用来播放电影,方法 NextFrame 用来让电影进入下一帧,使用 Draw 放来绘制电影。

internal static void Play ( Movie movie, string sequenceName, bool isRecord, bool isReplay )
{

    if ( !isReplay && movie.CurrentSequenceName == sequenceName )
        return;

    movie.CurrentSequenceName = sequenceName;
    if ( isRecord )
        movie.sequenceNames.Add ( sequenceName );

    if ( string.IsNullOrEmpty ( sequenceName ) || !movie.sequences.ContainsKey ( sequenceName ) )
    {
        movie.isVisible = false;
        return;
    }

    movie.isVisible = true;
    movie.currentSequence = movie.sequences[sequenceName];
    movie.currentRate = movie.currentSequence.Rate == 0 ? movie.rate : movie.currentSequence.Rate;
    movie.currentFrameCount = movie.currentRate;
    MovieSequence.Reset ( movie.currentSequence );

    movie.frameRectangle = new Rectangle ( ( int ) ( ( movie.currentSequence.CurrentFrame.X - 1 ) * movie.renderWidth ), ( int ) ( ( movie.currentSequence.CurrentFrame.Y - 1 ) * movie.renderHeight ), ( int ) movie.renderWidth, ( int ) movie.renderHeight );

}

internal static void NextFrame ( Movie movie, bool isForce )
{

    if ( !movie.isVisible || ( !isForce && --movie.currentFrameCount > 0 ) )
        return;

    if ( movie.currentSequence.FrameCount <= 1 )
    {

        if ( null != movie.Ended )
            movie.Ended ( movie, new MovieEventArgs ( movie ) );

        return;
    }

    movie.currentFrameCount = movie.currentRate;

    if ( MovieSequence.Next ( movie.currentSequence ) )
        if ( null != movie.Ended )
            movie.Ended ( movie, new MovieEventArgs ( movie ) );

    movie.frameRectangle = new Rectangle ( ( int ) ( ( movie.currentSequence.CurrentFrame.X - 1 ) * movie.renderWidth ), ( int ) ( ( movie.currentSequence.CurrentFrame.Y - 1 ) * movie.renderHeight ), ( int ) ( movie.renderWidth ), ( int ) ( movie.renderHeight ) );
}

internal static void Draw ( Movie movie, GameTime time, SpriteBatch batch )
{

    if ( !movie.isVisible )
        return;

    if ( movie.rotation == 0 )
        batch.Draw ( movie.Texture, movie.location * World.Scale, movie.frameRectangle, Color.White, 0, Vector2.Zero, World.TextureScale, SpriteEffects.None, movie.order );
    else
        batch.Draw ( movie.Texture, ( movie.location + movie.rotationLocation ) * World.Scale, movie.frameRectangle, Color.White, movie.rotation, movie.rotationLocation * World.Scale, World.TextureScale, SpriteEffects.None, movie.order );

}

 

小鸟

最后,我们来使用 Movie 类。

private readonly ResourceManager resourceManager;
private readonly Movie bird2;
private int bird2Angle = 0;

这里的 bird2Angle 表示小鸟的角度。

public World ( Color backgroundColor )
    : base ( )
{
    // ...

    this.resourceManager = new ResourceManager ( new Resource[] {
        new Resource ( "bird2.image", ResourceType.Image, @"image\bird2" )
    } );
    this.resourceManager.World = this;

    this.bird2 = new Movie ( "bird2.m", "bird2.image", new Vector2 ( 200, 200 ), 80, 80, 3, 0, "live",
        new MovieSequence ( "live", true, new Point ( 1, 1 ), new Point ( 2, 1 ) ),
        new MovieSequence ( "dead", 30, false, new Point ( 3, 1 ), new Point ( 4, 1 ) )
        );
    this.bird2.Ended += this.bird2MovieEnded;
}

private void bird2MovieEnded ( object sender, MovieEventArgs e )
{
    Debug.WriteLine ( "bird2MovieEnded: e.SequenceName=" + e.SequenceName );
}

protected override void OnNavigatedFrom ( NavigationEventArgs e )
{
    this.bird2.Ended -= this.bird2MovieEnded;

    base.OnNavigatedFrom ( e );
}

在构造函数中,我们通过 ResourceManager 定义了所需要的资源,然后创建了小鸟的电影。小鸟拥有两个动画,一个是 live,也就是活着时候的动画,另一个是 dead,是死亡时候的动画。而默认播放 live。我们定义了小鸟的 Ended 事件,在方法 bird2MovieEnded 中,我们将输出播放完毕的序列的名称。

protected override void OnNavigatedTo ( NavigationEventArgs e )
{
    // ...
    
    this.resourceManager.LoadContent ( );
    this.bird2.InitResource ( this.resourceManager );
    
    base.OnNavigatedTo ( e );
}

在 OnNavigatedTo 方法中,我们获取小鸟所需要的图片资源。

private void OnUpdate ( object sender, GameTimerEventArgs e )
{
    Movie.NextFrame ( this.bird2 );

    this.bird2.Rotation = this.bird2Angle++;

    if ( e.TotalTime.TotalSeconds > 5 && this.bird2.CurrentSequenceName == "live" )
        Movie.Play ( this.bird2, "dead" );
        
}

private void OnDraw ( object sender, GameTimerEventArgs e )
{
    this.GraphicsDevice.Clear ( this.BackgroundColor );

    this.spiritBatch.Begin ( );
    Movie.Draw ( this.bird2, new GameTime ( e.TotalTime, e.ElapsedTime ), this.spiritBatch );
    this.spiritBatch.End ( );
}

在 OnUpdate 方法中,我们需要使用 NextFrame 方法来不断的更新帧的信息,同时修改了小鸟的角度。而在 5 秒之后,我们播放小鸟的死亡动画。

在 OnDraw 方法中,我们绘制了小鸟。

本期视频 http://v.youku.com/v_show/id_XNTYyNjI5OTA0.html
项目地址 http://wp-xna.googlecode.com/

更多内容 WPXNA
平方开发的游戏 http://zoyobar.lofter.com/
QQ 群 213685539

欢迎访问我在其他位置发布的同一文章:http://www.wpgame.info/post/decc4_64b3d0

posted @ 2013-05-27 16:30  麦丝平方  阅读(1077)  评论(0编辑  收藏  举报