#region File Description //----------------------------------------------------------------------------- // PerformanceMeasuringGame.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; using PerformanceMeasuring.GameDebugTools; namespace PerformanceMeasuring { /// /// This sample game shows how to use the GameDebugTools to measure the performance of a game, /// as well as how the number of objects and interactions between them can affect performance. /// public class PerformanceMeasuringGame : Game { // The maximum number of spheres in our world const int maximumNumberOfSpheres = 200; GraphicsDeviceManager graphics; // A SpriteBatch, font, and blank texture for drawing our instruction text SpriteBatch spriteBatch; SpriteFont font; Texture2D blank; // The text we draw as instructions. string instructions = #if WINDOWS_PHONE "Tap - Toggle collisions\nDrag up/down - Change number of spheres"; #else "X - Toggle collisions\nUp - Increase number of spheres\nDown - Decrease number of spheres"; #endif // The size of the world. The world is a bounding box ranging from -worldSize to worldSize on // the X and Z axis, and from 0 to worldSize on the Y axis. const float worldSize = 20f; // A model for our ground Model ground; // An array of spheres and the number of currently active spheres Sphere[] spheres = new Sphere[maximumNumberOfSpheres]; int activeSphereCount = 50; // Are we colliding the spheres against each other? bool collideSpheres = true; // Various input states for changing the simulation GamePadState gamePad, gamePadPrev; KeyboardState keyboard, keyboardPrev; public PerformanceMeasuringGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; #if WINDOWS_PHONE TargetElapsedTime = TimeSpan.FromTicks(333333); graphics.IsFullScreen = true; #endif } /// /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// protected override void Initialize() { // Initialize the DebugSystem and change the visibility of the FpsCounter and TimeRuler. // The FpsCounter will show us our current "frames per second". On Windows Phone we can // have a maximum of 30 frames per second. On Windows and Xbox, we can reach 60 frames // per second with our current game settings. // The TimeRuler allows us to instrument our code and figure out where our bottlenecks // are so we can speed up slow code. DebugSystem.Initialize(this, "Font"); DebugSystem.Instance.FpsCounter.Visible = true; DebugSystem.Instance.TimeRuler.Visible = true; DebugSystem.Instance.TimeRuler.ShowLog = true; // Enable the Tap and FreeDrag gestures for our input on Windows Phone TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag; base.Initialize(); } /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // Load the font for our UI font = Content.Load("Font"); // Create a blank texture for our UI drawing blank = new Texture2D(GraphicsDevice, 1, 1); blank.SetData(new[] { Color.White }); // Load the ground model ground = Content.Load("Ground"); // Create our spheres CreateSpheres(); } /// /// Helper method that creates all of our spheres. /// private void CreateSpheres() { // Create a random number generator Random random = new Random(); // These are the various colors we use when creating the spheres Color[] sphereColors = new[] { Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Pink, Color.Purple, Color.Yellow }; // The radius of a sphere const float radius = 1f; for (int i = 0; i < maximumNumberOfSpheres; i++) { // Create the sphere Sphere sphere = new Sphere(GraphicsDevice, radius); // Position the sphere in our world sphere.Position = new Vector3( RandomFloat(random, -worldSize + radius, worldSize - radius), RandomFloat(random, radius, worldSize - radius), RandomFloat(random, -worldSize + radius, worldSize - radius)); // Pick a random color for the sphere sphere.Color = sphereColors[random.Next(sphereColors.Length)]; // Create a random velocity vector sphere.Velocity = new Vector3( RandomFloat(random, -10f, 10f), RandomFloat(random, -10f, 10f), RandomFloat(random, -10f, 10f)); // Add the sphere to our array spheres[i] = sphere; } } /// /// A helper method that generates a random float in a given range. /// private static float RandomFloat(Random random, float min, float max) { return (float)random.NextDouble() * (max - min) + min; } /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { // We must call StartFrame at the top of Update to indicate to the TimeRuler // that a new frame has started. DebugSystem.Instance.TimeRuler.StartFrame(); // We can now begin measuring our Update method DebugSystem.Instance.TimeRuler.BeginMark("Update", Color.Blue); // Update the input state for the game UpdateInput(gameTime); // Update all of the spheres in the game, handling collision if desired UpdateSpheres(gameTime); base.Update(gameTime); // End measuring the Update method DebugSystem.Instance.TimeRuler.EndMark("Update"); } /// /// Helper method to handle the input for the sample. /// private void UpdateInput(GameTime gameTime) { // Update the game pad and keyboard input states gamePadPrev = gamePad; gamePad = GamePad.GetState(PlayerIndex.One); keyboardPrev = keyboard; keyboard = Keyboard.GetState(); // If we've pressed the Back button or Escape key, exit the game if (gamePad.IsButtonDown(Buttons.Back) || keyboard.IsKeyDown(Keys.Escape)) Exit(); // If the X key or button was pressed, toggle whether or not we're colliding the spheres if ((gamePad.IsButtonDown(Buttons.X) && gamePadPrev.IsButtonUp(Buttons.X)) || (keyboard.IsKeyDown(Keys.X) && keyboardPrev.IsKeyUp(Keys.X))) { collideSpheres = !collideSpheres; } // If the user pressed Up or Down on the keyboard, DPad, or left thumbstick, we adjust // the number of active spheres in our scene if (gamePad.IsButtonDown(Buttons.DPadUp) || gamePad.IsButtonDown(Buttons.LeftThumbstickUp) || keyboard.IsKeyDown(Keys.Up)) { activeSphereCount++; } else if (gamePad.IsButtonDown(Buttons.DPadDown) || gamePad.IsButtonDown(Buttons.LeftThumbstickDown) || keyboard.IsKeyDown(Keys.Down)) { activeSphereCount--; } // Poll for all the gestures our game received while (TouchPanel.IsGestureAvailable) { GestureSample gesture = TouchPanel.ReadGesture(); // If the user tapped the screen, toggle whether or not we're colliding the spheres if (gesture.GestureType == GestureType.Tap) { collideSpheres = !collideSpheres; } // If the user dragged on the screen, we adjust the number of active spheres in the scene else if (gesture.GestureType == GestureType.FreeDrag) { activeSphereCount -= Math.Sign(gesture.Delta.Y); } } // Make sure we clamp our active sphere count so that we don't go above our maximum or below 1 activeSphereCount = Math.Max(Math.Min(activeSphereCount, maximumNumberOfSpheres), 1); } /// /// Helper method that updates our sphere simulation. /// private void UpdateSpheres(GameTime gameTime) { // Update all spheres and perform collision against the world for (int i = 0; i < activeSphereCount; i++) { Sphere s = spheres[i]; s.Update(gameTime); BounceSphereInWorld(s); } // If we are colliding spheres against each other if (collideSpheres) { // Iterate over the list twice to compare the spheres against each other for (int i = 0; i < activeSphereCount; i++) { for (int j = 0; j < activeSphereCount; j++) { // Make sure we don't collid a sphere with itself if (i == j) continue; // Get the spheres Sphere a = spheres[i]; Sphere b = spheres[j]; // If the spheres are intersecting if (a.Bounds.Intersects(b.Bounds)) { // Get the vector between their centers Vector3 delta = b.Position - a.Position; // Calculate the point halfway between the spheres Vector3 center = a.Position + delta / 2f; // Normalize the delta vector delta.Normalize(); // Move the spheres to resolve the collision a.Position = center - delta * a.Radius; b.Position = center + delta * b.Radius; // Reflect the velocities to bounce the spheres a.Velocity = Vector3.Normalize(Vector3.Reflect(a.Velocity, delta)) * b.Velocity.Length(); b.Velocity = Vector3.Normalize(Vector3.Reflect(b.Velocity, delta)) * a.Velocity.Length(); } } } } } /// /// Helper method that keeps a sphere in the world by bouncing it off the walls. /// private static void BounceSphereInWorld(Sphere s) { // First test along the X axis, flipping the velocity if a collision occurs. if (s.Position.X < -worldSize + s.Radius) { s.Position.X = -worldSize + s.Radius; if (s.Velocity.X < 0f) s.Velocity.X *= -1f; } else if (s.Position.X > worldSize - s.Radius) { s.Position.X = worldSize - s.Radius; if (s.Velocity.X > 0f) s.Velocity.X *= -1f; } // Then we test the Y axis if (s.Position.Y < s.Radius) { s.Position.Y = s.Radius; if (s.Velocity.Y < 0f) s.Velocity.Y *= -1f; } else if (s.Position.Y > worldSize - s.Radius) { s.Position.Y = worldSize - s.Radius; if (s.Velocity.Y > 0f) s.Velocity.Y *= -1f; } // And lastly the Z axis if (s.Position.Z < -worldSize + s.Radius) { s.Position.Z = -worldSize + s.Radius; if (s.Velocity.Z < 0f) s.Velocity.Z *= -1f; } else if (s.Position.Z > worldSize - s.Radius) { s.Position.Z = worldSize - s.Radius; if (s.Velocity.Z > 0f) s.Velocity.Z *= -1f; } } /// /// This is called when the game should draw itself. /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { // Begin measuring our Draw method DebugSystem.Instance.TimeRuler.BeginMark("Draw", Color.Red); GraphicsDevice.Clear(Color.CornflowerBlue); // Create a view and projection matrix for our camera Matrix view = Matrix.CreateLookAt( new Vector3(worldSize, worldSize, worldSize) * 1.5f, Vector3.Zero, Vector3.Up); Matrix projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.01f, 100f); // Set our sampler state to allow the ground to have a repeated texture GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap; // Draw the ground scaled to our world ground.Draw(Matrix.CreateScale(worldSize, 1f, worldSize), view, projection); // Draw all of our spheres for (int i = 0; i < activeSphereCount; i++) { spheres[i].Draw(view, projection); } // Draw the demo text on top of the scene DrawDemoText(); base.Draw(gameTime); // End measuring our Draw method DebugSystem.Instance.TimeRuler.EndMark("Draw"); } /// /// Helper that draws our demo text, including our current settings and the instructions. /// private void DrawDemoText() { // Create the text based on our current states string demoText = string.Format( "Sphere count: {0}\nCollisions Enabled: {1}\n\n{2}", activeSphereCount, collideSpheres, instructions); // Measure our text and calculate the correct position to draw it Vector2 size = font.MeasureString(demoText); Vector2 pos = new Vector2( GraphicsDevice.Viewport.TitleSafeArea.Right - size.X, GraphicsDevice.Viewport.TitleSafeArea.Top); spriteBatch.Begin(); // Draw a blank box as a background for our text spriteBatch.Draw( blank, new Rectangle((int)pos.X - 5, (int)pos.Y, (int)size.X + 10, (int)size.Y + 5), Color.Black * .5f); // Draw the text itself spriteBatch.DrawString(font, demoText, pos, Color.White); spriteBatch.End(); } } }