Click here to Skip to main content
15,892,927 members
Articles / XNA
Technical Blog

Using the Spirit class to create basic units in an XNA game, WPXNA (13)

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
10 Jul 2013CPOL4 min read 5.7K   7  
Using the Spirit class to create basic units in an XNA game, WPXNA (13).

The original: http://www.wpgamer.info/2013/07/using-spirit-class-to-create-basic.html

Introduction/Catalog

I has developed some games on Windows Phone. Here, I'll share my experiences and gradually to upload some classes, no good name, I just call it WPXNA. (Some example code may not be stringent enough.)

  • Spirit
  • Modify the World
  • Example

Spirit

Spirit is an important class that is the unit in game, such as: the enemies, player. Many classes are derived from the Spirit class.

Here are some members of the Spirit.

Destroyed event is called in the Destroy method, and the event is primarily being used by SpiritManager. DrawOrderChanged will be called by DrawOrder property, this property is used to determine the drawing order of spirits.

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

 }
}

Field scene is the scene used to control spirits, later we will change the type to IPlayScene. Field world is World class control the scenes. Field audioManager is used to control the music, we will get AudioManager from the scene, rather than recreate it.

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

Field movie is the movie used by Spirit which can be shared, field movieName is the name of movie. Fieldd extendMovie, extendMovieName represent the extended movie and extended movie's name. Extended movie can be used to play special effects, such as: a red light after the player was injured.

Property Type represents the type of spirit.

C#
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; }
}

Fields Width, Height is used to indicate size of the spirits, halfSize is used to represent the half the size of the spirits, we will calculate this value in advance in order to avoid double counting.

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

Field Location is the spirit's position, field Angle is the spirit's angle, if isRotable is false, then the Angle cannot be modified, field isMovieRotable is whether to modify the angle of the movie. Whenever the Angle is modified, we will call the updateSpeed method.

Field speed is the speed of spirits, field xSpeed, ySpeed represent the x, y axis speed. If you modify the Speed property, the xSpeed and ySpeed fields will not be modified, but you can modify the updateSpeed method in a derived class to do this task.

Fields protoSpeed, protoXSpeed, protoYSpeed are used to record the original value of speed. Field spiritBatch is used to draw the movie.

Field isMovable indicates whether the spirit can be moved, field isMoving indicates whether the spirit is moving.

C#
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;

Field destroyFrameCount is the frame count used to destroy the spirit, the field isAreaLimited indicates whether the spirit can exceed BattleArea of the World, in the field isAreaEntered indicates whether the spirit had entered BattleArea. (But there's no show on isAreaLimited, isAreaEntered.)

C#
private long destroyFrameCount;

private readonly bool isAreaLimited;
protected bool isAreaEntered;

private long areaFrameCount;

internal bool IsVisible = true;

In the constructor of the Spirit, we will initialize these fields.

C#
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 ( ) );

}

In the LoadContent method, we will set the movie content, and get the SpriteBatch from the World. And we will eliminate a number of objects in the Dispose method.

C#
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 ( );

 }

}

In the Update method, we will call the updating method depending on availability. In the updating method, we will determine whether to destroy the spirits, and whether to move the spirits, fix the location of HitArea.

We allow the HitArea and Spirit in different locations, you can modify the offset value in getHitAreaLocation method.

C#
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 ); }

In the Draw method, we decide whether to call the drawing method based on some criteria. In the drawing method, we will adjust the movie's location and draw them.

Similarly, the movie location can be different from the spirit location, you can use the method getMovieLocation to tweak it.

C#
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; }

Method movieEnded will be called after the movie is finished, method Execute is used to execute commands.

Methodx move and updateSpeed require to be modified by a derived class.

C#
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;
}

Modify the World

We need to add a manager to World class, as follows:

C#
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 );
 }

}

And then we add a field called Components used to manage the spirits.

C#
internal readonly SpiritCollection Components = new SpiritCollection ( );

Example

In the SceneT14, we have created a bird, in addition, we have two buttons Play and Stop that control the bird.

Here is the bird's code, we modified the method updateSpeed, move. And use the Stop and Go method to control the movement of the bird.

C#
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" );
 }

}

In Selected events of the two buttons, we let the bird move and stop, of course, we forgot to logoff the events.

C#
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 ( );
 }
}

Get code here: http://wp-xna.googlecode.com/, more contents, please visit WPXNA.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --