#region File Description //----------------------------------------------------------------------------- // InvitesGame.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.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Net; #endregion namespace Invites { /// /// Sample showing how to add invite support to a networked XNA Framework /// game. This builds on the Peer-to-Peer sample, extending it by adding /// an event handler which will respond to invite notifications. /// public class InvitesGame : Microsoft.Xna.Framework.Game { #region Fields const int screenWidth = 1067; const int screenHeight = 600; const int maxGamers = 16; const int maxLocalGamers = 4; GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont font; KeyboardState currentKeyboardState; GamePadState currentGamePadState; NetworkSession networkSession; PacketWriter packetWriter = new PacketWriter(); PacketReader packetReader = new PacketReader(); string errorMessage; #endregion #region Initialization public InvitesGame() { graphics = new GraphicsDeviceManager(this); graphics.PreferredBackBufferWidth = screenWidth; graphics.PreferredBackBufferHeight = screenHeight; Content.RootDirectory = "Content"; Components.Add(new GamerServicesComponent(this)); // Listen for invite notification events. This handler will be called // whenever the user accepts an invite message, or if they select the // "Join Session In Progress" option from their Guide friends screen. NetworkSession.InviteAccepted += InviteAcceptedEventHandler; } /// /// Load your content. /// protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); font = Content.Load("Font"); } #endregion #region Update /// /// Allows the game to run logic. /// protected override void Update(GameTime gameTime) { HandleInput(); if (networkSession == null) { // If we are not in a network session, update the // menu screen that will let us create or join one. UpdateMenuScreen(); } else { // If we are in a network session, update it. UpdateNetworkSession(); } base.Update(gameTime); } /// /// Menu screen provides options to create or join network sessions. /// void UpdateMenuScreen() { if (IsActive) { if (Gamer.SignedInGamers.Count == 0) { // If there are no profiles signed in, we cannot proceed. // Show the Guide so the user can sign in. Guide.ShowSignIn(maxLocalGamers, false); } else if (IsPressed(Keys.A, Buttons.A)) { // Create a new session? CreateSession(); } else if (IsPressed(Keys.B, Buttons.B)) { // Join an existing session? JoinSession(); } } } /// /// Starts hosting a new network session. /// void CreateSession() { DrawMessage("Creating session..."); try { networkSession = NetworkSession.Create(NetworkSessionType.PlayerMatch, maxLocalGamers, maxGamers); HookSessionEvents(); } catch (Exception error) { errorMessage = error.Message; } } /// /// Joins an existing network session. /// void JoinSession() { DrawMessage("Joining session..."); try { // Search for sessions. using (AvailableNetworkSessionCollection availableSessions = NetworkSession.Find(NetworkSessionType.PlayerMatch, maxLocalGamers, null)) { if (availableSessions.Count == 0) { errorMessage = "No network sessions found."; return; } // Join the first session we found. networkSession = NetworkSession.Join(availableSessions[0]); HookSessionEvents(); } } catch (Exception error) { errorMessage = error.Message; } } /// /// This event handler will be called whenever the game recieves an invite /// notification. This can occur when the user accepts an invite that was /// sent to them by a friend (pull mode), or if they choose the "Join /// Session In Progress" option in their friends screen (push mode). /// The handler should leave the current session (if any), then join the /// session referred to by the invite. It is not necessary to prompt the /// user before doing this, as the Guide will already have taken care of /// the necessary confirmations before the invite was delivered to you. /// void InviteAcceptedEventHandler(object sender, InviteAcceptedEventArgs e) { DrawMessage("Joining session from invite..."); // Leave the current network session. if (networkSession != null) { networkSession.Dispose(); networkSession = null; } try { // Join a new session in response to the invite. networkSession = NetworkSession.JoinInvited(maxLocalGamers); HookSessionEvents(); } catch (Exception error) { errorMessage = error.Message; } } /// /// After creating or joining a network session, we must subscribe to /// some events so we will be notified when the session changes state. /// void HookSessionEvents() { networkSession.GamerJoined += GamerJoinedEventHandler; networkSession.SessionEnded += SessionEndedEventHandler; } /// /// This event handler will be called whenever a new gamer joins the session. /// We use it to allocate a Tank object, and associate it with the new gamer. /// void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e) { int gamerIndex = networkSession.AllGamers.IndexOf(e.Gamer); e.Gamer.Tag = new Tank(gamerIndex, Content, screenWidth, screenHeight); } /// /// Event handler notifies us when the network session has ended. /// void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e) { errorMessage = e.EndReason.ToString(); networkSession.Dispose(); networkSession = null; } /// /// Updates the state of the network session, moving the tanks /// around and synchronizing their state over the network. /// void UpdateNetworkSession() { // Update our locally controlled tanks, and send their // latest position data to everyone in the session. foreach (LocalNetworkGamer gamer in networkSession.LocalGamers) { UpdateLocalGamer(gamer); } // Pump the underlying session object. networkSession.Update(); // Make sure the session has not ended. if (networkSession == null) return; // Read any packets telling us the positions of remotely controlled tanks. foreach (LocalNetworkGamer gamer in networkSession.LocalGamers) { ReadIncomingPackets(gamer); } } /// /// Helper for updating a locally controlled gamer. /// void UpdateLocalGamer(LocalNetworkGamer gamer) { // Look up what tank is associated with this local player. Tank localTank = gamer.Tag as Tank; // Update the tank. ReadTankInputs(localTank, gamer.SignedInGamer.PlayerIndex); localTank.Update(); // Write the tank state into a network packet. packetWriter.Write(localTank.Position); packetWriter.Write(localTank.TankRotation); packetWriter.Write(localTank.TurretRotation); // Send the data to everyone in the session. gamer.SendData(packetWriter, SendDataOptions.InOrder); } /// /// Helper for reading incoming network packets. /// void ReadIncomingPackets(LocalNetworkGamer gamer) { // Keep reading as long as incoming packets are available. while (gamer.IsDataAvailable) { NetworkGamer sender; // Read a single packet from the network. gamer.ReceiveData(packetReader, out sender); // Discard packets sent by local gamers: we already know their state! if (sender.IsLocal) continue; // Look up the tank associated with whoever sent this packet. Tank remoteTank = sender.Tag as Tank; // Read the state of this tank from the network packet. remoteTank.Position = packetReader.ReadVector2(); remoteTank.TankRotation = packetReader.ReadSingle(); remoteTank.TurretRotation = packetReader.ReadSingle(); } } #endregion #region Draw /// /// This is called when the game should draw itself. /// protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); if (networkSession == null) { // If we are not in a network session, draw the // menu screen that will let us create or join one. DrawMenuScreen(); } else { // If we are in a network session, draw it. DrawNetworkSession(); } base.Draw(gameTime); } /// /// Draws the startup screen used to create and join network sessions. /// void DrawMenuScreen() { string message = string.Empty; if (!string.IsNullOrEmpty(errorMessage)) message += "Error:\n" + errorMessage.Replace(". ", ".\n") + "\n\n"; message += "A = create session\n" + "B = join session"; spriteBatch.Begin(); spriteBatch.DrawString(font, message, new Vector2(161, 161), Color.Black); spriteBatch.DrawString(font, message, new Vector2(160, 160), Color.White); spriteBatch.End(); } /// /// Draws the state of an active network session. /// void DrawNetworkSession() { spriteBatch.Begin(); // For each person in the session... foreach (NetworkGamer gamer in networkSession.AllGamers) { // Look up the tank object belonging to this network gamer. Tank tank = gamer.Tag as Tank; // Draw the tank. tank.Draw(spriteBatch); // Draw a gamertag label. string label = gamer.Gamertag; Color labelColor = Color.Black; Vector2 labelOffset = new Vector2(100, 150); if (gamer.IsHost) label += " (host)"; // Flash the gamertag to yellow when the player is talking. if (gamer.IsTalking) labelColor = Color.Yellow; spriteBatch.DrawString(font, label, tank.Position, labelColor, 0, labelOffset, 0.6f, SpriteEffects.None, 0); } spriteBatch.End(); } /// /// Helper draws notification messages before calling blocking network methods. /// void DrawMessage(string message) { if (!BeginDraw()) return; GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.DrawString(font, message, new Vector2(161, 161), Color.Black); spriteBatch.DrawString(font, message, new Vector2(160, 160), Color.White); spriteBatch.End(); EndDraw(); } #endregion #region Handle Input /// /// Handles input. /// private void HandleInput() { currentKeyboardState = Keyboard.GetState(); currentGamePadState = GamePad.GetState(PlayerIndex.One); // Check for exit. if (IsActive && IsPressed(Keys.Escape, Buttons.Back)) { Exit(); } } /// /// Checks if the specified button is pressed on either keyboard or gamepad. /// bool IsPressed(Keys key, Buttons button) { return (currentKeyboardState.IsKeyDown(key) || currentGamePadState.IsButtonDown(button)); } /// /// Reads input data from keyboard and gamepad, and stores /// it into the specified tank object. /// void ReadTankInputs(Tank tank, PlayerIndex playerIndex) { // Read the gamepad. GamePadState gamePad = GamePad.GetState(playerIndex); Vector2 tankInput = gamePad.ThumbSticks.Left; Vector2 turretInput = gamePad.ThumbSticks.Right; // Read the keyboard. KeyboardState keyboard = Keyboard.GetState(playerIndex); if (keyboard.IsKeyDown(Keys.Left)) tankInput.X = -1; else if (keyboard.IsKeyDown(Keys.Right)) tankInput.X = 1; if (keyboard.IsKeyDown(Keys.Up)) tankInput.Y = 1; else if (keyboard.IsKeyDown(Keys.Down)) tankInput.Y = -1; if (keyboard.IsKeyDown(Keys.A)) turretInput.X = -1; else if (keyboard.IsKeyDown(Keys.D)) turretInput.X = 1; if (keyboard.IsKeyDown(Keys.W)) turretInput.Y = 1; else if (keyboard.IsKeyDown(Keys.S)) turretInput.Y = -1; // Normalize the input vectors. if (tankInput.Length() > 1) tankInput.Normalize(); if (turretInput.Length() > 1) turretInput.Normalize(); // Store these input values into the tank object. tank.TankInput = tankInput; tank.TurretInput = turretInput; } #endregion } #region Entry Point /// /// The main entry point for the application. /// static class Program { static void Main() { using (InvitesGame game = new InvitesGame()) { game.Run(); } } } #endregion }