Click here to Skip to main content
15,886,019 members
Articles / Desktop Programming / WPF

420 Frames Per Second

Rate me:
Please Sign up or sign in to vote.
4.91/5 (31 votes)
10 May 2013CPOL8 min read 53.7K   2.7K   56  
This article presents a class to manage enumerator based animations in WPF that can deal with different framerated animations independent of the hardware framerate
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace BubbleShot.Net
{
	public partial class WindowMain : Window
	{
		public const int CanvasHeight = 600;
		public const int CanvasWidth = 520;
		public const int BubbleWeight = 40;
		public const int BubbleRadious = BubbleWeight/2;
		public const int BubbleVerticalSpace = 35;
		public const int BubbleCollisionIndex = 33;
	
		internal static UIElementCollection _children;
		private readonly RadialGradientBrush[] _brushes = new RadialGradientBrush[8];
		private readonly Random _random = new Random();
		private Ellipse _bubble;
		private readonly BubbleGrid _grid = new BubbleGrid();
		
		private string[] _levels;
		private int _level;
	
		public WindowMain()
		{
			InitializeComponent();
			
			var children = canvas.Children;
			_children = children;

			for (int i = 0; i < 8; i++)
				_brushes[i] = _CreateBrush((i & 1) == 1, (i & 2) == 2, (i & 4) == 4);
				
			_levels = Directory.GetFiles("Levels");
			_LoadLevel();
			_CreateNextBubble();
			_ShowAngle(Math.PI/2);
		}
		
		private void _LoadLevel()
		{
			string name = _levels[_level];
			
			int bubbleCount = _children.Count;
			for(int i=bubbleCount-1; i>=0; i--)
				if (_children[i] is Ellipse)
					_children.RemoveAt(i);
			
			_grid.Clear();
			
			using(var file = File.OpenText(name))
			{
				for(int lineIndex=0; lineIndex<12; lineIndex++)
				{
					var textLine = file.ReadLine();
					if (textLine == null)
						return;
						
					var bubbleLine = _grid._lines[lineIndex];
					int lineCount = Math.Min(textLine.Length, bubbleLine.Length);
					for(int columnIndex=0; columnIndex<lineCount; columnIndex++)
					{
						char c = textLine[columnIndex];
						if (c < '0' || c > '7')
							continue;
							
						int colorValue = c - '0';
						var bubble = _CreateBubble(colorValue);
						_grid.Add(columnIndex, lineIndex, bubble);
						_children.Add(bubble);
					}
				}
			}
		}
		
		private void _CreateNextBubble()
		{
			_bubble = _CreateBubble();
			Canvas.SetTop(_bubble, CanvasHeight - BubbleWeight);
			Canvas.SetLeft(_bubble, (CanvasWidth-BubbleWeight)/2);
			canvas.Children.Add(_bubble);
		}
		
		private Ellipse _CreateBubble()
		{
			var brushes = _GetAvailableBubbleColors();
			int index = _random.Next(brushes.Count);
			var result = _CreateBubble(brushes[index]);
			return result;
		}
		private Ellipse _CreateBubble(int index)
		{
			var brush = _brushes[index];
			var result = _CreateBubble(brush);
			return result;
		}
		private Ellipse _CreateBubble(Brush brush)
		{
			var result = new Ellipse();
			result.Width = BubbleWeight;
			result.Height = BubbleWeight;
			result.Fill = brush;
			return result;
		}
		private RadialGradientBrush _CreateBrush(bool red, bool green, bool blue)
		{
			var brush = new RadialGradientBrush();
			brush.Center = new Point(0.5, 0.5);
			brush.GradientOrigin = new Point(0.3, 0.3);

			var gradientStops = brush.GradientStops;
			gradientStops.Add(new GradientStop(Colors.White, 0));
			gradientStops.Add(new GradientStop(_CreateColor(red, green, blue, 0xEE), 0.25));
			gradientStops.Add(new GradientStop(_CreateColor(red, green, blue, 0xBB), 0.7));
			gradientStops.Add(new GradientStop(_CreateColor(red, green, blue, 0x55), 1));
			brush.Freeze();
			return brush;
		}
		private Color _CreateColor(bool red, bool green, bool blue, byte valueForTrue)
		{
			byte redIndex = 0;
			byte greenIndex = 0;
			byte blueIndex = 0;
			
			if (red || green || blue)
			{
				if (red)
					redIndex = valueForTrue;

				if (green)
					greenIndex = valueForTrue;

				if (blue)
					blueIndex = valueForTrue;
			}
			else
			{
				redIndex = (byte)(valueForTrue / 3);
				greenIndex = redIndex;
				blueIndex = redIndex;
			}
				
			var result = Color.FromArgb(255, redIndex, greenIndex, blueIndex);
			return result;
		}
		
		private void canvas_MouseMove(object sender, MouseEventArgs e)
		{
			var position = e.MouseDevice.GetPosition(canvas);
			double x = position.X - CanvasWidth/2;
			double y = CanvasHeight - BubbleRadious - position.Y;
			var angle = Math.Atan2(y, x);
			_ShowAngle(angle);
		}
		
		private void _ShowAngle(double angle)
		{
			double centerX = Math.Cos(angle) * 50;
			double centerY = -Math.Sin(angle) * 50;

			centerX += CanvasWidth/2;
			centerY += CanvasHeight - BubbleRadious;

			Canvas.SetLeft(angleSquare, centerX - 10);
			Canvas.SetTop(angleSquare, centerY - 10);
			rotateTransform.Angle = -angle * 180 / Math.PI;
		}

		private readonly TimeSpan _420FramesPerSecond = TimeSpan.FromMilliseconds(1000.0/60.0/7.0);
		private void canvas_MouseUp(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;

			var position = e.MouseDevice.GetPosition(canvas);
			double x = position.X - CanvasWidth/2;
			double y = CanvasHeight - BubbleRadious - position.Y;

			var angle = Math.Atan2(y, x);
			double xSpeed = Math.Cos(angle);
			double ySpeed = Math.Sin(angle);
			
			if (ySpeed <= 0)
				return;

			IsEnabled = false;
			var animation = _Animation(xSpeed, ySpeed);
			CompositionTargetAnimationManager.AddAnimation(animation, _420FramesPerSecond, null);
		}

		private void _EndGame()
		{
			_LoadLevel();
		}
		
		private IEnumerator<bool> _Animation(double xSpeed, double ySpeed)
		{
			try
			{
				double top = CanvasHeight-BubbleWeight;
				double left = (CanvasWidth-BubbleWeight)/2;
				while(top > 0)
				{
					top -= ySpeed;
					
					double oldLeft = left;
					left += xSpeed;
					if (left < 0 || left > CanvasWidth-BubbleWeight)
					{
						xSpeed = -xSpeed;
						left = oldLeft + xSpeed;
					}
					
					if (_grid.Collides((int)left, (int)top))
						yield break;

					Canvas.SetLeft(_bubble, left);
					Canvas.SetTop(_bubble, top);
					
					yield return true;
				}
			}
			finally
			{
				var bubbleStatus = _grid.AddOrBlow(_bubble);

				switch (bubbleStatus)
				{
					case BubbleStatus.GameOver:
						_EndGame();
						break;

					case BubbleStatus.SomeBlown:
						if (!_children.OfType<Ellipse>().Any())
						{
							_level++;
							if (_level >= _levels.Length)
								_level = 0;

							_LoadLevel();
						}
						break;
				}

				IsEnabled = true;
				_CreateNextBubble();
			}
		}
		
		private List<RadialGradientBrush> _GetAvailableBubbleColors()
		{
			var result = new List<RadialGradientBrush>();
			foreach(var child in _children)
			{
				var ellipse = child as Ellipse;
				if (ellipse == null)
					continue;
					
				var brush =_GetBrushByBubble(ellipse);
				//if (!result.Contains(brush)) add again, so the more it has, 
				//the more chances it will appear.
					result.Add(brush);
			}
			return result;
		}
		
		private RadialGradientBrush _GetBrushByBubble(Ellipse ellipse)
		{
			var brushOrCopy = (RadialGradientBrush)ellipse.Fill;
			return _GetBrushByColor(brushOrCopy.GradientStops[1].Color);
		}
		private RadialGradientBrush _GetBrushByColor(Color color)
		{
			foreach(var brush in _brushes)
				if (brush.GradientStops[1].Color == color)
					return brush;
					
			throw new ApplicationException("Can't find brush.");
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions