PageRenderTime 49ms CodeModel.GetById 23ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/Samples/Breakout/Board.cs

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