diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76ec77d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.user +/obj +/bin diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..162a923 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "FNA"] + path = FNA + url = git://srchub.org/fna-workbench.git diff --git a/.vs/GameStateManagement/v14/.suo b/.vs/GameStateManagement/v14/.suo new file mode 100644 index 0000000..1c131cc Binary files /dev/null and b/.vs/GameStateManagement/v14/.suo differ diff --git a/BackgroundScreen.cs b/BackgroundScreen.cs new file mode 100644 index 0000000..5c1019b --- /dev/null +++ b/BackgroundScreen.cs @@ -0,0 +1,114 @@ +#region File Description +//----------------------------------------------------------------------------- +// BackgroundScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using GameStateManagement; +#endregion + +namespace GameStateManagement +{ + /// + /// The background screen sits behind all the other menu screens. + /// It draws a background image that remains fixed in place regardless + /// of whatever transitions the screens on top of it may be doing. + /// + public class BackgroundScreen : GameScreen + { + #region Fields + + ContentManager content; + Texture2D backgroundTexture; + + #endregion + + #region Initialization + + + /// + /// Constructor. + /// + public BackgroundScreen() + { + TransitionOnTime = TimeSpan.FromSeconds(0.5); + TransitionOffTime = TimeSpan.FromSeconds(0.5); + } + + + /// + /// Loads graphics content for this screen. The background texture is quite + /// big, so we use our own local ContentManager to load it. This allows us + /// to unload before going from the menus into the game itself, wheras if we + /// used the shared ContentManager provided by the Game class, the content + /// would remain loaded forever. + /// + public override void Activate(bool instancePreserved) + { + if (!instancePreserved) + { + if (content == null) + content = new ContentManager(ScreenManager.Game.Services, "Content"); + + backgroundTexture = content.Load("background"); + } + } + + + /// + /// Unloads graphics content for this screen. + /// + public override void Unload() + { + content.Unload(); + } + + + #endregion + + #region Update and Draw + + + /// + /// Updates the background screen. Unlike most screens, this should not + /// transition off even if it has been covered by another screen: it is + /// supposed to be covered, after all! This overload forces the + /// coveredByOtherScreen parameter to false in order to stop the base + /// Update method wanting to transition off. + /// + public override void Update(GameTime gameTime, bool otherScreenHasFocus, + bool coveredByOtherScreen) + { + base.Update(gameTime, otherScreenHasFocus, false); + } + + + /// + /// Draws the background screen. + /// + public override void Draw(GameTime gameTime) + { + SpriteBatch spriteBatch = ScreenManager.SpriteBatch; + Viewport viewport = ScreenManager.GraphicsDevice.Viewport; + Rectangle fullscreen = new Rectangle(0, 0, viewport.Width, viewport.Height); + + spriteBatch.Begin(); + + spriteBatch.Draw(backgroundTexture, fullscreen, + new Color(TransitionAlpha, TransitionAlpha, TransitionAlpha)); + + spriteBatch.End(); + } + + + #endregion + } +} diff --git a/Button.cs b/Button.cs new file mode 100644 index 0000000..35c0130 --- /dev/null +++ b/Button.cs @@ -0,0 +1,191 @@ +#region File Description +//----------------------------------------------------------------------------- +// Button.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System; +using GameStateManagement; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace GameStateManagement +{ + /// + /// A special button that handles toggling between "On" and "Off" + /// + class BooleanButton : Button + { + private string option; + private bool value; + + /// + /// Creates a new BooleanButton. + /// + /// The string text to display for the option. + /// The initial value of the button. + public BooleanButton(string option, bool value) + : base(option) + { + this.option = option; + this.value = value; + + GenerateText(); + } + + protected override void OnTapped() + { + // When tapped we need to toggle the value and regenerate the text + value = !value; + GenerateText(); + + base.OnTapped(); + } + + /// + /// Helper that generates the actual Text value the base class uses for drawing. + /// + private void GenerateText() + { + Text = string.Format("{0}: {1}", option, value ? "On" : "Off"); + } + } + + /// + /// Represents a touchable button. + /// + class Button + { + /// + /// The text displayed in the button. + /// + public string Text = "Button"; + + /// + /// The position of the top-left corner of the button. + /// + public Vector2 Position = Vector2.Zero; + + /// + /// The size of the button. + /// + public Vector2 Size = new Vector2(250, 75); + + /// + /// The thickness of the border drawn for the button. + /// + public int BorderThickness = 4; + + /// + /// The color of the button border. + /// + public Color BorderColor = new Color(200, 200, 200); + + /// + /// The color of the button background. + /// + public Color FillColor = new Color(100, 100, 100) * .75f; + + /// + /// The color of the text. + /// + public Color TextColor = Color.White; + + /// + /// The opacity of the button. + /// + public float Alpha = 0f; + + /// + /// Invoked when the button is tapped. + /// + public event EventHandler Tapped; + + /// + /// Creates a new Button. + /// + /// The text to display in the button. + public Button(string text) + { + Text = text; + } + + /// + /// Invokes the Tapped event and allows subclasses to perform actions when tapped. + /// + protected virtual void OnTapped() + { + if (Tapped != null) + Tapped(this, EventArgs.Empty); + } + + /// + /// Passes a tap location to the button for handling. + /// + /// The location of the tap. + /// True if the button was tapped, false otherwise. + public bool HandleTap(Vector2 tap) + { + if (tap.X >= Position.X && + tap.Y >= Position.Y && + tap.X <= Position.X + Size.X && + tap.Y <= Position.Y + Size.Y) + { + OnTapped(); + return true; + } + + return false; + } + + /// + /// Draws the button + /// + /// The screen drawing the button + public void Draw(GameScreen screen) + { + // Grab some common items from the ScreenManager + SpriteBatch spriteBatch = screen.ScreenManager.SpriteBatch; + SpriteFont font = screen.ScreenManager.Font; + Texture2D blank = screen.ScreenManager.BlankTexture; + + // Compute the button's rectangle + Rectangle r = new Rectangle( + (int)Position.X, + (int)Position.Y, + (int)Size.X, + (int)Size.Y); + + // Fill the button + spriteBatch.Draw(blank, r, FillColor * Alpha); + + // Draw the border + spriteBatch.Draw( + blank, + new Rectangle(r.Left, r.Top, r.Width, BorderThickness), + BorderColor * Alpha); + spriteBatch.Draw( + blank, + new Rectangle(r.Left, r.Top, BorderThickness, r.Height), + BorderColor * Alpha); + spriteBatch.Draw( + blank, + new Rectangle(r.Right - BorderThickness, r.Top, BorderThickness, r.Height), + BorderColor * Alpha); + spriteBatch.Draw( + blank, + new Rectangle(r.Left, r.Bottom - BorderThickness, r.Width, BorderThickness), + BorderColor * Alpha); + + // Draw the text centered in the button + Vector2 textSize = font.MeasureString(Text); + Vector2 textPosition = new Vector2(r.Center.X, r.Center.Y) - textSize / 2f; + textPosition.X = (int)textPosition.X; + textPosition.Y = (int)textPosition.Y; + spriteBatch.DrawString(font, Text, textPosition, TextColor * Alpha); + } + } +} diff --git a/FNA b/FNA new file mode 160000 index 0000000..4e35338 --- /dev/null +++ b/FNA @@ -0,0 +1 @@ +Subproject commit 4e35338d6c3dfb85752b4b94c6ced3223c8b35f5 diff --git a/GameScreen.cs b/GameScreen.cs new file mode 100644 index 0000000..fd7c803 --- /dev/null +++ b/GameScreen.cs @@ -0,0 +1,365 @@ +#region File Description +//----------------------------------------------------------------------------- +// GameScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System; +using System.IO; +using Microsoft.Xna.Framework; +// TODO: Add Touch API to FNA +//using Microsoft.Xna.Framework.Input.Touch; + +namespace GameStateManagement +{ + /// + /// Enum describes the screen transition state. + /// + public enum ScreenState + { + TransitionOn, + Active, + TransitionOff, + Hidden, + } + + + /// + /// A screen is a single layer that has update and draw logic, and which + /// can be combined with other layers to build up a complex menu system. + /// For instance the main menu, the options menu, the "are you sure you + /// want to quit" message box, and the main game itself are all implemented + /// as screens. + /// + public abstract class GameScreen + { + + public GameScreen() + { + HasVirtualStick = false; + HasCursor = false; + } + public bool HasCursor = false; + /// + /// Normally when one screen is brought up over the top of another, + /// the first screen will transition off to make room for the new + /// one. This property indicates whether the screen is only a small + /// popup, in which case screens underneath it do not need to bother + /// transitioning off. + /// + public bool IsPopup + { + get { return isPopup; } + protected set { isPopup = value; } + } + + bool isPopup = false; + public bool HasVirtualStick { get; set; } + + /// + /// Indicates how long the screen takes to + /// transition on when it is activated. + /// + public TimeSpan TransitionOnTime + { + get { return transitionOnTime; } + protected set { transitionOnTime = value; } + } + + TimeSpan transitionOnTime = TimeSpan.Zero; + + + /// + /// Indicates how long the screen takes to + /// transition off when it is deactivated. + /// + public TimeSpan TransitionOffTime + { + get { return transitionOffTime; } + protected set { transitionOffTime = value; } + } + + TimeSpan transitionOffTime = TimeSpan.Zero; + + + /// + /// Gets the current position of the screen transition, ranging + /// from zero (fully active, no transition) to one (transitioned + /// fully off to nothing). + /// + public float TransitionPosition + { + get { return transitionPosition; } + protected set { transitionPosition = value; } + } + + float transitionPosition = 1; + + + /// + /// Gets the current alpha of the screen transition, ranging + /// from 1 (fully active, no transition) to 0 (transitioned + /// fully off to nothing). + /// + public float TransitionAlpha + { + get { return 1f - TransitionPosition; } + } + + + /// + /// Gets the current screen transition state. + /// + public ScreenState ScreenState + { + get { return screenState; } + protected set { screenState = value; } + } + + ScreenState screenState = ScreenState.TransitionOn; + + + /// + /// There are two possible reasons why a screen might be transitioning + /// off. It could be temporarily going away to make room for another + /// screen that is on top of it, or it could be going away for good. + /// This property indicates whether the screen is exiting for real: + /// if set, the screen will automatically remove itself as soon as the + /// transition finishes. + /// + public bool IsExiting + { + get { return isExiting; } + protected internal set { isExiting = value; } + } + + bool isExiting = false; + + + /// + /// Checks whether this screen is active and can respond to user input. + /// + public bool IsActive + { + get + { + return !otherScreenHasFocus && + (screenState == ScreenState.TransitionOn || + screenState == ScreenState.Active); + } + } + + bool otherScreenHasFocus; + + + /// + /// Gets the manager that this screen belongs to. + /// + public ScreenManager ScreenManager + { + get { return screenManager; } + internal set { screenManager = value; } + } + + ScreenManager screenManager; + + + /// + /// Gets the index of the player who is currently controlling this screen, + /// or null if it is accepting input from any player. This is used to lock + /// the game to a specific player profile. The main menu responds to input + /// from any connected gamepad, but whichever player makes a selection from + /// this menu is given control over all subsequent screens, so other gamepads + /// are inactive until the controlling player returns to the main menu. + /// + public PlayerIndex? ControllingPlayer + { + get { return controllingPlayer; } + internal set { controllingPlayer = value; } + } + + PlayerIndex? controllingPlayer; + + + /// + /// Gets the gestures the screen is interested in. Screens should be as specific + /// as possible with gestures to increase the accuracy of the gesture engine. + /// For example, most menus only need Tap or perhaps Tap and VerticalDrag to operate. + /// These gestures are handled by the ScreenManager when screens change and + /// all gestures are placed in the InputState passed to the HandleInput method. + /// + /*public GestureType EnabledGestures + { + get { return enabledGestures; } + protected set + { + enabledGestures = value; + + // the screen manager handles this during screen changes, but + // if this screen is active and the gesture types are changing, + // we have to update the TouchPanel ourself. + if (ScreenState == ScreenState.Active) + { + TouchPanel.EnabledGestures = value; + } + } + } + + GestureType enabledGestures = GestureType.None;*/ + + /// + /// Gets whether or not this screen is serializable. If this is true, + /// the screen will be recorded into the screen manager's state and + /// its Serialize and Deserialize methods will be called as appropriate. + /// If this is false, the screen will be ignored during serialization. + /// By default, all screens are assumed to be serializable. + /// + public bool IsSerializable + { + get { return isSerializable; } + protected set { isSerializable = value; } + } + + bool isSerializable = true; + + + /// + /// Activates the screen. Called when the screen is added to the screen manager or if the game resumes + /// from being paused or tombstoned. + /// + /// + /// True if the game was preserved during deactivation, false if the screen is just being added or if the game was tombstoned. + /// On Xbox and Windows this will always be false. + /// + public virtual void Activate(bool instancePreserved) { } + + + /// + /// Deactivates the screen. Called when the game is being deactivated due to pausing or tombstoning. + /// + public virtual void Deactivate() { } + + + /// + /// Unload content for the screen. Called when the screen is removed from the screen manager. + /// + public virtual void Unload() { } + + + /// + /// Allows the screen to run logic, such as updating the transition position. + /// Unlike HandleInput, this method is called regardless of whether the screen + /// is active, hidden, or in the middle of a transition. + /// + public virtual void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen) + { + this.otherScreenHasFocus = otherScreenHasFocus; + + if (isExiting) + { + // If the screen is going away to die, it should transition off. + screenState = ScreenState.TransitionOff; + + if (!UpdateTransition(gameTime, transitionOffTime, 1)) + { + // When the transition finishes, remove the screen. + ScreenManager.RemoveScreen(this); + } + } + else if (coveredByOtherScreen) + { + // If the screen is covered by another, it should transition off. + if (UpdateTransition(gameTime, transitionOffTime, 1)) + { + // Still busy transitioning. + screenState = ScreenState.TransitionOff; + } + else + { + // Transition finished! + screenState = ScreenState.Hidden; + } + } + else + { + // Otherwise the screen should transition on and become active. + if (UpdateTransition(gameTime, transitionOnTime, -1)) + { + // Still busy transitioning. + screenState = ScreenState.TransitionOn; + } + else + { + // Transition finished! + screenState = ScreenState.Active; + } + } + } + + + /// + /// Helper for updating the screen transition position. + /// + bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction) + { + // How much should we move by? + float transitionDelta; + + if (time == TimeSpan.Zero) + transitionDelta = 1; + else + transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / time.TotalMilliseconds); + + // Update the transition position. + transitionPosition += transitionDelta * direction; + + // Did we reach the end of the transition? + if (((direction < 0) && (transitionPosition <= 0)) || + ((direction > 0) && (transitionPosition >= 1))) + { + transitionPosition = MathHelper.Clamp(transitionPosition, 0, 1); + return false; + } + + // Otherwise we are still busy transitioning. + return true; + } + + + /// + /// Allows the screen to handle user input. Unlike Update, this method + /// is only called when the screen is active, and not when some other + /// screen has taken the focus. + /// + public virtual void HandleInput(GameTime gameTime, InputState input) { } + + + /// + /// This is called when the screen should draw itself. + /// + public virtual void Draw(GameTime gameTime) { } + + + /// + /// Tells the screen to go away. Unlike ScreenManager.RemoveScreen, which + /// instantly kills the screen, this method respects the transition timings + /// and will give the screen a chance to gradually transition off. + /// + public void ExitScreen() + { + if (TransitionOffTime == TimeSpan.Zero) + { + // If the screen has a zero transition time, remove it immediately. + ScreenManager.RemoveScreen(this); + } + else + { + // Otherwise flag that it should transition off and then exit. + isExiting = true; + } + } + } +} diff --git a/GameStateManagement.csproj b/GameStateManagement.csproj new file mode 100644 index 0000000..d0d6f56 --- /dev/null +++ b/GameStateManagement.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7} + Library + Properties + GameStateManagement + GameStateManagement + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {35253ce1-c864-4cd3-8249-4d1319748e8f} + FNA + + + + + \ No newline at end of file diff --git a/GameStateManagement.sln b/GameStateManagement.sln new file mode 100644 index 0000000..b240261 --- /dev/null +++ b/GameStateManagement.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameStateManagement", "GameStateManagement.csproj", "{A1A96363-C163-4A2A-8F31-D84D80C4C0D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Debug|x86.Build.0 = Debug|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Release|Any CPU.Build.0 = Release|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Release|x86.ActiveCfg = Release|Any CPU + {A1A96363-C163-4A2A-8F31-D84D80C4C0D7}.Release|x86.Build.0 = Release|Any CPU + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.ActiveCfg = Debug|x86 + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|x86.ActiveCfg = Debug|x86 + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|x86.Build.0 = Debug|x86 + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.ActiveCfg = Release|x86 + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|x86.ActiveCfg = Release|x86 + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/GameplayScreen.cs b/GameplayScreen.cs new file mode 100644 index 0000000..363eb5f --- /dev/null +++ b/GameplayScreen.cs @@ -0,0 +1,265 @@ +#region File Description +//----------------------------------------------------------------------------- +// GameplayScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using System.Threading; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using GameStateManagement; +#endregion + +namespace GameStateManagementSample +{ + /// + /// This screen implements the actual game logic. It is just a + /// placeholder to get the idea across: you'll probably want to + /// put some more interesting gameplay in here! + /// + class GameplayScreen : GameScreen + { + #region Fields + + ContentManager content; + SpriteFont gameFont; + + Vector2 playerPosition = new Vector2(100, 100); + Vector2 enemyPosition = new Vector2(100, 100); + + Random random = new Random(); + + float pauseAlpha; + + InputAction pauseAction; + + #endregion + + #region Initialization + + + /// + /// Constructor. + /// + public GameplayScreen() + { + TransitionOnTime = TimeSpan.FromSeconds(1.5); + TransitionOffTime = TimeSpan.FromSeconds(0.5); + + pauseAction = new InputAction( + new Buttons[] { Buttons.Start, Buttons.Back }, + new Keys[] { Keys.Escape }, + true); + } + + + /// + /// Load graphics content for the game. + /// + public override void Activate(bool instancePreserved) + { + if (!instancePreserved) + { + if (content == null) + content = new ContentManager(ScreenManager.Game.Services, "Content"); + + gameFont = content.Load("gamefont"); + + // A real game would probably have more content than this sample, so + // it would take longer to load. We simulate that by delaying for a + // while, giving you a chance to admire the beautiful loading screen. + Thread.Sleep(1000); + + // once the load has finished, we use ResetElapsedTime to tell the game's + // timing mechanism that we have just finished a very long frame, and that + // it should not try to catch up. + ScreenManager.Game.ResetElapsedTime(); + } + +#if WINDOWS_PHONE + if (Microsoft.Phone.Shell.PhoneApplicationService.Current.State.ContainsKey("PlayerPosition")) + { + playerPosition = (Vector2)Microsoft.Phone.Shell.PhoneApplicationService.Current.State["PlayerPosition"]; + enemyPosition = (Vector2)Microsoft.Phone.Shell.PhoneApplicationService.Current.State["EnemyPosition"]; + } +#endif + } + + + public override void Deactivate() + { +#if WINDOWS_PHONE + Microsoft.Phone.Shell.PhoneApplicationService.Current.State["PlayerPosition"] = playerPosition; + Microsoft.Phone.Shell.PhoneApplicationService.Current.State["EnemyPosition"] = enemyPosition; +#endif + + base.Deactivate(); + } + + + /// + /// Unload graphics content used by the game. + /// + public override void Unload() + { + content.Unload(); + +#if WINDOWS_PHONE + Microsoft.Phone.Shell.PhoneApplicationService.Current.State.Remove("PlayerPosition"); + Microsoft.Phone.Shell.PhoneApplicationService.Current.State.Remove("EnemyPosition"); +#endif + } + + + #endregion + + #region Update and Draw + + + /// + /// Updates the state of the game. This method checks the GameScreen.IsActive + /// property, so the game will stop updating when the pause menu is active, + /// or if you tab away to a different application. + /// + public override void Update(GameTime gameTime, bool otherScreenHasFocus, + bool coveredByOtherScreen) + { + base.Update(gameTime, otherScreenHasFocus, false); + + // Gradually fade in or out depending on whether we are covered by the pause screen. + if (coveredByOtherScreen) + pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1); + else + pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0); + + if (IsActive) + { + // Apply some random jitter to make the enemy move around. + const float randomization = 10; + + enemyPosition.X += (float)(random.NextDouble() - 0.5) * randomization; + enemyPosition.Y += (float)(random.NextDouble() - 0.5) * randomization; + + // Apply a stabilizing force to stop the enemy moving off the screen. + Vector2 targetPosition = new Vector2( + ScreenManager.GraphicsDevice.Viewport.Width / 2 - gameFont.MeasureString("Insert Gameplay Here").X / 2, + 200); + + enemyPosition = Vector2.Lerp(enemyPosition, targetPosition, 0.05f); + + // TODO: this game isn't very fun! You could probably improve + // it by inserting something more interesting in this space :-) + } + } + + + /// + /// Lets the game respond to player input. Unlike the Update method, + /// this will only be called when the gameplay screen is active. + /// + public override void HandleInput(GameTime gameTime, InputState input) + { + if (input == null) + throw new ArgumentNullException("input"); + + // Look up inputs for the active player profile. + int playerIndex = (int)ControllingPlayer.Value; + + KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex]; + GamePadState gamePadState = input.CurrentGamePadStates[playerIndex]; + + // The game pauses either if the user presses the pause button, or if + // they unplug the active gamepad. This requires us to keep track of + // whether a gamepad was ever plugged in, because we don't want to pause + // on PC if they are playing with a keyboard and have no gamepad at all! + bool gamePadDisconnected = !gamePadState.IsConnected && + input.GamePadWasConnected[playerIndex]; + + PlayerIndex player; + if (pauseAction.Evaluate(input, ControllingPlayer, out player) || gamePadDisconnected) + { +#if WINDOWS_PHONE + ScreenManager.AddScreen(new PhonePauseScreen(), ControllingPlayer); +#else + ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer); +#endif + } + else + { + // Otherwise move the player position. + Vector2 movement = Vector2.Zero; + + if (keyboardState.IsKeyDown(Keys.Left)) + movement.X--; + + if (keyboardState.IsKeyDown(Keys.Right)) + movement.X++; + + if (keyboardState.IsKeyDown(Keys.Up)) + movement.Y--; + + if (keyboardState.IsKeyDown(Keys.Down)) + movement.Y++; + + Vector2 thumbstick = gamePadState.ThumbSticks.Left; + + movement.X += thumbstick.X; + movement.Y -= thumbstick.Y; + + /*if (input.TouchState.Count > 0) + { + Vector2 touchPosition = input.TouchState[0].Position; + Vector2 direction = touchPosition - playerPosition; + direction.Normalize(); + movement += direction; + }*/ + + if (movement.Length() > 1) + movement.Normalize(); + + playerPosition += movement * 8f; + } + } + + + /// + /// Draws the gameplay screen. + /// + public override void Draw(GameTime gameTime) + { + // This game has a blue background. Why? Because! + ScreenManager.GraphicsDevice.Clear(ClearOptions.Target, + Color.CornflowerBlue, 0, 0); + + // Our player and enemy are both actually just text strings. + SpriteBatch spriteBatch = ScreenManager.SpriteBatch; + + spriteBatch.Begin(); + + spriteBatch.DrawString(gameFont, "// TODO", playerPosition, Color.Green); + + spriteBatch.DrawString(gameFont, "Insert Gameplay Here", + enemyPosition, Color.DarkRed); + + spriteBatch.End(); + + // If the game is transitioning on or off, fade it out to black. + if (TransitionPosition > 0 || pauseAlpha > 0) + { + float alpha = MathHelper.Lerp(1f - TransitionAlpha, 1f, pauseAlpha / 2); + + ScreenManager.FadeBackBufferToBlack(alpha); + } + } + + + #endregion + } +} diff --git a/IScreenFactory.cs b/IScreenFactory.cs new file mode 100644 index 0000000..f9fb2b6 --- /dev/null +++ b/IScreenFactory.cs @@ -0,0 +1,49 @@ +#region File Description +//----------------------------------------------------------------------------- +// IScreenFactory.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System; + +namespace GameStateManagement +{ + /// + /// Defines an object that can create a screen when given its type. + /// + /// The ScreenManager attempts to handle tombstoning on Windows Phone by creating an XML + /// document that has a list of the screens currently in the manager. When the game is + /// reactivated, the ScreenManager needs to create instances of those screens. However + /// since there is no restriction that a particular GameScreen subclass has a parameterless + /// constructor, there is no way the ScreenManager alone could create those instances. + /// + /// IScreenFactory fills this gap by providing an interface the game should implement to + /// act as a translation from type to instance. The ScreenManager locates the IScreenFactory + /// from the Game.Services collection and passes each screen type to the factory, expecting + /// to get the correct GameScreen out. + /// + /// If your game screens all have parameterless constructors, the minimal implementation of + /// this interface would look like this: + /// + /// return Activator.CreateInstance(screenType) as GameScreen; + /// + /// If you have screens with constructors that take arguments, you will need to ensure that + /// you can read these arguments from storage or generate new ones, then construct the screen + /// based on the type. + /// + /// The ScreenFactory type in the sample game has the minimal implementation along with some + /// extra comments showing a potentially more complex example of how to implement IScreenFactory. + /// + public interface IScreenFactory + { + /// + /// Creates a GameScreen from the given type. + /// + /// The type of screen to create. + /// The newly created screen. + GameScreen CreateScreen(Type screenType); + } +} diff --git a/InputAction.cs b/InputAction.cs new file mode 100644 index 0000000..302c051 --- /dev/null +++ b/InputAction.cs @@ -0,0 +1,96 @@ +#region File Description +//----------------------------------------------------------------------------- +// InputAction.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace GameStateManagement +{ + /// + /// Defines an action that is designated by some set of buttons and/or keys. + /// + /// The way actions work is that you define a set of buttons and keys that trigger the action. You can + /// then evaluate the action against an InputState which will test to see if any of the buttons or keys + /// are pressed by a player. You can also set a flag that indicates if the action only occurs once when + /// the buttons/keys are first pressed or whether the action should occur each frame. + /// + /// Using this InputAction class means that you can configure new actions based on keys and buttons + /// without having to directly modify the InputState type. This means more customization by your games + /// without having to change the core classes of Game State Management. + /// + public class InputAction + { + private readonly Buttons[] buttons; + private readonly Keys[] keys; + private readonly bool newPressOnly; + + // These delegate types map to the methods on InputState. We use these to simplify the evalute method + // by allowing us to map the appropriate delegates and invoke them, rather than having two separate code paths. + private delegate bool ButtonPress(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex player); + private delegate bool KeyPress(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex player); + + /// + /// Initializes a new InputAction. + /// + /// An array of buttons that can trigger the action. + /// An array of keys that can trigger the action. + /// Whether the action only occurs on the first press of one of the buttons/keys, + /// false if it occurs each frame one of the buttons/keys is down. + public InputAction(Buttons[] buttons, Keys[] keys, bool newPressOnly) + { + // Store the buttons and keys. If the arrays are null, we create a 0 length array so we don't + // have to do null checks in the Evaluate method + this.buttons = buttons != null ? buttons.Clone() as Buttons[] : new Buttons[0]; + this.keys = keys != null ? keys.Clone() as Keys[] : new Keys[0]; + + this.newPressOnly = newPressOnly; + } + + /// + /// Evaluates the action against a given InputState. + /// + /// The InputState to test for the action. + /// The player to test, or null to allow any player. + /// If controllingPlayer is null, this is the player that performed the action. + /// True if the action occurred, false otherwise. + public bool Evaluate(InputState state, PlayerIndex? controllingPlayer, out PlayerIndex player) + { + // Figure out which delegate methods to map from the state which takes care of our "newPressOnly" logic + ButtonPress buttonTest; + KeyPress keyTest; + if (newPressOnly) + { + buttonTest = state.IsNewButtonPress; + keyTest = state.IsNewKeyPress; + } + else + { + buttonTest = state.IsButtonPressed; + keyTest = state.IsKeyPressed; + } + + // Now we simply need to invoke the appropriate methods for each button and key in our collections + foreach (Buttons button in buttons) + { + if (buttonTest(button, controllingPlayer, out player)) + return true; + } + foreach (Keys key in keys) + { + if (keyTest(key, controllingPlayer, out player)) + return true; + } + + // If we got here, the action is not matched + player = PlayerIndex.One; + return false; + } + } +} diff --git a/InputState.cs b/InputState.cs new file mode 100644 index 0000000..8c9f800 --- /dev/null +++ b/InputState.cs @@ -0,0 +1,608 @@ +#region File Description +//----------------------------------------------------------------------------- +// InputState.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Input; +//using Microsoft.Xna.Framework.Input.Touch; +//using FarseerPhysics.SamplesFramework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Linq; + + +namespace GameStateManagement +{ + + /// + /// an enum of all available mouse buttons. + /// + public enum MouseButtons + { + LeftButton, + MiddleButton, + RightButton, + ExtraButton1, + ExtraButton2 + } + + /// + /// Helper for reading input from keyboard, gamepad, and touch input. This class + /// tracks both the current and previous state of the input devices, and implements + /// query methods for high level input actions such as "move up through the menu" + /// or "pause the game". + /// + public class InputState + { + public const int MaxInputs = 4; + + public readonly KeyboardState[] CurrentKeyboardStates; + public readonly GamePadState[] CurrentGamePadStates; + + public readonly KeyboardState[] LastKeyboardStates; + public readonly GamePadState[] LastGamePadStates; + + public readonly bool[] GamePadWasConnected; + + /* + * Needed for virtual stick on WP7 + * -- Nathan Adams [adamsna@datanethost.net] - 4/12/2012 + */ + private GamePadState _currentVirtualState; + private GamePadState _lastVirtualState; + private bool _handleVirtualStick; + /* + * I didn't create an array for the virtual stick because there will only be one + * -- Nathan Adams [adamsna@datanethost.net] - 4/12/2012 + */ + + + /* + * Adding variables for the cursor + * -- Nathan Adams [adamsna@datanethost.net] - 4/15/2012 + * + */ + private MouseState _currentMouseState; + private MouseState _lastMouseState; + + private Vector2 _cursor; + private bool _cursorIsValid; + private bool _cursorIsVisible; + private bool _cursorMoved; + private Texture2D _cursorSprite; + +#if WINDOWS_PHONE + private VirtualStick _phoneStick; + private VirtualButton _phoneA; + private VirtualButton _phoneB; +#endif + + //public TouchCollection TouchState; + + //public readonly List Gestures = new List(); + + private ScreenManager _manager; + private Viewport _viewport; + + + /// + /// Constructs a new input state. + /// + public InputState(ScreenManager manager) + { + _manager = manager; + CurrentKeyboardStates = new KeyboardState[MaxInputs]; + CurrentGamePadStates = new GamePadState[MaxInputs]; + + LastKeyboardStates = new KeyboardState[MaxInputs]; + LastGamePadStates = new GamePadState[MaxInputs]; + + GamePadWasConnected = new bool[MaxInputs]; + _currentVirtualState = new GamePadState(); + _lastVirtualState = new GamePadState(); + + _cursorIsVisible = false; + _cursorMoved = false; +#if WINDOWS_PHONE + _cursorIsValid = false; +#else + _cursorIsValid = true; +#endif + _cursor = Vector2.Zero; + + _handleVirtualStick = false; + } + + public MouseState MouseState + { + get { return _currentMouseState; } + } + + public GamePadState VirtualState + { + get { return _currentVirtualState; } + } + + public MouseState PreviousMouseState + { + get { return _lastMouseState; } + } + + public GamePadState PreviousVirtualState + { + get { return _lastVirtualState; } + } + + public bool ShowCursor + { + get { return _cursorIsVisible && _cursorIsValid; } + set { _cursorIsVisible = value; } + } + + public bool EnableVirtualStick + { + get { return _handleVirtualStick; } + set { _handleVirtualStick = value; } + } + + public Vector2 Cursor + { + get { return _cursor; } + } + + public bool IsCursorMoved + { + get { return _cursorMoved; } + } + + public bool IsCursorValid + { + get { return _cursorIsValid; } + } + + public void LoadContent() + { + ContentManager man = new ContentManager(_manager.Game.Services, "Content"); + //_cursorSprite = new Sprite(man.Load("Common/cursor")); + _cursorSprite = man.Load("Common/cursor"); +#if WINDOWS_PHONE + // virtual stick content + _phoneStick = new VirtualStick(man.Load("Common/socket"), + man.Load("Common/stick"), new Vector2(80f, 400f)); + + Texture2D temp = man.Load("Common/buttons"); + _phoneA = new VirtualButton(temp, new Vector2(695f, 380f), new Rectangle(0, 0, 40, 40), new Rectangle(0, 40, 40, 40)); + _phoneB = new VirtualButton(temp, new Vector2(745f, 360f), new Rectangle(40, 0, 40, 40), new Rectangle(40, 40, 40, 40)); +#endif + _viewport = _manager.GraphicsDevice.Viewport; + } + + private GamePadState HandleVirtualStickWin() + { + Vector2 _leftStick = Vector2.Zero; + List _buttons = new List(); + PlayerIndex pout; + if (IsNewKeyPress(Keys.A, PlayerIndex.One, out pout)) + { + _leftStick.X -= 1f; + } + if (IsNewKeyPress(Keys.S, PlayerIndex.One, out pout)) + { + _leftStick.Y -= 1f; + } + if (IsNewKeyPress(Keys.D, PlayerIndex.One, out pout)) + { + _leftStick.X += 1f; + } + if (IsNewKeyPress(Keys.W, PlayerIndex.One, out pout)) + { + _leftStick.Y += 1f; + } + if (IsNewKeyPress(Keys.Space, PlayerIndex.One, out pout)) + { + _buttons.Add(Buttons.A); + } + if (IsNewKeyPress(Keys.LeftControl, PlayerIndex.One, out pout)) + { + _buttons.Add(Buttons.B); + } + if (_leftStick != Vector2.Zero) + { + _leftStick.Normalize(); + } + + return new GamePadState(_leftStick, Vector2.Zero, 0f, 0f, _buttons.ToArray()); + } + + private GamePadState HandleVirtualStickWP7() + { + List _buttons = new List(); + Vector2 _stick = Vector2.Zero; +#if WINDOWS_PHONE + _phoneA.Pressed = false; + _phoneB.Pressed = false; + TouchCollection touchLocations = TouchPanel.GetState(); + foreach (TouchLocation touchLocation in touchLocations) + { + _phoneA.Update(touchLocation); + _phoneB.Update(touchLocation); + _phoneStick.Update(touchLocation); + } + if (_phoneA.Pressed) + { + _buttons.Add(Buttons.A); + } + if (_phoneB.Pressed) + { + _buttons.Add(Buttons.B); + } + _stick = _phoneStick.StickPosition; +#endif + return new GamePadState(_stick, Vector2.Zero, 0f, 0f, _buttons.ToArray()); + } + + public void Draw() + { + if (_cursorIsVisible && _cursorIsValid) + { + _manager.SpriteBatch.Begin(); + _manager.SpriteBatch.Draw(_cursorSprite, _cursor, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f); + _manager.SpriteBatch.End(); + } +#if WINDOWS_PHONE + if (_handleVirtualStick) + { + _manager.SpriteBatch.Begin(); + _phoneA.Draw(_manager.SpriteBatch); + _phoneB.Draw(_manager.SpriteBatch); + _phoneStick.Draw(_manager.SpriteBatch); + _manager.SpriteBatch.End(); + } +#endif + } + + /// + /// Reads the latest state user input. + /// + public void Update(GameTime gameTime) + { + _lastMouseState = _currentMouseState; + if (_handleVirtualStick) + { + _lastVirtualState = _currentVirtualState; + } + + _currentMouseState = Mouse.GetState(); + + if (_handleVirtualStick) + { +#if XBOX + _currentVirtualState= GamePad.GetState(PlayerIndex.One); +#elif WINDOWS + if (GamePad.GetState(PlayerIndex.One).IsConnected) + { + _currentVirtualState = GamePad.GetState(PlayerIndex.One); + } + else + { + _currentVirtualState = HandleVirtualStickWin(); + } +#elif WINDOWS_PHONE + _currentVirtualState = HandleVirtualStickWP7(); +#endif + } + for (int i = 0; i < MaxInputs; i++) + { + LastKeyboardStates[i] = CurrentKeyboardStates[i]; + LastGamePadStates[i] = CurrentGamePadStates[i]; + + CurrentKeyboardStates[i] = Keyboard.GetState(); + CurrentGamePadStates[i] = GamePad.GetState((PlayerIndex)i); + + // Keep track of whether a gamepad has ever been + // connected, so we can detect if it is unplugged. + if (CurrentGamePadStates[i].IsConnected) + { + GamePadWasConnected[i] = true; + } + } + + // Get the raw touch state from the TouchPanel + //TouchState = TouchPanel.GetState(); + + // Read in any detected gestures into our list for the screens to later process + //Gestures.Clear(); + /*while (TouchPanel.IsGestureAvailable) + { + //System.Diagnostics.Debugger.Break(); + Gestures.Add(TouchPanel.ReadGesture()); + }*/ + //System.Diagnostics.Debugger.Break(); + + // Update cursor + Vector2 oldCursor = _cursor; + + if (CurrentGamePadStates[0].IsConnected && CurrentGamePadStates[0].ThumbSticks.Left != Vector2.Zero) + { + Vector2 temp = CurrentGamePadStates[0].ThumbSticks.Left; + _cursor += temp * new Vector2(300f, -300f) * (float)gameTime.ElapsedGameTime.TotalSeconds; + Mouse.SetPosition((int)_cursor.X, (int)_cursor.Y); + } + else + { + _cursor.X = _currentMouseState.X; + _cursor.Y = _currentMouseState.Y; + } + + //if (this.IsNewKeyPress(Keys.P, PlayerIndex.One, out p)) + // Console.WriteLine(_cursor.ToString()); + + _cursor.X = MathHelper.Clamp(_cursor.X, 0f, _viewport.Width); + _cursor.Y = MathHelper.Clamp(_cursor.Y, 0f, _viewport.Height); + + //if (this.IsNewKeyPress(Keys.P, PlayerIndex.One, out p)) + // Console.WriteLine(_cursor.ToString()); + + if (_cursorIsValid && oldCursor != _cursor) + { + _cursorMoved = true; + } + else + { + _cursorMoved = false; + } + +#if WINDOWS + if (_viewport.Bounds.Contains(_currentMouseState.X, _currentMouseState.Y)) + { + _cursorIsValid = true; + } + else + { + _cursorIsValid = false; + } +#elif WINDOWS_PHONE + if (_currentMouseState.LeftButton == ButtonState.Pressed) + { + _cursorIsValid = true; + } + else + { + _cursorIsValid = false; + } +#endif + + //if (this.IsNewKeyPress(Keys.P, PlayerIndex.One, out p)) + // Console.WriteLine(_viewport.ToString()); + } + + + /// + /// Helper for checking if a key was pressed during this update. The + /// controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a keypress + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsKeyPressed(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return CurrentKeyboardStates[i].IsKeyDown(key); + } + else + { + // Accept input from any player. + return (IsKeyPressed(key, PlayerIndex.One, out playerIndex) || + IsKeyPressed(key, PlayerIndex.Two, out playerIndex) || + IsKeyPressed(key, PlayerIndex.Three, out playerIndex) || + IsKeyPressed(key, PlayerIndex.Four, out playerIndex)); + } + } + + /// + /// Helper for checking if a button was pressed during this update. + /// The controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a button press + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsButtonPressed(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return CurrentGamePadStates[i].IsButtonDown(button); + } + else + { + // Accept input from any player. + return (IsButtonPressed(button, PlayerIndex.One, out playerIndex) || + IsButtonPressed(button, PlayerIndex.Two, out playerIndex) || + IsButtonPressed(button, PlayerIndex.Three, out playerIndex) || + IsButtonPressed(button, PlayerIndex.Four, out playerIndex)); + } + } + + /// + /// Helper for checking if a key was newly pressed during this update. The + /// controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a keypress + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentKeyboardStates[i].IsKeyDown(key) && + LastKeyboardStates[i].IsKeyUp(key)); + } + else + { + // Accept input from any player. + return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) || + IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) || + IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) || + IsNewKeyPress(key, PlayerIndex.Four, out playerIndex)); + } + } + + public bool IsNewKeyRelease(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentKeyboardStates[i].IsKeyUp(key) && + LastKeyboardStates[i].IsKeyDown(key)); + } + else + { + // Accept input from any player. + return (IsNewKeyRelease(key, PlayerIndex.One, out playerIndex) || + IsNewKeyRelease(key, PlayerIndex.Two, out playerIndex) || + IsNewKeyRelease(key, PlayerIndex.Three, out playerIndex) || + IsNewKeyRelease(key, PlayerIndex.Four, out playerIndex)); + } + } + + /// + /// Helper for checking if a button was newly pressed during this update. + /// The controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a button press + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentGamePadStates[i].IsButtonDown(button) && + LastGamePadStates[i].IsButtonUp(button)); + } + else + { + // Accept input from any player. + return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) || + IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) || + IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) || + IsNewButtonPress(button, PlayerIndex.Four, out playerIndex)); + } + } + + public bool IsNewButtonRelease(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentGamePadStates[i].IsButtonUp(button) && + LastGamePadStates[i].IsButtonDown(button)); + } + else + { + // Accept input from any player. + return (IsNewButtonRelease(button, PlayerIndex.One, out playerIndex) || + IsNewButtonRelease(button, PlayerIndex.Two, out playerIndex) || + IsNewButtonRelease(button, PlayerIndex.Three, out playerIndex) || + IsNewButtonRelease(button, PlayerIndex.Four, out playerIndex)); + } + } + + public bool IsNewVirtualButtonPress(Buttons button) + { + return (_lastVirtualState.IsButtonUp(button) && + _currentVirtualState.IsButtonDown(button)); + } + + public bool IsNewVirtualButtonRelease(Buttons button) + { + return (_lastVirtualState.IsButtonDown(button) && + _currentVirtualState.IsButtonUp(button)); + } + + /// + /// Helper for checking if a mouse button was newly pressed during this update. + /// + public bool IsNewMouseButtonPress(MouseButtons button) + { + switch (button) + { + case MouseButtons.LeftButton: + return (_currentMouseState.LeftButton == ButtonState.Pressed && + _lastMouseState.LeftButton == ButtonState.Released); + case MouseButtons.RightButton: + return (_currentMouseState.RightButton == ButtonState.Pressed && + _lastMouseState.RightButton == ButtonState.Released); + case MouseButtons.MiddleButton: + return (_currentMouseState.MiddleButton == ButtonState.Pressed && + _lastMouseState.MiddleButton == ButtonState.Released); + case MouseButtons.ExtraButton1: + return (_currentMouseState.XButton1 == ButtonState.Pressed && + _lastMouseState.XButton1 == ButtonState.Released); + case MouseButtons.ExtraButton2: + return (_currentMouseState.XButton2 == ButtonState.Pressed && + _lastMouseState.XButton2 == ButtonState.Released); + default: + return false; + } + } + + /// + /// Checks if the requested mouse button is released. + /// + /// The button. + public bool IsNewMouseButtonRelease(MouseButtons button) + { + switch (button) + { + case MouseButtons.LeftButton: + return (_lastMouseState.LeftButton == ButtonState.Pressed && + _currentMouseState.LeftButton == ButtonState.Released); + case MouseButtons.RightButton: + return (_lastMouseState.RightButton == ButtonState.Pressed && + _currentMouseState.RightButton == ButtonState.Released); + case MouseButtons.MiddleButton: + return (_lastMouseState.MiddleButton == ButtonState.Pressed && + _currentMouseState.MiddleButton == ButtonState.Released); + case MouseButtons.ExtraButton1: + return (_lastMouseState.XButton1 == ButtonState.Pressed && + _currentMouseState.XButton1 == ButtonState.Released); + case MouseButtons.ExtraButton2: + return (_lastMouseState.XButton2 == ButtonState.Pressed && + _currentMouseState.XButton2 == ButtonState.Released); + default: + return false; + } + } + } +} diff --git a/LoadingScreen.cs b/LoadingScreen.cs new file mode 100644 index 0000000..bd0e162 --- /dev/null +++ b/LoadingScreen.cs @@ -0,0 +1,163 @@ +#region File Description +//----------------------------------------------------------------------------- +// LoadingScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using GameStateManagement; +#endregion + +namespace GameStateManagement +{ + /// + /// The loading screen coordinates transitions between the menu system and the + /// game itself. Normally one screen will transition off at the same time as + /// the next screen is transitioning on, but for larger transitions that can + /// take a longer time to load their data, we want the menu system to be entirely + /// gone before we start loading the game. This is done as follows: + /// + /// - Tell all the existing screens to transition off. + /// - Activate a loading screen, which will transition on at the same time. + /// - The loading screen watches the state of the previous screens. + /// - When it sees they have finished transitioning off, it activates the real + /// next screen, which may take a long time to load its data. The loading + /// screen will be the only thing displayed while this load is taking place. + /// + class LoadingScreen : GameScreen + { + #region Fields + + bool loadingIsSlow; + bool otherScreensAreGone; + + GameScreen[] screensToLoad; + + #endregion + + #region Initialization + + + /// + /// The constructor is private: loading screens should + /// be activated via the static Load method instead. + /// + private LoadingScreen(ScreenManager screenManager, bool loadingIsSlow, + GameScreen[] screensToLoad) + { + this.loadingIsSlow = loadingIsSlow; + this.screensToLoad = screensToLoad; + + TransitionOnTime = TimeSpan.FromSeconds(0.5); + } + + + /// + /// Activates the loading screen. + /// + public static void Load(ScreenManager screenManager, bool loadingIsSlow, + PlayerIndex? controllingPlayer, + params GameScreen[] screensToLoad) + { + // Tell all the current screens to transition off. + foreach (GameScreen screen in screenManager.GetScreens()) + screen.ExitScreen(); + + // Create and activate the loading screen. + LoadingScreen loadingScreen = new LoadingScreen(screenManager, + loadingIsSlow, + screensToLoad); + + screenManager.AddScreen(loadingScreen, controllingPlayer); + } + + + #endregion + + #region Update and Draw + + + /// + /// Updates the loading screen. + /// + public override void Update(GameTime gameTime, bool otherScreenHasFocus, + bool coveredByOtherScreen) + { + base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + + // If all the previous screens have finished transitioning + // off, it is time to actually perform the load. + if (otherScreensAreGone) + { + ScreenManager.RemoveScreen(this); + + foreach (GameScreen screen in screensToLoad) + { + if (screen != null) + { + ScreenManager.AddScreen(screen, ControllingPlayer); + } + } + + // Once the load has finished, we use ResetElapsedTime to tell + // the game timing mechanism that we have just finished a very + // long frame, and that it should not try to catch up. + ScreenManager.Game.ResetElapsedTime(); + } + } + + + /// + /// Draws the loading screen. + /// + public override void Draw(GameTime gameTime) + { + // If we are the only active screen, that means all the previous screens + // must have finished transitioning off. We check for this in the Draw + // method, rather than in Update, because it isn't enough just for the + // screens to be gone: in order for the transition to look good we must + // have actually drawn a frame without them before we perform the load. + if ((ScreenState == ScreenState.Active) && + (ScreenManager.GetScreens().Length == 1)) + { + otherScreensAreGone = true; + } + + // The gameplay screen takes a while to load, so we display a loading + // message while that is going on, but the menus load very quickly, and + // it would look silly if we flashed this up for just a fraction of a + // second while returning from the game to the menus. This parameter + // tells us how long the loading is going to take, so we know whether + // to bother drawing the message. + if (loadingIsSlow) + { + SpriteBatch spriteBatch = ScreenManager.SpriteBatch; + SpriteFont font = ScreenManager.Font; + + const string message = "Loading..."; + + // Center the text in the viewport. + Viewport viewport = ScreenManager.GraphicsDevice.Viewport; + Vector2 viewportSize = new Vector2(viewport.Width, viewport.Height); + Vector2 textSize = font.MeasureString(message); + Vector2 textPosition = (viewportSize - textSize) / 2; + + Color color = Color.White * TransitionAlpha; + + // Draw the text. + spriteBatch.Begin(); + spriteBatch.DrawString(font, message, textPosition, color); + spriteBatch.End(); + } + } + + + #endregion + } +} diff --git a/MainMenuScreen.cs b/MainMenuScreen.cs new file mode 100644 index 0000000..8d6e035 --- /dev/null +++ b/MainMenuScreen.cs @@ -0,0 +1,99 @@ +#region File Description +//----------------------------------------------------------------------------- +// MainMenuScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using Microsoft.Xna.Framework; +using GameStateManagementSample; +#endregion + +namespace GameStateManagement +{ + /// + /// The main menu screen is the first thing displayed when the game starts up. + /// + class MainMenuScreen : MenuScreen + { + #region Initialization + + + /// + /// Constructor fills in the menu contents. + /// + public MainMenuScreen() + : base("Main Menu") + { + // Create our menu entries. + MenuEntry playGameMenuEntry = new MenuEntry("Play Game"); + MenuEntry optionsMenuEntry = new MenuEntry("Options"); + MenuEntry exitMenuEntry = new MenuEntry("Exit"); + + // Hook up menu event handlers. + playGameMenuEntry.Selected += PlayGameMenuEntrySelected; + optionsMenuEntry.Selected += OptionsMenuEntrySelected; + exitMenuEntry.Selected += OnCancel; + + // Add entries to the menu. + MenuEntries.Add(playGameMenuEntry); + MenuEntries.Add(optionsMenuEntry); + MenuEntries.Add(exitMenuEntry); + } + + + #endregion + + #region Handle Input + + + /// + /// Event handler for when the Play Game menu entry is selected. + /// + void PlayGameMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + LoadingScreen.Load(ScreenManager, true, e.PlayerIndex, + new GameplayScreen()); + } + + + /// + /// Event handler for when the Options menu entry is selected. + /// + void OptionsMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + ScreenManager.AddScreen(new OptionsMenuScreen(), e.PlayerIndex); + } + + + /// + /// When the user cancels the main menu, ask if they want to exit the sample. + /// + protected override void OnCancel(PlayerIndex playerIndex) + { + const string message = "Are you sure you want to exit this sample?"; + + MessageBoxScreen confirmExitMessageBox = new MessageBoxScreen(message); + + confirmExitMessageBox.Accepted += ConfirmExitMessageBoxAccepted; + + ScreenManager.AddScreen(confirmExitMessageBox, playerIndex); + } + + + /// + /// Event handler for when the user selects ok on the "are you sure + /// you want to exit" message box. + /// + void ConfirmExitMessageBoxAccepted(object sender, PlayerIndexEventArgs e) + { + ScreenManager.Game.Exit(); + } + + + #endregion + } +} diff --git a/MenuEntry.cs b/MenuEntry.cs new file mode 100644 index 0000000..97d2c40 --- /dev/null +++ b/MenuEntry.cs @@ -0,0 +1,192 @@ +#region File Description +//----------------------------------------------------------------------------- +// MenuEntry.cs +// +// XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using GameStateManagement; +#endregion + +namespace GameStateManagement +{ + /// + /// Helper class represents a single entry in a MenuScreen. By default this + /// just draws the entry text string, but it can be customized to display menu + /// entries in different ways. This also provides an event that will be raised + /// when the menu entry is selected. + /// + public class MenuEntry + { + #region Fields + + /// + /// The text rendered for this entry. + /// + string text; + + /// + /// Tracks a fading selection effect on the entry. + /// + /// + /// The entries transition out of the selection effect when they are deselected. + /// + float selectionFade; + + /// + /// The position at which the entry is drawn. This is set by the MenuScreen + /// each frame in Update. + /// + Vector2 position; + + #endregion + + #region Properties + + + /// + /// Gets or sets the text of this menu entry. + /// + public string Text + { + get { return text; } + set { text = value; } + } + + + /// + /// Gets or sets the position at which to draw this menu entry. + /// + public Vector2 Position + { + get { return position; } + set { position = value; } + } + + + #endregion + + #region Events + + + /// + /// Event raised when the menu entry is selected. + /// + public event EventHandler Selected; + + + /// + /// Method for raising the Selected event. + /// + protected internal virtual void OnSelectEntry(PlayerIndex playerIndex) + { + if (Selected != null) + Selected(this, new PlayerIndexEventArgs(playerIndex)); + } + + + #endregion + + #region Initialization + + + /// + /// Constructs a new menu entry with the specified text. + /// + public MenuEntry(string text) + { + this.text = text; + } + + + #endregion + + #region Update and Draw + + + /// + /// Updates the menu entry. + /// + public virtual void Update(MenuScreen screen, bool isSelected, GameTime gameTime) + { + // there is no such thing as a selected item on Windows Phone, so we always + // force isSelected to be false +#if WINDOWS_PHONE + isSelected = false; +#endif + + // When the menu selection changes, entries gradually fade between + // their selected and deselected appearance, rather than instantly + // popping to the new state. + float fadeSpeed = (float)gameTime.ElapsedGameTime.TotalSeconds * 4; + + if (isSelected) + selectionFade = Math.Min(selectionFade + fadeSpeed, 1); + else + selectionFade = Math.Max(selectionFade - fadeSpeed, 0); + } + + + /// + /// Draws the menu entry. This can be overridden to customize the appearance. + /// + public virtual void Draw(MenuScreen screen, bool isSelected, GameTime gameTime) + { + // there is no such thing as a selected item on Windows Phone, so we always + // force isSelected to be false +#if WINDOWS_PHONE + isSelected = false; +#endif + + // Draw the selected entry in yellow, otherwise white. + Color color = isSelected ? Color.Yellow : Color.White; + + // Pulsate the size of the selected menu entry. + double time = gameTime.TotalGameTime.TotalSeconds; + + float pulsate = (float)Math.Sin(time * 6) + 1; + + float scale = 1 + pulsate * 0.05f * selectionFade; + + // Modify the alpha to fade text out during transitions. + color *= screen.TransitionAlpha; + + // Draw text, centered on the middle of each line. + ScreenManager screenManager = screen.ScreenManager; + SpriteBatch spriteBatch = screenManager.SpriteBatch; + SpriteFont font = screenManager.Font; + + Vector2 origin = new Vector2(0, font.LineSpacing / 2); + + spriteBatch.DrawString(font, text, position, color, 0, + origin, scale, SpriteEffects.None, 0); + } + + + /// + /// Queries how much space this menu entry requires. + /// + public virtual int GetHeight(MenuScreen screen) + { + return screen.ScreenManager.Font.LineSpacing; + } + + + /// + /// Queries how wide the entry is, used for centering on the screen. + /// + public virtual int GetWidth(MenuScreen screen) + { + return (int)screen.ScreenManager.Font.MeasureString(Text).X; + } + + + #endregion + } +} diff --git a/MenuScreen.cs b/MenuScreen.cs new file mode 100644 index 0000000..8a1b061 --- /dev/null +++ b/MenuScreen.cs @@ -0,0 +1,329 @@ +#region File Description +//----------------------------------------------------------------------------- +// MenuScreen.cs +// +// XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +//using Microsoft.Xna.Framework.Input.Touch; +using Microsoft.Xna.Framework.Input; +//using FarseerPhysics.SamplesFramework; +#endregion + +namespace GameStateManagement +{ + /// + /// Base class for screens that contain a menu of options. The user can + /// move up and down to select an entry, or cancel to back out of the screen. + /// + public class MenuScreen : GameScreen + { + #region Fields + + // the number of pixels to pad above and below menu entries for touch input + const int menuEntryPadding = 10; + + private List menuEntries = new List(); + int selectedEntry = 0; + string menuTitle; + + InputAction menuUp; + InputAction menuDown; + InputAction menuSelect; + InputAction menuCancel; + + #endregion + + #region Properties + + + /// + /// Gets the list of menu entries, so derived classes can add + /// or change the menu contents. + /// + protected IList MenuEntries + { + get { return menuEntries; } + } + + + #endregion + + #region Initialization + + + /// + /// Constructor. + /// + public MenuScreen(string menuTitle) + { + this.menuTitle = menuTitle; + // menus generally only need Tap for menu selection + //EnabledGestures = GestureType.Tap; + + TransitionOnTime = TimeSpan.FromSeconds(0.5); + TransitionOffTime = TimeSpan.FromSeconds(0.5); + + menuUp = new InputAction( + new Buttons[] { Buttons.DPadUp, Buttons.LeftThumbstickUp }, + new Keys[] { Keys.Up }, + true); + menuDown = new InputAction( + new Buttons[] { Buttons.DPadDown, Buttons.LeftThumbstickDown }, + new Keys[] { Keys.Down }, + true); + menuSelect = new InputAction( + new Buttons[] { Buttons.A, Buttons.Start }, + new Keys[] { Keys.Enter, Keys.Space }, + true); + menuCancel = new InputAction( + new Buttons[] { Buttons.B, Buttons.Back }, + new Keys[] { Keys.Escape }, + true); + } + + + #endregion + + public void AddMenuItem(string name) + { + + menuEntries.Add(new MenuEntry(name)); + } + + #region Handle Input + + /// + /// Allows the screen to create the hit bounds for a particular menu entry. + /// + protected virtual Rectangle GetMenuEntryHitBounds(MenuEntry entry) + { + // the hit bounds are the entire width of the screen, and the height of the entry + // with some additional padding above and below. + return new Rectangle( + 0, + (int)entry.Position.Y - menuEntryPadding, + ScreenManager.GraphicsDevice.Viewport.Width, + entry.GetHeight(this) + (menuEntryPadding * 2)); + } + + /// + /// Responds to user input, changing the selected entry and accepting + /// or cancelling the menu. + /// + public override void HandleInput(GameTime gameTime, InputState input) + { + // For input tests we pass in our ControllingPlayer, which may + // either be null (to accept input from any player) or a specific index. + // If we pass a null controlling player, the InputState helper returns to + // us which player actually provided the input. We pass that through to + // OnSelectEntry and OnCancel, so they can tell which player triggered them. + + +#if WINDOWS || XBOX360 + PlayerIndex playerIndex; + // Move to the previous menu entry? + if (menuUp.Evaluate(input, ControllingPlayer, out playerIndex)) + { + selectedEntry--; + + if (selectedEntry < 0) + selectedEntry = menuEntries.Count - 1; + } + + // Move to the next menu entry? + if (menuDown.Evaluate(input, ControllingPlayer, out playerIndex)) + { + selectedEntry++; + + if (selectedEntry >= menuEntries.Count) + selectedEntry = 0; + } + + if (menuSelect.Evaluate(input, ControllingPlayer, out playerIndex)) + { + OnSelectEntry(selectedEntry, playerIndex); + } + else if (menuCancel.Evaluate(input, ControllingPlayer, out playerIndex)) + { + OnCancel(playerIndex); + } +#endif + +#if WINDOWS_PHONE + //selectedEntry = 1; + + PlayerIndex player; + if (input.IsNewButtonPress(Buttons.Back, ControllingPlayer, out player)) + { + OnCancel(player); + } + + // look for any taps that occurred and select any entries that were tapped + foreach (GestureSample gesture in input.Gestures) + { + //System.Diagnostics.Debugger.Break(); + if (gesture.GestureType == GestureType.Tap) + { + // convert the position to a Point that we can test against a Rectangle + Point tapLocation = new Point((int)gesture.Position.X, (int)gesture.Position.Y); + + // iterate the entries to see if any were tapped + for (int i = 0; i < menuEntries.Count; i++) + { + MenuEntry menuEntry = menuEntries[i]; + + if (GetMenuEntryHitBounds(menuEntry).Contains(tapLocation)) + { + // select the entry. since gestures are only available on Windows Phone, + // we can safely pass PlayerIndex.One to all entries since there is only + // one player on Windows Phone. + OnSelectEntry(i, PlayerIndex.One); + } + } + } + } +#endif + } + + + /// + /// Handler for when the user has chosen a menu entry. + /// + protected virtual void OnSelectEntry(int entryIndex, PlayerIndex playerIndex) + { + menuEntries[entryIndex].OnSelectEntry(playerIndex); + } + + + /// + /// Handler for when the user has cancelled the menu. + /// + protected virtual void OnCancel(PlayerIndex playerIndex) + { + ExitScreen(); + } + + + /// + /// Helper overload makes it easy to use OnCancel as a MenuEntry event handler. + /// + protected void OnCancel(object sender, PlayerIndexEventArgs e) + { + OnCancel(e.PlayerIndex); + } + + + #endregion + + #region Update and Draw + + + /// + /// Allows the screen the chance to position the menu entries. By default + /// all menu entries are lined up in a vertical list, centered on the screen. + /// + protected virtual void UpdateMenuEntryLocations() + { + // Make the menu slide into place during transitions, using a + // power curve to make things look more interesting (this makes + // the movement slow down as it nears the end). + float transitionOffset = (float)Math.Pow(TransitionPosition, 2); + + // start at Y = 175; each X value is generated per entry + Vector2 position = new Vector2(0f, 175f); + + // update each menu entry's location in turn + for (int i = 0; i < menuEntries.Count; i++) + { + MenuEntry menuEntry = menuEntries[i]; + + // each entry is to be centered horizontally + position.X = ScreenManager.GraphicsDevice.Viewport.Width / 2 - menuEntry.GetWidth(this) / 2; + + if (ScreenState == ScreenState.TransitionOn) + position.X -= transitionOffset * 256; + else + position.X += transitionOffset * 512; + + // set the entry's position + menuEntry.Position = position; + + // move down for the next entry the size of this entry + position.Y += menuEntry.GetHeight(this); + } + } + + + /// + /// Updates the menu. + /// + public override void Update(GameTime gameTime, bool otherScreenHasFocus, + bool coveredByOtherScreen) + { + base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + + // Update each nested MenuEntry object. + for (int i = 0; i < menuEntries.Count; i++) + { + bool isSelected = IsActive && (i == selectedEntry); + + menuEntries[i].Update(this, isSelected, gameTime); + } + } + + + /// + /// Draws the menu. + /// + public override void Draw(GameTime gameTime) + { + // make sure our entries are in the right place before we draw them + UpdateMenuEntryLocations(); + + GraphicsDevice graphics = ScreenManager.GraphicsDevice; + SpriteBatch spriteBatch = ScreenManager.SpriteBatch; + SpriteFont font = ScreenManager.Font; + + spriteBatch.Begin(); + + // Draw each menu entry in turn. + for (int i = 0; i < menuEntries.Count; i++) + { + MenuEntry menuEntry = menuEntries[i]; + + bool isSelected = IsActive && (i == selectedEntry); + + menuEntry.Draw(this, isSelected, gameTime); + } + + // Make the menu slide into place during transitions, using a + // power curve to make things look more interesting (this makes + // the movement slow down as it nears the end). + float transitionOffset = (float)Math.Pow(TransitionPosition, 2); + + // Draw the menu title centered on the screen + Vector2 titlePosition = new Vector2(graphics.Viewport.Width / 2, 80); + Vector2 titleOrigin = font.MeasureString(menuTitle) / 2; + Color titleColor = new Color(192, 192, 192) * TransitionAlpha; + float titleScale = 1.25f; + + titlePosition.Y -= transitionOffset * 100; + + spriteBatch.DrawString(font, menuTitle, titlePosition, titleColor, 0, + titleOrigin, titleScale, SpriteEffects.None, 0); + + spriteBatch.End(); + } + + + #endregion + } +} diff --git a/MessageBoxScreen.cs b/MessageBoxScreen.cs new file mode 100644 index 0000000..9c25ac0 --- /dev/null +++ b/MessageBoxScreen.cs @@ -0,0 +1,186 @@ +#region File Description +//----------------------------------------------------------------------------- +// MessageBoxScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using GameStateManagement; +#endregion + +namespace GameStateManagement +{ + /// + /// A popup message box screen, used to display "are you sure?" + /// confirmation messages. + /// + class MessageBoxScreen : GameScreen + { + #region Fields + + string message; + Texture2D gradientTexture; + + InputAction menuSelect; + InputAction menuCancel; + + #endregion + + #region Events + + public event EventHandler Accepted; + public event EventHandler Cancelled; + + #endregion + + #region Initialization + + + /// + /// Constructor automatically includes the standard "A=ok, B=cancel" + /// usage text prompt. + /// + public MessageBoxScreen(string message) + : this(message, true) + { } + + + /// + /// Constructor lets the caller specify whether to include the standard + /// "A=ok, B=cancel" usage text prompt. + /// + public MessageBoxScreen(string message, bool includeUsageText) + { + const string usageText = "\nA button, Space, Enter = ok" + + "\nB button, Esc = cancel"; + + if (includeUsageText) + this.message = message + usageText; + else + this.message = message; + + IsPopup = true; + + TransitionOnTime = TimeSpan.FromSeconds(0.2); + TransitionOffTime = TimeSpan.FromSeconds(0.2); + + menuSelect = new InputAction( + new Buttons[] { Buttons.A, Buttons.Start }, + new Keys[] { Keys.Space, Keys.Enter }, + true); + menuCancel = new InputAction( + new Buttons[] { Buttons.B, Buttons.Back }, + new Keys[] { Keys.Escape, Keys.Back }, + true); + } + + + /// + /// Loads graphics content for this screen. This uses the shared ContentManager + /// provided by the Game class, so the content will remain loaded forever. + /// Whenever a subsequent MessageBoxScreen tries to load this same content, + /// it will just get back another reference to the already loaded data. + /// + public override void Activate(bool instancePreserved) + { + if (!instancePreserved) + { + ContentManager content = ScreenManager.Game.Content; + gradientTexture = content.Load("gradient"); + } + } + + + #endregion + + #region Handle Input + + + /// + /// Responds to user input, accepting or cancelling the message box. + /// + public override void HandleInput(GameTime gameTime, InputState input) + { + PlayerIndex playerIndex; + + // We pass in our ControllingPlayer, which may either be null (to + // accept input from any player) or a specific index. If we pass a null + // controlling player, the InputState helper returns to us which player + // actually provided the input. We pass that through to our Accepted and + // Cancelled events, so they can tell which player triggered them. + if (menuSelect.Evaluate(input, ControllingPlayer, out playerIndex)) + { + // Raise the accepted event, then exit the message box. + if (Accepted != null) + Accepted(this, new PlayerIndexEventArgs(playerIndex)); + + ExitScreen(); + } + else if (menuCancel.Evaluate(input, ControllingPlayer, out playerIndex)) + { + // Raise the cancelled event, then exit the message box. + if (Cancelled != null) + Cancelled(this, new PlayerIndexEventArgs(playerIndex)); + + ExitScreen(); + } + } + + + #endregion + + #region Draw + + + /// + /// Draws the message box. + /// + public override void Draw(GameTime gameTime) + { + SpriteBatch spriteBatch = ScreenManager.SpriteBatch; + SpriteFont font = ScreenManager.Font; + + // Darken down any other screens that were drawn beneath the popup. + ScreenManager.FadeBackBufferToBlack(TransitionAlpha * 2 / 3); + + // Center the message text in the viewport. + Viewport viewport = ScreenManager.GraphicsDevice.Viewport; + Vector2 viewportSize = new Vector2(viewport.Width, viewport.Height); + Vector2 textSize = font.MeasureString(message); + Vector2 textPosition = (viewportSize - textSize) / 2; + + // The background includes a border somewhat larger than the text itself. + const int hPad = 32; + const int vPad = 16; + + Rectangle backgroundRectangle = new Rectangle((int)textPosition.X - hPad, + (int)textPosition.Y - vPad, + (int)textSize.X + hPad * 2, + (int)textSize.Y + vPad * 2); + + // Fade the popup alpha during transitions. + Color color = Color.White * TransitionAlpha; + + spriteBatch.Begin(); + + // Draw the background rectangle. + spriteBatch.Draw(gradientTexture, backgroundRectangle, color); + + // Draw the message box text. + spriteBatch.DrawString(font, message, textPosition, color); + + spriteBatch.End(); + } + + + #endregion + } +} diff --git a/OptionsMenuScreen.cs b/OptionsMenuScreen.cs new file mode 100644 index 0000000..5afba98 --- /dev/null +++ b/OptionsMenuScreen.cs @@ -0,0 +1,149 @@ +#region File Description +//----------------------------------------------------------------------------- +// OptionsMenuScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using Microsoft.Xna.Framework; +#endregion + +namespace GameStateManagement +{ + /// + /// The options screen is brought up over the top of the main menu + /// screen, and gives the user a chance to configure the game + /// in various hopefully useful ways. + /// + class OptionsMenuScreen : MenuScreen + { + #region Fields + + MenuEntry ungulateMenuEntry; + MenuEntry languageMenuEntry; + MenuEntry frobnicateMenuEntry; + MenuEntry elfMenuEntry; + + enum Ungulate + { + BactrianCamel, + Dromedary, + Llama, + } + + static Ungulate currentUngulate = Ungulate.Dromedary; + + static string[] languages = { "C#", "French", "Deoxyribonucleic acid" }; + static int currentLanguage = 0; + + static bool frobnicate = true; + + static int elf = 23; + + #endregion + + #region Initialization + + + /// + /// Constructor. + /// + public OptionsMenuScreen() + : base("Options") + { + // Create our menu entries. + ungulateMenuEntry = new MenuEntry(string.Empty); + languageMenuEntry = new MenuEntry(string.Empty); + frobnicateMenuEntry = new MenuEntry(string.Empty); + elfMenuEntry = new MenuEntry(string.Empty); + + SetMenuEntryText(); + + MenuEntry back = new MenuEntry("Back"); + + // Hook up menu event handlers. + ungulateMenuEntry.Selected += UngulateMenuEntrySelected; + languageMenuEntry.Selected += LanguageMenuEntrySelected; + frobnicateMenuEntry.Selected += FrobnicateMenuEntrySelected; + elfMenuEntry.Selected += ElfMenuEntrySelected; + back.Selected += OnCancel; + + // Add entries to the menu. + MenuEntries.Add(ungulateMenuEntry); + MenuEntries.Add(languageMenuEntry); + MenuEntries.Add(frobnicateMenuEntry); + MenuEntries.Add(elfMenuEntry); + MenuEntries.Add(back); + } + + + /// + /// Fills in the latest values for the options screen menu text. + /// + void SetMenuEntryText() + { + ungulateMenuEntry.Text = "Preferred ungulate: " + currentUngulate; + languageMenuEntry.Text = "Language: " + languages[currentLanguage]; + frobnicateMenuEntry.Text = "Frobnicate: " + (frobnicate ? "on" : "off"); + elfMenuEntry.Text = "elf: " + elf; + } + + + #endregion + + #region Handle Input + + + /// + /// Event handler for when the Ungulate menu entry is selected. + /// + void UngulateMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + currentUngulate++; + + if (currentUngulate > Ungulate.Llama) + currentUngulate = 0; + + SetMenuEntryText(); + } + + + /// + /// Event handler for when the Language menu entry is selected. + /// + void LanguageMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + currentLanguage = (currentLanguage + 1) % languages.Length; + + SetMenuEntryText(); + } + + + /// + /// Event handler for when the Frobnicate menu entry is selected. + /// + void FrobnicateMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + frobnicate = !frobnicate; + + SetMenuEntryText(); + } + + + /// + /// Event handler for when the Elf menu entry is selected. + /// + void ElfMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + elf++; + + SetMenuEntryText(); + } + + + #endregion + } +} diff --git a/PauseMenuScreen.cs b/PauseMenuScreen.cs new file mode 100644 index 0000000..525a3f9 --- /dev/null +++ b/PauseMenuScreen.cs @@ -0,0 +1,79 @@ +#region File Description +//----------------------------------------------------------------------------- +// PauseMenuScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using Microsoft.Xna.Framework; +#endregion + +namespace GameStateManagement +{ + /// + /// The pause menu comes up over the top of the game, + /// giving the player options to resume or quit. + /// + class PauseMenuScreen : MenuScreen + { + #region Initialization + + + /// + /// Constructor. + /// + public PauseMenuScreen() + : base("Paused") + { + // Create our menu entries. + MenuEntry resumeGameMenuEntry = new MenuEntry("Resume Game"); + MenuEntry quitGameMenuEntry = new MenuEntry("Quit Game"); + + // Hook up menu event handlers. + resumeGameMenuEntry.Selected += OnCancel; + quitGameMenuEntry.Selected += QuitGameMenuEntrySelected; + + // Add entries to the menu. + MenuEntries.Add(resumeGameMenuEntry); + MenuEntries.Add(quitGameMenuEntry); + } + + + #endregion + + #region Handle Input + + + /// + /// Event handler for when the Quit Game menu entry is selected. + /// + void QuitGameMenuEntrySelected(object sender, PlayerIndexEventArgs e) + { + const string message = "Are you sure you want to quit this game?"; + + MessageBoxScreen confirmQuitMessageBox = new MessageBoxScreen(message); + + confirmQuitMessageBox.Accepted += ConfirmQuitMessageBoxAccepted; + + ScreenManager.AddScreen(confirmQuitMessageBox, ControllingPlayer); + } + + + /// + /// Event handler for when the user selects ok on the "are you sure + /// you want to quit" message box. This uses the loading screen to + /// transition from the game back to the main menu screen. + /// + void ConfirmQuitMessageBoxAccepted(object sender, PlayerIndexEventArgs e) + { + LoadingScreen.Load(ScreenManager, false, null, new BackgroundScreen(), + new MainMenuScreen()); + } + + + #endregion + } +} diff --git a/PhoneMainMenuScreen.cs b/PhoneMainMenuScreen.cs new file mode 100644 index 0000000..613781c --- /dev/null +++ b/PhoneMainMenuScreen.cs @@ -0,0 +1,66 @@ +#region File Description +//----------------------------------------------------------------------------- +// PhoneMainMenuScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System; +using GameStateManagement; +using GameStateManagementSample; +using Microsoft.Xna.Framework; + +namespace GameStateManagement +{ + class PhoneMainMenuScreen : PhoneMenuScreen + { + public PhoneMainMenuScreen() + : base("Main Menu") + { + // Create a button to start the game + Button playButton = new Button("Play"); + playButton.Tapped += playButton_Tapped; + MenuButtons.Add(playButton); + + // Create two buttons to toggle sound effects and music. This sample just shows one way + // of making and using these buttons; it doesn't actually have sound effects or music + BooleanButton sfxButton = new BooleanButton("Sound Effects", true); + sfxButton.Tapped += sfxButton_Tapped; + MenuButtons.Add(sfxButton); + + BooleanButton musicButton = new BooleanButton("Music", true); + musicButton.Tapped += musicButton_Tapped; + MenuButtons.Add(musicButton); + } + + void playButton_Tapped(object sender, EventArgs e) + { + // When the "Play" button is tapped, we load the GameplayScreen + LoadingScreen.Load(ScreenManager, true, PlayerIndex.One, new GameplayScreen()); + } + + void sfxButton_Tapped(object sender, EventArgs e) + { + BooleanButton button = sender as BooleanButton; + + // In a real game, you'd want to store away the value of + // the button to turn off sounds here. :) + } + + void musicButton_Tapped(object sender, EventArgs e) + { + BooleanButton button = sender as BooleanButton; + + // In a real game, you'd want to store away the value of + // the button to turn off music here. :) + } + + protected override void OnCancel() + { + ScreenManager.Game.Exit(); + base.OnCancel(); + } + } +} diff --git a/PhoneMenuScreen.cs b/PhoneMenuScreen.cs new file mode 100644 index 0000000..32e2b05 --- /dev/null +++ b/PhoneMenuScreen.cs @@ -0,0 +1,149 @@ +#region File Description +//----------------------------------------------------------------------------- +// PhoneMenuScreen.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System; +using System.Collections.Generic; +using GameStateManagement; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +//using Microsoft.Xna.Framework.Input.Touch; + +namespace GameStateManagement +{ + /// + /// Provides a basic base screen for menus on Windows Phone leveraging the Button class. + /// + class PhoneMenuScreen : GameScreen + { + List