近期发布
专辑列表

Windows Phone 游戏合集
JQueryElement
IEBrowser
WPXNA

使用 Spirit 类在 XNA 中创建游戏中的基本单位精灵(十三)

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

Spirit

如果你觉得不习惯,可以使用精灵的另一种写法 Sprite。Spirit 是一个重要的类,表示游戏中的单位,比如:敌人,玩家等。很多类都会从 Spirit 类派生。

下面是 Spirit 的一些成员。

Destroyed 事件会在 Destroy 方法中被调用,而这个事件主要被精灵管理器所使用,精灵管理器会在以后被讲到。DrawOrderChanged 事件会在 DrawOrder 属性中被调用,该属性用来确定精灵的绘制次序。

internal event EventHandler<SpiritEventArgs> Destroyed;
internal event EventHandler<SpiritEventArgs> DrawOrderChanged;

internal virtual void Destroy ( )
{

    if ( null != this.Destroyed )
        this.Destroyed ( this, new SpiritEventArgs ( this ) );

}

private int drawOrder = 0;

internal int DrawOrder
{
    get { return this.drawOrder; }
    set
    {
        this.drawOrder = value;

        if ( null != this.DrawOrderChanged )
            this.DrawOrderChanged ( this, new SpiritEventArgs ( this ) );

    }
}

字段 scene 用来表示控制精灵的场景,以后我们会将他的类型改为 IPlayScene。字段 world 表示控制场景的 World 类。字段 audioManager 用来控制音乐,我们将从场景中获取 AudioManager,而不是重新创建。

protected readonly IScene scene;
private readonly World world;
protected readonly AudioManager audioManager;

字段 movie 表示精灵使用的电影,多个精灵可以共享同一个电影,字段 movieName 表示电影的名称。字段 extendMovie,extendMovieName 表示扩展的电影以及扩展的电影的名称。扩展电影可以用来播放特殊的效果,比如:玩家受伤后发出红色的光。

属性 Type 表示精灵的类型,你可以根据精灵的类型来确定精灵具体是什么东西。

protected readonly Movie movie;
private readonly string movieName;
protected readonly Movie extendMovie;
private readonly string extendMovieName;
protected int type;
internal virtual int Type
{
    get { return this.type; }
    set { this.type = value; }
}

字段 Width,Height 用来表示精灵的大小,而字段 halfSize 用来表示精灵尺寸的一半,我们会预先计算这个值,以避免重复计算。

internal readonly int Width;
internal readonly int Height;
protected readonly Vector2 halfSize;

字段 Location 表示精灵的位置,属性 Angle 表示精灵的角度,如果字段 isRotable 为 false,则 Angle 是不能被修改的,字段 isMovieRotable 则表示电影的角度是否同时被修改。每当 Angle 被修改后,我们会调用 updateSpeed 方法来更新速度。

字段 speed 表示精灵的速度,字段 xSpeed,ySpeed 分别表示精灵在 x,y 轴上的速度。如果修改 Speed 属性,xSpeed 和 ySpeed 并不会被修改,但是你可以在派生类中修改 updateSpeed 方法来完成。

字段 protoSpeed,protoXSpeed,protoYSpeed 用来记录速度的原始值。字段 spiritBatch 用来绘制电影。

字段 isMovable 表示精灵是否可以移动,字段 isMoving 表示精灵是否正在移动中。

internal Vector2 Location;
internal readonly HitArea HitArea;

protected int angle;
internal virtual int Angle
{
    get { return this.angle; }
    set
    {

        if ( !this.isRotable )
            return;

        value = Calculator.Degree ( value );

        this.angle = value;

        if ( this.isMovieRotable )
        {
            this.movie.Rotation = value;

            if ( null != this.extendMovie )
                this.extendMovie.Rotation = value;

        }

        this.updateSpeed ( );
    }
}

private float protoSpeed;
private float protoXSpeed;
private float protoYSpeed;
protected float speed;
protected float xSpeed;
protected float ySpeed;
public virtual float Speed
{
    get { return this.speed; }
    set
    {
        this.speed = value;
        this.protoSpeed = this.speed;
        this.updateSpeed ( );
    }
}

private SpriteBatch spiritBatch;

protected bool isMoving = false;
protected bool isMovable = true;
protected bool isRotable = true;
private readonly bool isMovieRotable;

字段 destroyFrameCount 用来表示销毁精灵的帧数,字段 isAreaLimited 表示精灵是否可以超出 World 的 BattleArea,字段 isAreaEntered 表示精灵是否已经进入 BattleArea 中,字段 areaFrameCount 表示精灵进入 BattleArea 的限制时间。(但这里没有展示关于 isAreaLimited,isAreaEntered,areaFrameCount 的代码。)

字段 IsVisible 表示精灵是否可视。

private long destroyFrameCount;

private readonly bool isAreaLimited;
protected bool isAreaEntered;

private long areaFrameCount;

internal bool IsVisible = true;

在 Spirit 的构造函数中,我们将初始化这些字段。

protected Spirit ( IScene scene, int type, Vector2 location, string movieName, string extendMovieName, float speed, int angle, HitArea hitArea, int width, int height, double destroySecond, bool isMovieRotable, bool isAreaLimited, bool isAreaEntered, double areaSecond )
{

    if ( null == scene || string.IsNullOrEmpty ( movieName ) )
        throw new ArgumentNullException ( "scene, movieName", "scene, movieName can't be null" );

    this.destroyFrameCount = World.ToFrameCount ( destroySecond );

    this.scene = scene;
    this.world = scene.World;
    this.audioManager = scene.AudioManager;

    this.isMovieRotable = isMovieRotable;
    this.isAreaLimited = isAreaLimited;
    this.isAreaEntered = isAreaEntered;

    this.areaFrameCount = World.ToFrameCount ( areaSecond );

    this.Location = location;

    this.movie = Movie.Clone ( this.scene.Makings[movieName] as Movie );
    this.movie.Ended += this.movieEnded;

    this.movieName = movieName;

    if ( !string.IsNullOrEmpty ( extendMovieName ) )
    {
        this.extendMovie = Movie.Clone ( this.scene.Makings[extendMovieName] as Movie );
        this.extendMovieName = extendMovieName;
    }

    this.Width = width;
    this.Height = height;
    this.halfSize = new Vector2 ( width / 2, height / 2 );

    this.Type = type;

    this.Speed = speed;
    this.Angle = angle;
    this.HitArea = hitArea;

    if ( null != this.HitArea )
        this.HitArea.Locate ( this.getHitAreaLocation ( ) );

}

在方法 LoadContent 中,我们将设置电影的相关内容,并从 World 中获取 SpriteBatch。在 Dispose 方法中我们销毁了一些对象。

internal virtual void LoadContent ( )
{
    this.spiritBatch = this.scene.World.Services.GetService ( typeof ( SpriteBatch ) ) as SpriteBatch;

    this.movie.Texture = ( this.scene.Makings[ this.movieName ] as Movie ).Texture;

    if ( null != this.extendMovie )
        this.extendMovie.Texture = ( this.scene.Makings[ this.extendMovieName ] as Movie ).Texture;

}

public void Dispose ( )
{ this.Dispose ( true ); }

protected virtual void Dispose ( bool disposing )
{

    if ( disposing )
    {
        this.movie.Ended -= this.movieEnded;
        this.movie.Dispose ( );

        if ( null != this.extendMovie )
            this.extendMovie.Dispose ( );

        if ( null != this.HitArea )
            this.HitArea.Dispose ( );

    }

}

在 Update 方法中,我们将播放动画,并根据可用性调用 updating 方法。而在 updating 方法中,我们将判断是否需要销毁精灵,以及是否需要移动精灵,使碰撞区的位置和精灵的位置保持一致。

我们允许碰撞区和精灵的位置存在偏移,你可以通过 getHitAreaLocation 方法修改他。

internal void Update ( GameTime time )
{
    Movie.NextFrame ( this.movie );

    if ( null != this.extendMovie )
        Movie.NextFrame ( this.extendMovie );

    if ( this.scene.IsEnabled && this.world.IsEnabled )
        this.updating ( time );

}

protected virtual void updating ( GameTime time )
{

    if ( this.destroyFrameCount > 0 && --this.destroyFrameCount <= 0 )
    {
        this.Destroy ( );
        return;
    }

    if ( this.isMoving && this.isMovable )
        this.move ( );

    if ( null != this.HitArea )
        this.HitArea.Locate ( this.getHitAreaLocation ( ) );

}

protected virtual Point getHitAreaLocation ( )
{ return new Point ( ( int ) this.Location.X, ( int ) this.Location.Y ); }

在 Draw 方法中,我们将根据一些条件来决定是否调用方法 drawing。而在 drawing 方法中,我们将调整电影的位置,并绘制他们。

同样,电影的位置可以不同于精灵的位置,你可以通过方法 getMovieLocation 来调整他。

internal void Draw ( GameTime time )
{

    if ( !this.scene.IsClosed && this.IsVisible )
    {
        this.spiritBatch.Begin ( );
        this.drawing ( time, this.spiritBatch );
        this.spiritBatch.End ( );
    }

}

protected virtual void drawing ( GameTime time, SpriteBatch batch )
{
    this.movie.Location = this.getMovieLocation ( );

    Movie.Draw ( this.movie, time, batch );

    if ( null != this.extendMovie )
    {
        this.extendMovie.Location = this.movie.Location;
        Movie.Draw ( this.extendMovie, time, batch );
    }

}

protected virtual Vector2 getMovieLocation ( )
{ return this.Location; }

方法 movieEnded 将在电影播放完毕之后调用,方法 Execute 用来执行一些命令,在本示例中不会用到。

方法 move 和 updateSpeed 是需要派生类修改的,以设置精灵的速度和移动。

protected virtual void movieEnded ( object sender, MovieEventArgs e )
{ }

internal virtual void Execute ( int action )
{ }

protected virtual void move ( )
{ }

protected virtual void updateSpeed ( )
{
    this.protoXSpeed = this.xSpeed;
    this.protoYSpeed = this.ySpeed;
}

精灵还有一些播放电影的方法,这里不再累述。

修改 World

我们需要为 World 增加一个管理精灵的类,代码如下:

internal sealed class SpiritCollection
{
    private readonly List<Spirit> spirits = new List<Spirit> ( );

    private bool isInitialized = false;

    internal SpiritCollection ( )
    { }

    internal void Initialize ( )
    {

        if ( this.isInitialized )
            return;

        foreach ( Spirit spirit in this.spirits.ToArray ( ) )
            spirit.LoadContent ( );

        this.isInitialized = true;
    }

    internal void Update ( GameTime time )
    {

        foreach ( Spirit spirit in this.spirits.ToArray ( ) )
            spirit.Update ( time );

    }

    internal void Draw ( GameTime time )
    {

        foreach ( Spirit spirit in this.spirits.ToArray ( ) )
            spirit.Draw ( time );

    }

    internal void Add ( Spirit spirit )
    {

        if ( spirit == null || this.spirits.Contains ( spirit ) )
            return;

        if ( isInitialized )
            spirit.LoadContent ( );

        spirit.DrawOrderChanged += this.drawOrderChanged;
        this.spirits.Add ( spirit );
        this.spirits.Sort ( DrawableSort );
    }

    internal bool Remove ( Spirit spirit )
    {

        if ( spirit == null )
            return false;

        spirit.DrawOrderChanged -= this.drawOrderChanged;

        return this.spirits.Remove ( spirit );
    }

    private void drawOrderChanged ( object sender, SpiritEventArgs e )
    { this.spirits.Sort ( DrawableSort ); }

    private static int DrawableSort ( Spirit a, Spirit b )
    {
        return a.DrawOrder.CompareTo ( b.DrawOrder );
    }

}

然后我们为 World 增加一个名称为 Components 的字段,用来管理精灵。

internal readonly SpiritCollection Components = new SpiritCollection ( );

 

示例

在 SceneT14 场景中,我们创建了一个精灵 bird,另外,我们有两个按钮,点击 Play,小鸟将移动,点击 Stop,小鸟停止移动。

下面是小鸟的代码,在代码中,我们通过修改 updateSpeed,move 方法实现了小鸟的移动。并通过 Go 和 Stop 方法控制了小鸟的移动。

internal class Bird
    : Spirit
{

    internal Bird ( IScene scene, Vector2 location )
        : base ( scene, 0, location,
        "bird", null,
        4, 0,
        new SingleRectangleHitArea ( new Rectangle ( -40, -40, 80, 80 ) ),
        80,
        80,
        0,
        true,
        false,
        false,
        0
        )
    { }

    protected override void updateSpeed ( )
    {
        this.xSpeed = this.speed;
        this.ySpeed = this.speed;

        base.updateSpeed ( );
    }

    protected override void move ( )
    {
        this.Location.X += this.xSpeed;
        this.Location.Y += this.ySpeed;
    }

    internal void Go ( )
    {
        this.isMoving = true;
        this.PlayMovie ( "go" );
    }

    internal void Stop ( )
    {
        this.isMoving = false;
        this.PlayMovie ( "stop" );
    }

}

在两个按钮的 Selected 事件中,我们分别让小鸟移动和停止,当然,我们忘记了注销事件。

internal sealed class SceneT14
    : CommandScene
{
    // ...

    private Bird bird;
    private readonly Button goButton;
    private readonly Button stopButton;

    internal SceneT14 ( )
        : base ( Vector2.Zero, GestureType.None, "background1",
        new Resource[] {
            new Resource ( "bird2.image", ResourceType.Image, @"image\bird2" ),
            new Resource ( "go.image", ResourceType.Image, @"image\button1" ),
            new Resource ( "stop.image", ResourceType.Image, @"image\button2" ),
        },
        new Making[] {
            new Movie ( "bird", "bird2.image", 80, 80, 5, "stop",
                new MovieSequence ( "go", true, new Point ( 1, 1 ), new Point ( 2, 1 ) ),
                new MovieSequence ( "stop", true, new Point ( 3, 1 ) )
                ),
            new Button ( "b.go", "go.image", "GO", new Vector2 ( 100, 100 ), 100, 50, new Point ( 1, 1 ) ),
            new Button ( "b.play", "stop.image", "STOP", new Vector2 ( 100, 300 ), 100, 50, new Point ( 1, 1 ) )
        }
        )
    {
        this.goButton = this.makings[ "b.go" ] as Button;
        this.stopButton = this.makings[ "b.play" ] as Button;

        this.goButton.Selected += this.goButtonSelected;
        this.stopButton.Selected += this.stopButtonSelected;
    }

    private void goButtonSelected ( object sender, ButtonEventArgs e )
    { this.bird.Go ( ); }

    private void stopButtonSelected ( object sender, ButtonEventArgs e )
    { this.bird.Stop ( ); }

    public override void LoadContent ( )
    {
        base.LoadContent ( );

        this.bird = new Bird ( this, new Vector2 ( 200, 100 ) );
        this.bird.LoadContent ( );

        this.world.Components.Add ( this.bird );
    }

    public override void UnloadContent ( )
    {
        this.world.Components.Remove ( this.bird );
        this.bird.Dispose ( );

        base.UnloadContent ( );
    }

}

 

本期视频 http://v.youku.com/v_show/id_XNTgwOTE3NTky.html

项目地址 http://wp-xna.googlecode.com/
更多内容 WPXNA

平方开发的游戏 http://zoyobar.lofter.com/

QQ 群 213685539

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

posted @ 2013-07-10 13:19  麦丝平方  阅读(997)  评论(5编辑  收藏  举报