PeerToPeerGame.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. //-----------------------------------------------------------------------------
  2. // PeerToPeerGame.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using Microsoft.Xna.Framework;
  9. using Microsoft.Xna.Framework.GamerServices;
  10. using Microsoft.Xna.Framework.Graphics;
  11. using Microsoft.Xna.Framework.Input;
  12. using Microsoft.Xna.Framework.Input.Touch;
  13. using Microsoft.Xna.Framework.Net;
  14. namespace PeerToPeer
  15. {
  16. /// <summary>
  17. /// Sample showing how to implement a simple multiplayer
  18. /// network session, using a peer-to-peer network topology.
  19. /// </summary>
  20. public class PeerToPeerGame : Microsoft.Xna.Framework.Game
  21. {
  22. const int screenWidth = 480;
  23. const int screenHeight = 540;
  24. const int maxGamers = 16;
  25. const int maxLocalGamers = 4;
  26. GraphicsDeviceManager graphicsDeviceManager;
  27. SpriteBatch spriteBatch;
  28. SpriteFont font;
  29. KeyboardState currentKeyboardState;
  30. KeyboardState previousKeyboardState;
  31. GamePadState currentGamePadState;
  32. GamePadState previousGamePadState;
  33. TouchCollection currentTouchState;
  34. NetworkSession networkSession;
  35. PacketWriter packetWriter = new PacketWriter();
  36. PacketReader packetReader = new PacketReader();
  37. string errorMessage;
  38. //Texture2D gamePadTexture;
  39. public PeerToPeerGame()
  40. {
  41. graphicsDeviceManager = new GraphicsDeviceManager(this);
  42. graphicsDeviceManager.PreferredBackBufferWidth = screenWidth;
  43. graphicsDeviceManager.PreferredBackBufferHeight = screenHeight;
  44. if (UIUtility.IsMobile)
  45. {
  46. graphicsDeviceManager.IsFullScreen = true;
  47. IsMouseVisible = false;
  48. }
  49. else if (UIUtility.IsDesktop)
  50. {
  51. graphicsDeviceManager.IsFullScreen = false;
  52. IsMouseVisible = true;
  53. }
  54. else
  55. {
  56. throw new PlatformNotSupportedException();
  57. }
  58. Content.RootDirectory = "Content";
  59. Components.Add(new GamerServicesComponent(this));
  60. }
  61. /// <summary>
  62. /// Load your content.
  63. /// </summary>
  64. protected override void LoadContent()
  65. {
  66. spriteBatch = new SpriteBatch(GraphicsDevice);
  67. font = Content.Load<SpriteFont>("Font");
  68. if (UIUtility.IsMobile)
  69. {
  70. /* gamePadTexture = Content.Load<Texture2D>("gamepad.png");
  71. ThumbStickDefinition thumbStickLeft = new ThumbStickDefinition();
  72. thumbStickLeft.Position = new Vector2(10, 400);
  73. thumbStickLeft.Texture = gamePadTexture;
  74. thumbStickLeft.TextureRect = new Rectangle(2, 2, 68, 68);
  75. GamePad.LeftThumbStickDefinition = thumbStickLeft;
  76. ThumbStickDefinition thumbStickRight = new ThumbStickDefinition();
  77. thumbStickRight.Position = new Vector2(240, 400);
  78. thumbStickRight.Texture = gamePadTexture;
  79. thumbStickRight.TextureRect = new Rectangle(2, 2, 68, 68);
  80. GamePad.RightThumbStickDefinition = thumbStickRight; */
  81. }
  82. }
  83. /// <summary>
  84. /// Allows the game to run logic.
  85. /// </summary>
  86. protected override void Update(GameTime gameTime)
  87. {
  88. HandleInput();
  89. if (networkSession == null)
  90. {
  91. // If we are not in a network session, update the
  92. // menu screen that will let us create or join one.
  93. UpdateMenuScreen();
  94. }
  95. else
  96. {
  97. // If we are in a network session, update it.
  98. UpdateNetworkSession();
  99. }
  100. base.Update(gameTime);
  101. }
  102. /// <summary>
  103. /// Menu screen provides options to create or join network sessions.
  104. /// </summary>
  105. void UpdateMenuScreen()
  106. {
  107. if (IsActive)
  108. {
  109. if (Gamer.SignedInGamers.Count == 0)
  110. {
  111. // If there are no profiles signed in, we cannot proceed.
  112. // Show the Guide so the user can sign in.
  113. Guide.ShowSignIn(maxLocalGamers, false);
  114. }
  115. else if (IsPressed(Keys.A, Buttons.A))
  116. {
  117. // Create a new LAN session?
  118. CreateSession(NetworkSessionType.SystemLink);
  119. }
  120. else if (IsPressed(Keys.X, Buttons.X))
  121. {
  122. CreateSession(NetworkSessionType.PlayerMatch);
  123. }
  124. else if (IsPressed(Keys.Y, Buttons.Y))
  125. {
  126. JoinSession(NetworkSessionType.PlayerMatch);
  127. }
  128. else if (IsPressed(Keys.B, Buttons.B))
  129. {
  130. // Join an existing session?
  131. JoinSession(NetworkSessionType.SystemLink);
  132. }
  133. }
  134. }
  135. /// <summary>
  136. /// Starts hosting a new network session.
  137. /// </summary>
  138. void CreateSession(NetworkSessionType type)
  139. {
  140. DrawMessage($"Creating {type} session...");
  141. try
  142. {
  143. networkSession = NetworkSession.Create(type, maxLocalGamers, maxGamers);
  144. HookSessionEvents();
  145. }
  146. catch (Exception e)
  147. {
  148. errorMessage = e.Message;
  149. }
  150. }
  151. /// <summary>
  152. /// Joins an existing network session.
  153. /// </summary>
  154. async void JoinSession(NetworkSessionType type)
  155. {
  156. DrawMessage($"Joining {type} session...");
  157. try
  158. {
  159. // Search for sessions asynchronously with cancellation support
  160. var cancellationTokenSource = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(5)); // 5 second timeout
  161. using (AvailableNetworkSessionCollection availableSessions = await NetworkSession.FindAsync(type, maxLocalGamers, null, cancellationTokenSource.Token))
  162. {
  163. if (availableSessions.Count == 0)
  164. {
  165. errorMessage = "No network sessions found.";
  166. return;
  167. }
  168. // Join the first session we found.
  169. networkSession = await NetworkSession.JoinAsync(availableSessions[0], cancellationTokenSource.Token);
  170. HookSessionEvents();
  171. }
  172. }
  173. catch (System.OperationCanceledException)
  174. {
  175. errorMessage = "Session discovery timed out after 5 seconds.";
  176. }
  177. catch (Exception e)
  178. {
  179. errorMessage = e.Message;
  180. }
  181. }
  182. /// <summary>
  183. /// After creating or joining a network session, we must subscribe to
  184. /// some events so we will be notified when the session changes state.
  185. /// </summary>
  186. void HookSessionEvents()
  187. {
  188. networkSession.GamerJoined += GamerJoinedEventHandler;
  189. networkSession.SessionEnded += SessionEndedEventHandler;
  190. }
  191. /// <summary>
  192. /// This event handler will be called whenever a new gamer joins the session.
  193. /// We use it to allocate a Tank object, and associate it with the new gamer.
  194. /// </summary>
  195. void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
  196. {
  197. int gamerIndex = networkSession.AllGamers.IndexOf((NetworkGamer)e.Gamer);
  198. e.Gamer.Tag = new Tank(gamerIndex, Content, screenWidth, screenHeight);
  199. }
  200. /// <summary>
  201. /// Event handler notifies us when the network session has ended.
  202. /// </summary>
  203. void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
  204. {
  205. errorMessage = e.EndReason.ToString();
  206. networkSession.Dispose();
  207. networkSession = null;
  208. }
  209. /// <summary>
  210. /// Updates the state of the network session, moving the tanks
  211. /// around and synchronizing their state over the network.
  212. /// </summary>
  213. void UpdateNetworkSession()
  214. {
  215. // Update our locally controlled tanks, and send their
  216. // latest position data to everyone in the session.
  217. foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
  218. {
  219. UpdateLocalGamer(gamer);
  220. }
  221. // Pump the underlying session object.
  222. networkSession.Update();
  223. // Make sure the session has not ended.
  224. if (networkSession == null)
  225. return;
  226. // Read any packets telling us the positions of remotely controlled tanks.
  227. foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
  228. {
  229. ReadIncomingPackets(gamer);
  230. }
  231. }
  232. /// <summary>
  233. /// Helper for updating a locally controlled gamer.
  234. /// </summary>
  235. void UpdateLocalGamer(LocalNetworkGamer localNetworkGamer)
  236. {
  237. // Look up what tank is associated with this local player.
  238. if (localNetworkGamer.Tag is Tank localTank)
  239. {
  240. // Update the tank.
  241. ReadTankInputs(localTank, (PlayerIndex)localNetworkGamer.SignedInGamer.PlayerIndex);
  242. localTank.Update();
  243. // Write the tank state into a network packet.
  244. packetWriter.Write(localTank.Position);
  245. packetWriter.Write(localTank.TankRotation);
  246. packetWriter.Write(localTank.TurretRotation);
  247. // Send the data to everyone in the session.
  248. localNetworkGamer.SendData(packetWriter, SendDataOptions.InOrder);
  249. }
  250. }
  251. /// <summary>
  252. /// Helper for reading incoming network packets.
  253. /// </summary>
  254. void ReadIncomingPackets(LocalNetworkGamer localNetworkGamer)
  255. {
  256. // Keep reading as long as incoming packets are available.
  257. while (localNetworkGamer.IsDataAvailable)
  258. {
  259. NetworkGamer sender;
  260. // Read a single packet from the network.
  261. localNetworkGamer.ReceiveData(packetReader, out sender);
  262. // Discard packets sent by local gamers: we already know their state!
  263. if (sender.IsLocal)
  264. continue;
  265. // Look up the tank associated with whoever sent this packet.
  266. if (sender.Tag is Tank remoteTank)
  267. {
  268. // Read the state of this tank from the network packet.
  269. remoteTank.Position = packetReader.ReadVector2();
  270. remoteTank.TankRotation = packetReader.ReadSingle();
  271. remoteTank.TurretRotation = packetReader.ReadSingle();
  272. }
  273. }
  274. }
  275. /// <summary>
  276. /// This is called when the game should draw itself.
  277. /// </summary>
  278. protected override void Draw(GameTime gameTime)
  279. {
  280. GraphicsDevice.Clear(Color.MonoGameOrange);
  281. if (networkSession == null)
  282. {
  283. // If we are not in a network session, draw the
  284. // menu screen that will let us create or join one.
  285. DrawMenuScreen();
  286. }
  287. else
  288. {
  289. // If we are in a network session, draw it.
  290. DrawNetworkSession(gameTime);
  291. }
  292. base.Draw(gameTime);
  293. }
  294. /// <summary>
  295. /// Draws the startup screen used to create and join network sessions.
  296. /// </summary>
  297. void DrawMenuScreen()
  298. {
  299. string message = string.Empty;
  300. if (!string.IsNullOrEmpty(errorMessage))
  301. message += "Error:\n" + errorMessage.Replace(". ", ".\n") + "\n\n";
  302. message += "A = Create LAN session\n" +
  303. "B = Join LAN session" + "\n" +
  304. "X = Create live session (NOT IMPLEMENTED)\n" +
  305. "Y = Join live session (NOT IMPLEMENTED)\n";
  306. spriteBatch.Begin();
  307. spriteBatch.DrawString(font, message, new Vector2(61, 161), Color.Black);
  308. spriteBatch.DrawString(font, message, new Vector2(60, 160), Color.White);
  309. spriteBatch.End();
  310. }
  311. /// <summary>
  312. /// Draws the state of an active network session.
  313. /// </summary>
  314. void DrawNetworkSession(GameTime gameTime)
  315. {
  316. spriteBatch.Begin();
  317. // For each person in the session...
  318. foreach (INetworkGamer gamer in networkSession.AllGamers)
  319. {
  320. // Look up the tank object belonging to this network gamer.
  321. Tank tank = gamer.Tag as Tank;
  322. if (tank != null)
  323. {
  324. // Draw the tank.
  325. tank.Draw(spriteBatch);
  326. // Draw a gamertag label.
  327. string label = gamer.Gamertag;
  328. Color labelColor = Color.Black;
  329. Vector2 labelOffset = new Vector2(100, 150);
  330. if (gamer.IsHost)
  331. label += " (host)";
  332. // Flash the gamertag to yellow when the player is talking.
  333. if (gamer is NetworkGamer networkGamer && networkGamer.IsTalking)
  334. labelColor = Color.Yellow;
  335. spriteBatch.DrawString(font, label, tank.Position, labelColor, 0,
  336. labelOffset, 0.6f, SpriteEffects.None, 0);
  337. }
  338. }
  339. if (UIUtility.IsMobile)
  340. {
  341. // TODO GamePad.Draw(gameTime, spriteBatch);
  342. }
  343. spriteBatch.End();
  344. }
  345. /// <summary>
  346. /// Helper draws notification messages before calling blocking network methods.
  347. /// </summary>
  348. void DrawMessage(string message)
  349. {
  350. if (!BeginDraw())
  351. return;
  352. GraphicsDevice.Clear(Color.MonoGameOrange);
  353. spriteBatch.Begin();
  354. spriteBatch.DrawString(font, message, new Vector2(161, 161), Color.Black);
  355. spriteBatch.DrawString(font, message, new Vector2(160, 160), Color.White);
  356. spriteBatch.End();
  357. EndDraw();
  358. }
  359. /// <summary>
  360. /// Handles input.
  361. /// </summary>
  362. private void HandleInput()
  363. {
  364. previousKeyboardState = currentKeyboardState;
  365. previousGamePadState = currentGamePadState;
  366. currentKeyboardState = Keyboard.GetState();
  367. currentGamePadState = GamePad.GetState(PlayerIndex.One);
  368. currentTouchState = TouchPanel.GetState();
  369. // Check for exit.
  370. if (IsActive && IsPressed(Keys.Escape, Buttons.Back))
  371. {
  372. // Phase 2: Gracefully leave session before exiting
  373. if (networkSession != null)
  374. {
  375. Console.WriteLine("[GAME] Gracefully leaving session...");
  376. networkSession.Leave("User quit");
  377. networkSession = null;
  378. }
  379. Exit();
  380. }
  381. // Only test of Menu touches when networkSession is null
  382. if (networkSession == null)
  383. {
  384. // Doing very very basic touch detection for menu
  385. if (currentTouchState.Count > 0)
  386. {
  387. Console.WriteLine(string.Format("X:{0}, Y:{1}", currentTouchState[0].Position.X, currentTouchState[0].Position.Y));
  388. if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 160)
  389. && (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 190))
  390. {
  391. CreateSession(NetworkSessionType.SystemLink);
  392. }
  393. if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 200)
  394. && (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 230))
  395. {
  396. CreateSession(NetworkSessionType.PlayerMatch);
  397. }
  398. if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 240)
  399. && (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 270))
  400. {
  401. JoinSession(NetworkSessionType.PlayerMatch);
  402. }
  403. if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 280)
  404. && (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 310))
  405. {
  406. JoinSession(NetworkSessionType.SystemLink);
  407. }
  408. }
  409. }
  410. }
  411. /// <summary>
  412. /// Checks if the specified button was just pressed (not held) on either keyboard or gamepad.
  413. /// </summary>
  414. bool IsPressed(Keys key, Buttons button)
  415. {
  416. // Check for key/button transition: was up last frame, is down this frame
  417. bool keyJustPressed = currentKeyboardState.IsKeyDown(key) && !previousKeyboardState.IsKeyDown(key);
  418. bool buttonJustPressed = currentGamePadState.IsButtonDown(button) && !previousGamePadState.IsButtonDown(button);
  419. return keyJustPressed || buttonJustPressed;
  420. }
  421. /// <summary>
  422. /// Reads input data from keyboard and gamepad, and stores
  423. /// it into the specified tank object.
  424. /// </summary>
  425. void ReadTankInputs(Tank tank, PlayerIndex playerIndex)
  426. {
  427. // Read the gamepad.
  428. GamePadState gamePad = GamePad.GetState(playerIndex);
  429. Vector2 tankInput = gamePad.ThumbSticks.Left;
  430. Vector2 turretInput = gamePad.ThumbSticks.Right;
  431. // Read the keyboard.
  432. KeyboardState keyboard = Keyboard.GetState();
  433. if (keyboard.IsKeyDown(Keys.Left))
  434. tankInput.X = -1;
  435. else if (keyboard.IsKeyDown(Keys.Right))
  436. tankInput.X = 1;
  437. if (keyboard.IsKeyDown(Keys.Up))
  438. tankInput.Y = 1;
  439. else if (keyboard.IsKeyDown(Keys.Down))
  440. tankInput.Y = -1;
  441. if (keyboard.IsKeyDown(Keys.A))
  442. turretInput.X = -1;
  443. else if (keyboard.IsKeyDown(Keys.D))
  444. turretInput.X = 1;
  445. if (keyboard.IsKeyDown(Keys.W))
  446. turretInput.Y = 1;
  447. else if (keyboard.IsKeyDown(Keys.S))
  448. turretInput.Y = -1;
  449. // Normalize the input vectors.
  450. if (tankInput.Length() > 1)
  451. tankInput.Normalize();
  452. if (turretInput.Length() > 1)
  453. turretInput.Normalize();
  454. // Store these input values into the tank object.
  455. tank.TankInput = tankInput;
  456. tank.TurretInput = turretInput;
  457. }
  458. }
  459. }