Click here to Skip to main content
15,881,812 members
Articles / Programming Languages / C#

Using Scene Class to Create Different Screens in XNA, WPXNA (8)

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
21 Jun 2013CPOL3 min read 6.6K   2  
Using Scene class to create different screens in XNA, WPXNA (8)

The original article has been posted here.

Introduction/Catalog

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

  • Scene, Screen
  • Modify the World Class
  • Example

Scene, Screen

Here, the scene is screen or page, such as the main screen, usually has a start button. I created Scene class to represent a screen. It will include some classes before, such as ResourceManager, AudioManager, and so on.

The following is some fields and properties of the class Scene.

C#
internal readonly GestureType GestureType;
private bool isClosed;
public bool IsClosed
{
 get { return this.isClosed; }
 set { this.isClosed = value; }
}
protected bool isEnabled = true;
public bool IsEnabled
{
 get { return this.isEnabled; }
}

internal readonly bool IsBroken;

internal readonly Vector2 Location;
protected World world;
public World World
{
 get { return this.world; }
 set
 {
  this.resourceManager.World = value;
  this.world = value;
 }
}

private SpriteBatch spiritBatch;
private readonly ResourceManager resourceManager;
protected AudioManager audioManager;
public AudioManager AudioManager
{
 get { return this.audioManager; }
}
private readonly bool isBackgroundMusicLoop;

protected readonly Dictionary<string, Making> makings = 
          new Dictionary<string, Making> ( );
public Dictionary<string, Making> Makings
{
 get { return this.makings; }
}

GestureType field of the Scene is the gesture supported, such as: double tap, tap, and so on.

Property IsClosed indicates whether the Scene have been closed, while properties IsEnabled indicates whether the Scene is available.

Field Location is location of the Scene. Property World is the World used by Scene.

Field spiritBatch obtained from the World is used to draw all contents on the screen. Field resourceManager is used to manage all the required resources, field audioManager is used to manage the audio, field isBackgroundMusicLoop indicates whether the background music loops.

Property Makings represents all the Making in the screen, such as labels.

After that, we will initialize some fields in constructor.

C#
protected Scene ( Vector2 location, GestureType gestureType, 
  IList<Resource> resources, IList<Making> makings, 
  bool isBroken, bool isBackgroundMusicLoop )
{
 this.Location = location;
 this.GestureType = gestureType;

 this.resourceManager = new ResourceManager ( resources );
 this.audioManager = new AudioManager ( );

 if ( null != makings )
  foreach ( Making making in makings )
   if ( null != making )
    this.makings.Add ( making.Name, making );

 foreach ( Making making in this.makings.Values )
  making.Init ( this );

 this.IsBroken = isBroken;
 this.isBackgroundMusicLoop = isBackgroundMusicLoop;
}

Note that we added an Init method for Making class, it used to initialize Making. In the Init method of Making, we will modify the location of Making which implements ILockable interface.

C#
protected Scene scene;

internal virtual void Init ( Scene scene )
{
 this.scene = scene;

 if ( this is ILockable )
  ( this as ILockable ).Location += scene.Location;
}

In the LoadContent method, we get SpriteBatch from the World, and call the LoadContent method of ResourceManager to load resources and pass them to the Making and AudioManager. Since then, the AudioManager trying to play music that is named scene.sound.

The UnloadContent method, we uninstalled the loaded resources. The Dispose method, in addition to unload resources, we will also call the Dispose method of Making.

C#
public virtual void LoadContent ( )
{
 this.spiritBatch = this.world.Services.GetService ( typeof ( SpriteBatch ) ) as SpriteBatch;

 this.resourceManager.LoadContent ( );

 foreach ( Making making in this.makings.Values )
  making.InitResource ( this.resourceManager );

 this.audioManager.LoadContent ( this.resourceManager );

 this.audioManager.PlayMusic ( "scene.sound", this.isBackgroundMusicLoop );
}

public virtual void UnloadContent ( )
{
 this.audioManager.UnloadContent ( );

 this.resourceManager.UnloadContent ( );
}

public virtual void Dispose ( )
{
 foreach ( Making making in this.makings.Values )
  making.Dispose ( );

 this.UnloadContent ( );
}

In the Update method, we don't update if the Scene is closed or is not enabled, classes derived from the Scene can modify the updating method to implement it own features. In the Draw method, we don't draw if the Scene is closed, classes derived from the Scene can modify the drawing to paint their own contents.

C#
protected virtual void updating ( GameTime time )
{ }

public void Update ( GameTime time )
{
 if ( this.isClosed || !this.isEnabled )
  return;

 this.updating ( time );
}

protected virtual void drawing ( GameTime time, SpriteBatch batch )
{ }

public void Draw ( GameTime time )
{
 if ( this.isClosed )
  return;

 this.spiritBatch.Begin ( );
 this.drawing ( time, this.spiritBatch );
 this.spiritBatch.End ( );
}

The Scene can use Controller class to determine the user's input. A derived class can modify the inputing method.

C#
protected virtual void inputing ( Controller controller )
{ }

public bool Input ( Controller controller )
{

 if ( this.isClosed || !this.isEnabled )
  return false;

 this.inputing ( controller );
 return true;
}

Finally, the Scene class can call the Close method to close itself. Before closing, we trigger the Closing and Closed.

C#
internal event EventHandler<SceneEventArgs> Closing;
internal event EventHandler<SceneEventArgs> Closed;

public void Close ( )
{
 if ( this.isClosed )
  return;

 if ( null == this.world )
  throw new NullReferenceException ( "world can't be null when closing scene" );

 if ( null != this.Closing )
 {
  SceneEventArgs closingArg = new SceneEventArgs ( );

  this.Closing ( this, closingArg );

  if ( closingArg.IsCancel )
   return;
 }

 if ( null != this.Closed )
  this.Closed ( this, new SceneEventArgs ( ) );

 this.world.RemoveScene ( this );
}

Modify the World Class

Increase isPreserved field for the World, we can get it by IsApplicationInstancePreserved property in activate method, if isPreserved is true, then we do not need to run the code in the OnNavigatedTo again.

C#
private bool isPreserved = false;

private void activate ( object sender, ActivatedEventArgs e )
{ this.isPreserved = e.IsApplicationInstancePreserved; }

protected override void OnNavigatedTo ( NavigationEventArgs e )
{

 if ( this.isPreserved )
  return;

 // ...
}

Add isInitialized field that used to indicate whether the World has already been initialized in order to decide whether to initialize the Scene. In the OnNavigatedTo, isInitialized is set to true.

C#
private bool isInitialized = false;

protected override void OnNavigatedTo ( NavigationEventArgs e )
{
 // ...
 
 this.isInitialized = true;
 
 // ...
}

We also added some methods to manage the Scene.

C#
pprivate readonly List<Scene> scenes = new List<Scene> ( );

protected override void OnNavigatedTo ( NavigationEventArgs e )
{
 // ...

 foreach ( Scene scene in this.scenes )
  scene.LoadContent ( );

 // ...
}

private void appendScene ( Scene scene, Type afterSceneType, bool isInitialized )
{
 if ( null == scene )
  return;

 if ( !isInitialized )
 {
  scene.World = this;
  scene.IsClosed = false;

  if ( this.isInitialized )
   scene.LoadContent ( );
 }

 int index = this.getSceneIndex ( afterSceneType );

 if ( index < 0 )
  this.scenes.Add ( scene );
 else
  this.scenes.Insert ( index, scene );

 TouchPanel.EnabledGestures = scene.GestureType;
}

internal void RemoveScene ( Scene scene )
{
 if ( null == scene || !this.scenes.Contains ( scene ) )
  return;

 scene.IsClosed = true;

 if ( this.isInitialized )
  try
  {
   if ( null != scene )
    scene.Dispose ( );
  }
  catch
  { scene.Dispose ( ); }

 this.scenes.Remove ( scene );

 if ( this.scenes.Count > 0 )
  TouchPanel.EnabledGestures = this.scenes[ this.scenes.Count - 1 ].GestureType;
}

private int getSceneIndex ( Type sceneType )
{
 if ( null == sceneType )
  return -1;

 string type = sceneType.ToString ( );

 for ( int index = this.scenes.Count - 1; index >= 0; index-- )
  if ( this.scenes[ index ].GetType ( ).ToString ( ) == type )
   return index;

 return -1;
}

private T getScene<T> ( )
 where T : Scene
{
 int index = this.getSceneIndex ( typeof ( T ) );

 return index == -1 ? null : this.scenes[ index ] as T;
}

Field scenes contains all scenes.

Method appendScene is used to add a scene to the World, parameter afterSceneType is used to indicate order.

Method RemoveScene is used to remove a scene from the World, getSceneIndex method is used to get the index of a particular scene, getScene<T> is used to get the specified scene.

C#
private void OnUpdate ( object sender, GameTimerEventArgs e )
{
 if ( !this.IsEnabled )
  return;

 this.controller.Update ( );

 Scene[] scenes = this.scenes.ToArray ( );
 GameTime time = new GameTime ( e.TotalTime, e.ElapsedTime );

 foreach ( Scene scene in scenes )
  if ( null != scene )
   scene.Update ( time );

 for ( int index = scenes.Length - 1; index >= 0; index-- )
  if ( null != scenes[ index ] && scenes[ index ].Input ( this.controller ) )
   break;
}

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

 bool isBroken = false;
 GameTime time = new GameTime ( e.TotalTime, e.ElapsedTime );

 foreach ( Scene scene in this.scenes.ToArray ( ) )
  if ( null != scene )
  {
   if ( !isBroken && scene.IsBroken )
   {
    // Draw sprites
    isBroken = true;
   }

   scene.Draw ( time );
  }

 if ( !isBroken )
  // Draw sprites.
  ;
}

The OnUpdate method, we will update every scene, and only the first scene can accept input from the user. In the OnDraw method, we will draw one by one.

Example

I created a scene called SceneT9.

C#
pinternal sealed class SceneT9
 : Scene
{
 private readonly Label l1;
 private readonly Movie bird2;

 internal SceneT9 ( )
  : base ( Vector2.Zero, GestureType.Tap | GestureType.DoubleTap,
  new Resource[] {
   new Resource ( "bird2.image", ResourceType.Image, @"image\bird2" ),
   new Resource ( "click.s", ResourceType.Sound, @"sound\click" ),
   new Resource ( "peg", ResourceType.Font, @"font\myfont" ),
  },
  new Making[] {
   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 ) )
   ),
   new Label ( "l1", "Hello windows phone!", 2f, Color.LightGreen, 0f )
  }
  )
 {
  this.l1 = this.makings[ "l1" ] as Label;
  this.bird2 = this.makings[ "bird2.m" ] as Movie;
 }

 protected override void inputing ( Controller controller )
 {
  if ( !controller.IsGestureEmpty && controller.Gestures[ 0 ].GestureType == GestureType.Tap )
   this.audioManager.PlaySound ( "click.s" );
 }

 protected override void updating ( GameTime time )
 {
  Movie.NextFrame ( this.bird2 );

  base.updating ( time );
 }

 protected override void drawing ( GameTime time, SpriteBatch batch )
 {
  base.drawing ( time, batch );

  Label.Draw ( this.l1, batch );
  Movie.Draw ( this.bird2, time, batch );
 }
}

In SceneT9, we add resources needed for the scene, and a bird movie, a label. By the makings of the scene, we will keep them into l1 and bird2.

In the updating and drawing methods, we updated the movie and drawing them. In the inputing method, we play clicking sound.

In the OnNavigatedTo method of the World, we use the appendScene method to add a new SceneT9.

C#
protected override void OnNavigatedTo ( NavigationEventArgs e )
{
 // ...

 this.appendScene ( new mygame.test.SceneT9 ( ), null, false );

 base.OnNavigatedTo ( e );
}

Get code at http://wp-xna.googlecode.com/, for 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 --