PageRenderTime 27ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 1ms

/Samples/Breakout/Board.cs

#
C# | 469 lines | 336 code | 43 blank | 90 comment | 39 complexity | 8412313468fbabab2235ad7b231e9f23 MD5 | raw file
Possible License(s): Apache-2.0
  1. using Delta.ContentSystem.Rendering;
  2. using Delta.ContentSystem.Rendering.Helpers;
  3. using Delta.Engine;
  4. using Delta.InputSystem;
  5. using Delta.Rendering.Basics.Materials;
  6. using Delta.Rendering.Effects.Modifiers;
  7. using Delta.Rendering.Enums;
  8. using Delta.Utilities.Datatypes;
  9. using Delta.Utilities.Helpers;
  10. using Delta.Utilities.Math;
  11. namespace Breakout
  12. {
  13. /// <summary>
  14. /// Board, which handles most of the high level game logic for this game.
  15. /// </summary>
  16. public class Board
  17. {
  18. #region Constants
  19. private const int StartLives = 3;
  20. private const int StartLevel = 1;//2;//1;
  21. private const float StartBallSpeedY = -1.0f;
  22. private const float BallSpeedYIncreaseFactor = 1.015f;
  23. private const float BallSpeedXIncreaseFactor = 2.5f;
  24. /// <summary>
  25. /// Name of the move left command for player paddle.
  26. /// </summary>
  27. private const string PaddleMoveLeftDigital = "PaddleMoveLeftDigital";
  28. /// <summary>
  29. /// Name of the move right command for player paddle.
  30. /// </summary>
  31. private const string PaddleMoveRightDigital = "PaddleMoveRightDigital";
  32. /// <summary>
  33. /// Name of the move command for player paddle per mouse or touch.
  34. /// </summary>
  35. private const string PaddleMoveDirect = "PaddleMoveDirect";
  36. /// <summary>
  37. /// Name of the command to fire off the ball.
  38. /// </summary>
  39. private const string FireBall = "FireBall";
  40. #endregion
  41. #region Public
  42. /// <summary>
  43. /// Paddle of the player
  44. /// </summary>
  45. public Paddle PlayerPaddle
  46. {
  47. get;
  48. private set;
  49. }
  50. /// <summary>
  51. /// Ball for the game
  52. /// </summary>
  53. public Ball Ball
  54. {
  55. get;
  56. private set;
  57. }
  58. /// <summary>
  59. /// Number of bricks alive
  60. /// </summary>
  61. public int LivingBricksCount
  62. {
  63. get;
  64. private set;
  65. }
  66. /// <summary>
  67. /// Bricks array in 2D
  68. /// </summary>
  69. public Brick[,] Bricks
  70. {
  71. get;
  72. private set;
  73. }
  74. /// <summary>
  75. /// Level
  76. /// </summary>
  77. public int Level
  78. {
  79. get;
  80. private set;
  81. }
  82. /// <summary>
  83. /// Is game over?
  84. /// </summary>
  85. public bool GameOver;
  86. /// <summary>
  87. /// Number of lives left.
  88. /// </summary>
  89. public int Lives;
  90. /// <summary>
  91. /// Current score
  92. /// </summary>
  93. public int Score;
  94. /// <summary>
  95. /// Number of bricks destroyed so far.
  96. /// </summary>
  97. public int DestroyedBlocks;
  98. #endregion
  99. #region Private
  100. private float brickStepX;
  101. private float brickStepY;
  102. private EffectData effectTemplate;
  103. private float paddleInputX;
  104. private bool releaseBall;
  105. private long gameStartTimeMs;
  106. #endregion
  107. #region Constructor
  108. /// <summary>
  109. /// Create board
  110. /// </summary>
  111. public Board()
  112. {
  113. StartGame();
  114. // Setup the visual collision effect for the ball
  115. var collision = new Material2DColored("BreakoutEffect");
  116. effectTemplate = EffectData.Get("<BrickDestroyEffect>");
  117. EmitterData emitter = new EmitterData();
  118. emitter.Modifiers.Add(new SpawnOnceModifier());
  119. emitter.Modifiers.Add(new SizeModifier()
  120. {
  121. WidthRange = new Range(1.0f, 4.0f),
  122. HeightRange = new Range(1.0f, 4.0f),
  123. });
  124. emitter.Modifiers.Add(new FadeModifier()
  125. {
  126. AlphaFade = new Range(1.0f, 0.0f),
  127. });
  128. emitter.Modifiers.Add(new MaterialModifier()
  129. {
  130. BillboardMode = BillboardMode.Only2D,
  131. Material = collision,
  132. });
  133. effectTemplate.Emitters.Add(emitter);
  134. RegisterInputCommands();
  135. }
  136. #endregion
  137. #region RegisterInputCommands
  138. /// <summary>
  139. /// Register input commands
  140. /// </summary>
  141. private void RegisterInputCommands()
  142. {
  143. // Get the commands we have in the system already
  144. CommandManager commands = Input.Commands;
  145. #region Keyboard and GamePad steering
  146. commands[PaddleMoveLeftDigital].Add(this, delegate(CommandTrigger input)
  147. {
  148. paddleInputX -= 1.5f * Time.Delta;
  149. });
  150. commands[PaddleMoveRightDigital].Add(this, delegate(CommandTrigger input)
  151. {
  152. paddleInputX += 1.5f * Time.Delta;
  153. });
  154. #endregion
  155. #region Mouse and Touch steering
  156. commands[PaddleMoveDirect].Add(this, delegate(CommandTrigger input)
  157. {
  158. paddleInputX = input.Position.X;
  159. });
  160. #endregion
  161. #region Fire off the ball
  162. commands[FireBall].Add(this, delegate
  163. {
  164. // Quick fix to avoid that ball will be shoot automatically with the
  165. // same 'Released' state every time the "game restart" question was
  166. // confirmed
  167. if (Time.Milliseconds - gameStartTimeMs > 100)
  168. {
  169. releaseBall = true;
  170. } // if
  171. });
  172. #endregion
  173. }
  174. #endregion
  175. #region StartGame
  176. /// <summary>
  177. /// Start game
  178. /// </summary>
  179. public void StartGame()
  180. {
  181. Score = 0;
  182. DestroyedBlocks = 0;
  183. Lives = StartLives;
  184. Level = StartLevel;
  185. paddleInputX = 0.5f;
  186. NextLevel();
  187. SetupGame();
  188. gameStartTimeMs = Time.Milliseconds;
  189. }
  190. #endregion
  191. #region GetBrickByScreenPosition
  192. /// <summary>
  193. /// Get brick by screen position, used for collision handling.
  194. /// </summary>
  195. /// <param name="x">X screen position between 0.0 and 1.0</param>
  196. /// <param name="y">Y screen position between 0.0 and 0.5</param>
  197. /// <returns>Found brick (can return null if y is too much)</returns>
  198. public Brick GetBrickByScreenPosition(float x, float y)
  199. {
  200. if (y > 0.5f)
  201. {
  202. return null;
  203. }
  204. return Bricks[(int)(x / brickStepX), (int)(y / brickStepY)];
  205. }
  206. #endregion
  207. #region NextLevel
  208. /// <summary>
  209. /// Proceed to the next level
  210. /// </summary>
  211. public void NextLevel()
  212. {
  213. // Update application title with the current level
  214. Application.Window.Title = "Delta Engine - Breakout - Level " + Level;
  215. // Always increase the level size
  216. int rows = Level + 1;
  217. int columns = Level + 1;
  218. Bricks = new Brick[rows, columns];
  219. LivingBricksCount = rows * columns;
  220. brickStepX = 1f / rows;
  221. brickStepY = 0.5f / columns;
  222. for (int x = 0; x < Bricks.GetLength(0); x++)
  223. {
  224. for (int y = 0; y < Bricks.GetLength(1); y++)
  225. {
  226. Bricks[x, y] = new Brick();
  227. Bricks[x, y].Bounds = new Rectangle(x * brickStepX,
  228. y * brickStepY, brickStepX, brickStepY);
  229. }
  230. }
  231. LevelData.SetupLevelColors(Bricks, Level);
  232. // And finally increase the level number for next time
  233. Level++;
  234. }
  235. #endregion
  236. #region RemoveBrick
  237. /// <summary>
  238. /// Remove certain bricks (will set its IsDead and remove one from the
  239. /// LivingBricksCount).
  240. /// </summary>
  241. /// <param name="brick">Brick to remove</param>
  242. public void RemoveBrick(Brick brick)
  243. {
  244. brick.IsDead = true;
  245. LivingBricksCount--;
  246. //obs, too much: Effect.Spawn(effectTemplate, brick.Bounds.Center);
  247. }
  248. #endregion
  249. #region SpawnEffect
  250. /// <summary>
  251. /// Spawns a new 2D effect at the given position.
  252. /// </summary>
  253. /// <param name="position">Position to spawn 2D effect.</param>
  254. public void SpawnEffect(Point position)
  255. {
  256. //HACK: Disabled right now, causes too much headache
  257. //Effect.Spawn(effectTemplate, position);
  258. }
  259. #endregion
  260. #region SetupGame
  261. private void SetupGame()
  262. {
  263. PlayerPaddle = new Paddle();
  264. Ball = new Ball();
  265. }
  266. #endregion
  267. #region IncreaseScore
  268. private void IncreaseScore()
  269. {
  270. Score += DestroyedBlocks + Level;
  271. DestroyedBlocks++;
  272. SpawnEffect(Ball.Position);
  273. }
  274. #endregion
  275. #region UpdateGame
  276. /// <summary>
  277. /// This is the most complex method of this game, it handles all collision
  278. /// detection and most of the game logic, input and updating of the paddle,
  279. /// ball and bricks of the game.
  280. /// </summary>
  281. public void UpdateGame()
  282. {
  283. #region Paddle
  284. // We are using command delegates to change the paddle position
  285. // Clamp it to the screen, so the paddle won't move outside the window.
  286. paddleInputX = MathHelper.Clamp(paddleInputX, PlayerPaddle.Size.WidthHalf,
  287. 1.0f - PlayerPaddle.Size.WidthHalf);
  288. // And update the paddle position.
  289. PlayerPaddle.Position = new Point(paddleInputX, PlayerPaddle.Position.Y);
  290. #endregion
  291. if (Ball.IsOnPaddle)
  292. {
  293. #region Ball release
  294. Ball.Position = new Point(PlayerPaddle.Position.X,
  295. PlayerPaddle.Position.Y - Ball.Radius);
  296. // Should be Ok for both of them.
  297. if (releaseBall)
  298. {
  299. Ball.IsOnPaddle = false;
  300. if (Ball.Velocity.X == 0f &&
  301. Ball.Velocity.Y == 0f)
  302. {
  303. float randomXSpeed = RandomHelper.RandomFloat(-0.5f, 0.5f) / 5f;
  304. if (randomXSpeed == 0f)
  305. {
  306. randomXSpeed = 0.01f;
  307. }
  308. Ball.Velocity = new Point(randomXSpeed, StartBallSpeedY);
  309. }
  310. releaseBall = false;
  311. }
  312. #endregion
  313. }
  314. else
  315. {
  316. // Update the ball movement
  317. Ball.Position += (Ball.Velocity * Time.Delta);
  318. #region Ball wall collision
  319. if (Ball.Position.X >= 1f - Ball.Radius)
  320. {
  321. Ball.AbsXSpeed(true);
  322. SpawnEffect(Ball.Position);
  323. }
  324. else if (Ball.Position.X <= Ball.Radius)
  325. {
  326. Ball.AbsXSpeed(false);
  327. SpawnEffect(Ball.Position);
  328. }
  329. else if (Ball.Position.Y >= 1.0f - Ball.Radius)
  330. {
  331. Ball = new Ball();
  332. Lives--;
  333. DestroyedBlocks /= 2;
  334. if (Lives == 0)
  335. {
  336. GameOver = true;
  337. }
  338. }
  339. else if (Ball.Position.Y <= Ball.Radius)
  340. {
  341. Ball.AbsYSpeed(false);
  342. SpawnEffect(Ball.Position);
  343. }
  344. #endregion
  345. else
  346. {
  347. #region Paddle collision
  348. if (Ball.Position.Y + Ball.Radius > PlayerPaddle.Position.Y &&
  349. Ball.Velocity.Y > 0)
  350. {
  351. if (Ball.Position.X + Ball.Radius >
  352. PlayerPaddle.Position.X - 0.1f &&
  353. Ball.Position.X - Ball.Radius < PlayerPaddle.Position.X + 0.1f)
  354. {
  355. Score++;
  356. SpawnEffect(Ball.Position-new Point(0, 0.1f));
  357. Ball.AbsYSpeed(true);
  358. float distance = Ball.Position.X - PlayerPaddle.Position.X;
  359. Ball.Velocity = new Point(
  360. Ball.Velocity.X + distance * BallSpeedXIncreaseFactor,
  361. Ball.Velocity.Y * BallSpeedYIncreaseFactor);
  362. if (Ball.Velocity.X > 5.0f)
  363. {
  364. Ball.Velocity = new Point(5.0f, Ball.Velocity.Y);
  365. }
  366. if (Ball.Velocity.X < -5.0f)
  367. {
  368. Ball.Velocity = new Point(-5.0f, Ball.Velocity.Y);
  369. }
  370. if (Ball.Velocity.Y < -5.0f)
  371. {
  372. Ball.Velocity = new Point(Ball.Velocity.X, -5.0f);
  373. }
  374. }
  375. }
  376. #endregion
  377. #region Bricks collision
  378. Brick colPongUpper = GetBrickByScreenPosition(
  379. Ball.Position.X, Ball.Position.Y - Ball.Radius);
  380. if (colPongUpper != null &&
  381. colPongUpper.IsDead == false)
  382. {
  383. IncreaseScore();
  384. RemoveBrick(colPongUpper);
  385. Ball.AbsYSpeed(false);
  386. }
  387. Brick colPongLower = GetBrickByScreenPosition(
  388. Ball.Position.X, Ball.Position.Y + Ball.Radius);
  389. if (colPongLower != null &&
  390. colPongLower.IsDead == false)
  391. {
  392. IncreaseScore();
  393. RemoveBrick(colPongLower);
  394. Ball.AbsYSpeed(true);
  395. }
  396. Brick colPongRight = GetBrickByScreenPosition(
  397. Ball.Position.X + Ball.Radius, Ball.Position.Y);
  398. if (colPongRight != null &&
  399. colPongRight.IsDead == false)
  400. {
  401. IncreaseScore();
  402. RemoveBrick(colPongRight);
  403. Ball.AbsXSpeed(true);
  404. }
  405. Brick colPongLeft = GetBrickByScreenPosition(
  406. Ball.Position.X - Ball.Radius, Ball.Position.Y);
  407. if (colPongLeft != null &&
  408. colPongLeft.IsDead == false)
  409. {
  410. IncreaseScore();
  411. RemoveBrick(colPongLeft);
  412. Ball.AbsXSpeed(false);
  413. }
  414. #endregion
  415. if (LivingBricksCount == 0)
  416. {
  417. Lives++;
  418. NextLevel();
  419. SetupGame();
  420. }
  421. }
  422. }
  423. }
  424. #endregion
  425. }
  426. }