Susan IbachTechnical Evangelist
In parts 1 and 2 of this series, we setup a multiplatform XNA solution that deploys to the PC, Xbox 360 and Windows Phone 7 devices seamlessly. In this 3rd and final part of the series, we’ll implement platform-specific behavior within the same codebase. And we’ll finally make our ship do something!
Conditional compilation symbols
When you created the three projects, Visual Studio set project-specific Conditional compilation symbols. These flags can be used with preprocessing directives to create conditions in the compilation process.
To view and edit these symbols, Right-click on a game project, select Properties, and go to the Build tab.
By default, Visual Studio added the WINDOWS symbol for the Windows project, XBOX and XBOX360 symbols for the Xbox project and WINDOWS_PHONE symbol the Windows Phone 7 project. In this article, we’ll use these to set certain platform-specific properties and capture hardware-specific input.
Settings up graphics properties
We’ll use the GraphicsDeviceManager to setup the screen properties for each platform. In the Initialize() method of the Game1 class, add the three #if preprocessor directives:
protected override void Initialize()
// TODO: Add your initialization logic here
For the purpose of this tutorial, we’ll run all three platform builds in full-screen. However, we’ll need to set an appropriate resolution for each one:
// TODO: Add your initialization logic here
graphics.PreferredBackBufferWidth = GraphicsDevice.DisplayMode.Width;
graphics.PreferredBackBufferHeight = GraphicsDevice.DisplayMode.Height;
graphics.PreferredBackBufferWidth = 1280;
graphics.PreferredBackBufferHeight = 720;
graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 480;
graphics.IsFullScreen = true;
For the PC, we retrieve the desktop resolution and set our back-buffer (the viewport) to match it. For the Xbox, we use its native 720p high-definition output by setting the back-buffer bounds to 1280 by 720 pixels. For the phone, we simply set the resolution to the maximum resolution dictated by the Windows Phone 7 platform. Finally, we set the IsFullScreen property to true and call the ApplyChanges() method to commit the above changes. Note that these 2 lines of code are outside of any platform-specific preprocessor directives, since we want these two things to apply to all three of our builds.
When building the solution, depending on the target project, each flag in the processing directive will be checked. If it is set, the code inside the #if-#endif pair will be built. This means that you must be careful about fragmenting your code. For example, if you declare a field in a #if-#endif directive, but assign to it outside of the same condition, you will get an error for a specific platform (or a specific condition) about a missing declaration.
At this point, if you were to run the project on any platform, the resolution will be automatically adjusted and you should see the ship in platform’s native full-screen resolution, without any scaling or stretching!
Note: IsFullScreen is not strictly needed for the Xbox version, as all XNA games on the Xbox run in full-screen by default.
Capturing platform-specific input
Let’s make things a bit more interesting and challenging, by rotating the ship with a platform specific input device. On the PC, the input comes primarily from the mouse/keyboard combination; on the Xbox 360 from the Xbox controller; on the Windows Phone 7 from the touch panel. The Xbox controller can also be used as a gamepad in Windows. A keyboard can be used on the Xbox 360, but you should avoid this scenario, since most Xbox 360 owners don’t have a keyboard connected to their console. Windows Phone 7 devices also have additional buttons such as the “back” button, which can be read in XNA.
First, we’ll add platform independent code that will rotate the ship around the y-axis (also known as “Yaw”). Add a float to hold the yaw angle:
public class Game1 : Microsoft.Xna.Framework.Game
float yawAngle = 0.0f;
To rotate the ship, we’ll set the world matrix to the rotation about the y-axis matrix that is constructed with the yawAngle, which we set with an input device.
protected override void Update(GameTime gameTime)
world = Matrix.CreateRotationY(yawAngle);
Next we will capture input from the keyboard to rotate the ship with the arrow keys. Remember that keyboard input should be captured on Windows only; we check if WINDOWS is set.
yawAngle += 0.05f;
yawAngle -= 0.05f;
We’ll use the Xbox controller’s left thumb-stick to rotate the ship left and right as well.
yawAngle += GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X;
If you were to test this code now, you’ll notice a little quirk. On Windows we’re incrementing/decrementing the angle by a constant of 0.05 radians every update. But on the Xbox the angle depends on the X position of the thumb-stick, which can result in a large value. The easiest solution is to simply dampen the value by a constant:
yawAngle += GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X * 0.05f;
On Windows Phone 7, input is not as simple as keyboard strokes and thumb-stick positions. The phone captures input by recording touches. Touch points and displacement between them can be determined from raw touch data. Common higher level motions – known as gestures – are already provided by the XNA framework, so you don’t have to write your own. We’ll use the horizontal drag gesture to rotate the ship. Before we go any further, however, we need to include a reference to the Microsoft.Xna.Framework.Input.Touch assembly. It wasn’t included because our Windows Phone 7 project was created from the Windows project.
Expand the Windows Phone Copy of XNAIntro project, Right-click on References and select Add Reference. Under the .NET tab select Microsoft.Xna.Framework.Input.Touch.
We can now use the namespace in Game1. Since it is to be used for the phone only, be sure to place it in a proper compilation condition:
/// This is the main type for your game
public class Game1 : Microsoft.Xna.Framework.Game
Before we can detect the horizontal drag gesture, we need to let the touch panel know that this gesture needs to be enabled.
TouchPanel.EnabledGestures = GestureType.HorizontalDrag;
Once the gesture is enabled, you can access the gesture with the TouchPanel.ReadGesture() method:
GestureSample gesture = TouchPanel.ReadGesture();
yawAngle += gesture.Delta.X * 0.005f;
We first check whether or not a gesture is available with the TouchPanel.IsGestureAvailable property. Failure to check for this condition will result in an exception thrown by the first TouchPanel.ReadGesture() call. We then read the gesture from the touch panel, which will return the next available gesture. This means that when you have multiple gestures enabled you would need to check the gesture.GestureType property to determine which gesture this is. In this case, the check is not required because the horizontal drag gesture is the only one we enabled. Also note that since the input feedback is different between all devices, we use a different dampening value.
Ready, Steady, Go!
We’re done! Select any platform and run the little “game” to enjoy a truly breathtaking experience. Using a single codebase, you effectively created not one or two, but three game versions. All three are identical, and yet all three run on different hardware architectures and accept platform-specific input.
Although this is the conclusion of the series, you can find plenty of resources to get you going further on the App Hub at http://create.msdn.com. Enjoy!
In part 1 of this series, we set up a tri-platform XNA
solution that can be deployed to three platforms simultaneously. While the cornflower
blue screen we saw in the previous part is a truly breathtaking achievement, in
this part we’ll write a little bit of code to make our game do something.
XNA is a game
development framework. Games often render stuff. Therefore, if A = B and B = C
and Albatrosses feed on both fish and krill, then we must conclude that by the
end of this article we’ll be drawing something!
Be sure to download the space ship model included in this
article. This model was stolen borrowed from the AppHub catalog, where
it is used in numerous XNA samples.
Open up the solution that we created in part 1 and brace
yourself for the extremely complex and time-consuming process of importing the
model into our project. Ready? Drag-and-drop Ship.fbx and ShipDiffuse.tga
directly into the XNAIntroContent
project in Visual Studio. Right-click
on ShipDiffuse.tga and click Exclude From Project. You’re done! While
you might be relieved to know how quick and simple the process really is, you
are probably wondering why we excluded the texture from the Content project.
is Ship.fbx’s UV map – a texture
that is mapped on to the model when it is rendered in 3D. It is referenced
directly by the model file, which means that the Content Pipeline will be aware of its physical location on disk during
build time. When we drag-and-dropped the two files into the Content project,
they were physically copied to the project’s folder, where they are expected to
be. Since the texture is used by the model, we don’t need to load or use it
explicitly in our project, hence its exclusion.
Inclusion of the texture in the project would not cause any technical
issues, but it will be built twice (once by the model’s processor and once by
the default texture processor) resulting in a warning, “Asset was built 2 times
with different settings”.
Now that we have the model in our content project, let’s
write some code to load it and draw it on the screen. We’ll begin by declaring
a field for it:
= new GraphicsDeviceManager(this);
You’ll notice that the ContentManager’s RootDirectory
property is set to “Content”. This is to reference the name of the Content
project set in the Content project’s properties under Content Root Directory.
This means that if you have two or more Content projects
in one solution, the ContentManager
of each game project could have its RootDirectory
property set to the desired Content project name.
Let’s use the ContentManager
to load our model. In the LoadContent() method add:
ship = Content.Load<Model>("Ship");
The string Ship
is the relative path to the asset in the Content project (our model in this
case) minus the extension. There is no need to add Content in the path. Note that ContentManager’s
Load method is generic and can therefore be used to load any type of an asset,
including models, sounds, textures, XML files and custom types.
Now that we loaded our 3D model, we want to draw it. In
the perfect world, this would be done by simply calling a Model.Draw() method
that would somehow magically read our minds and draw the model just as we want
it. In the real world, things are not quite as simple.
First, we need to use a number of Matrices to define various properties needed to draw a 3D scene.
Add the following matrix declarations after your model.
Before we do anything else, a quick rundown of what each
Matrix is and what it does:
We’ll set these parameters in the LoadContent() method. Right
after loading our model, add the following:
world = Matrix.Identity;
view = Matrix.CreateLookAt(Vector3.Backward * 5000.0f, Vector3.Zero, Vector3.Up);
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio, 1.0f, 10000.0f);
We set the world matrix to identity, which essentially
translates to the world’s “origin”. We specify the “3D origin” (Vector3.Zero) as the target (this is
where our model will be), place the camera an arbitrary distance backwards (Vector3.Backward
* 5000.0f) and specify the positive Y-Axis as the camera’s “Up” direction (Vector3.Up).
Our projection matrix will have the aspect ratio of the viewport (GraphicsDevice.Viewport.AspectRatio),
Pi over 4 radians (or 45 degrees) field of view, and fairly arbitrary value for
the near and far planes.
The reason for so many arbitrary values is due to the
limited scope of this article. Their exact meaning is simply not important
We are ready to draw our model. Add the following private
method in the Game1 class:
private void DrawModel(Model
(ModelMesh mesh in
(BasicEffect effect in
effect.World = world;
effect.View = view;
This method does three simple things. The first foreach loop iterates over all of the
meshes in the model. The second loop iterates over each effect; where the all of the mandatory and optional parameters are
set. In actual fact, foreach Effect in
ModelMesh.Effects is a shortcut for iterating over each MeshPart and setting each MeshPart Effect individually. Needless
to say, this is definitely not within the scope of this article.
The third and last step
is drawing the mesh with mesh.Draw().
Notice that we make use of our three key matrices – world, view and projection
– to set the three properties required to correctly draw the geometry (but not
necessarily the color and lighting) of the model. We also use EnableDefaultLighting() method to light
our model using the Three-point lighting method
We’re almost ready to
draw, but there is one last piece of the puzzle missing in our drawing code. We
need to account for transforms that
were applied to each mesh when the model was created. The transforms are specified in each Bone of the model. The collection of Bones defines the mesh hierarchy where each mesh has a relation to
its parent, and the transform
specifies the mesh’s transformation relative to its parent. For example, if a
tree model has a mesh for each of its branches, then each branch (and
ultimately the leaves) will be positioned relative to a parent branch.
We look up the transforms with two simple lines of
Matrix transforms = new
And then account for
each transform when multiplying by
the world matrix:
World = transforms[mesh.ParentBone.Index] * world;
Our final drawing code
now looks like this:
transforms = new Matrix[ship.Bones.Count];
transforms[mesh.ParentBone.Index] * world;
Let’s draw our ship by
calling the DrawModel(ship) in the
Draw() method of the Game1 class:
protected override void Draw(GameTime gameTime)
// TODO: Add
your drawing code here
By this point, you might
be wondering which platform this code was meant for. After all, we have three
separate projects, which will be built for three separate and architecturally
unique hardware platforms. You might even be convinced that at least some
conditions must be placed in code to ensure that it runs on three different
CPUs and is rendered by three different GPUs.
Oddly enough, it is with
this concern that I would like to formally welcome you to the wonderful world
of XNA – a true, tri-platform game development framework.
Stay tuned for part 3!
This combination video and text tutorial set teaches basic 2D game development on Windows, Xbox 360, and Windows Phone 7 using XNA Game Studio 4.0 my be found here
Learn the following lessons in our easy-to-follow chapters:
On this new Game Development Page
Microsoft's DigiGirlz programs give high school girls the opportunity to learn about careers in technology, connect with Microsoft employees, and participate in hands-n computer and technology workshops at the NERD Center in Cambridge, designed to provide high school girls with a better understanding of what a career in technology is all about.
During the event, students interact with Microsoft employees and managers to gain exposure to careers in business and technology and to get an inside look at what it's like to work at Microsoft. This exciting event provides girls with career planning assistance, information about technology and business roles, thought-provoking exercises, and interesting Microsoft product demonstrations. By participating in the Microsoft DigiGirlz Day, young women can find out about the variety of opportunities available in the high-tech industry and can explore future career paths.
There is no cost associated with this event. Microsoft provides the day free of charge for the girls who attend. Microsoft accepts girls and schools on a first-come, first-served basis. Local high schools are encouraged to send in registration requests for their interested girls for this event as soon as possible.
How do girls register to attend a DigiGirlz Day?
High schools can request to register girls for a DigiGirlz Day in their city by contacting email@example.com.
An individual girl who is currently in high school is welcome to register for a DigiGirlz Day in her area by sending an e-mail message with her name, her city and state, and the name of her high school to firstname.lastname@example.org.
Release Forms: All students and parent/legal guardian (if under 18) must complete and sign a media release form in order to attend the DigiGirlz event. The form can be obtained by e-mailing email@example.com.
You may find the tool set here