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
.
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.
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.
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
.
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.
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.
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
.
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.
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
.
private bool isInitialized = false;
protected override void OnNavigatedTo ( NavigationEventArgs e )
{
this.isInitialized = true;
}
We also added some methods to manage the Scene
.
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.
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 )
{
isBroken = true;
}
scene.Draw ( time );
}
if ( !isBroken )
;
}
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
.
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
.
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.