• 使用 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

  • 相关阅读:
    day25:接口类和抽象类
    vue1
    How the weather influences your mood?
    机器学习实验方法与原理
    How human activities damage the environment
    Slow food
    Brief Introduction to Esports
    Massive open online course (MOOC)
    Online learning in higher education
    Tensorflow Dataset API
  • 原文地址:https://www.cnblogs.com/zoyobar/p/wpxna13.html
Copyright © 2020-2023  润新知