zoukankan      html  css  js  c++  java
  • 使用 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, @"imageird2" ),
                new Resource ( "go.image", ResourceType.Image, @"imageutton1" ),
                new Resource ( "stop.image", ResourceType.Image, @"imageutton2" ),
            },
            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

  • 相关阅读:
    谈谈Nullable<T>的类型转换问题
    MiniProfiler使用方法
    捕获变量
    web服务相关的问题或技巧
    对接mysql数据库遇见的一些问题
    委托
    导出到Excel
    斐波那契数列的运算时间
    .net framework摘抄与理解
    sql 语句
  • 原文地址:https://www.cnblogs.com/zoyobar/p/wpxna13.html
Copyright © 2011-2022 走看看