Разработка игры на WPF

21.06.2014 at 12:00

Вчера на работе прошел второй хакатон. На этот раз темой был рабочий проект — все, что давно хотелось сделать, но в спринт никогда не попадет. Мы с Сашей Решетниковым решили делать игру, которую можно будет встроить в клиентское приложение в виде пасхального яйца.

Идея игры

Решили делать ремейк старой игры «Попади в меня кирпич».
oldbrick
Геймплей прост — игрок должен уворачиваться от кирпичей. Двигаться можно влево и вправо. Кирпичи отталкиваются друг от друга, от стен и от игрока.

Переключение экранов

Для простоты встраивания вся игра состоит из одного UserControl. Размер контрола захардкодили, так как это размера очень сильно зависит сложность игры.
Управление экранами игры очень простое — видимому экрану игры ставится Visibility = Visible, остальным — Collapsed.
Непосредственно игровой экран содержит Canvas, на котором и рисуются игровые объекты.

Обработка действий пользователя

При старте игры запускается таймер, который обрабатывает состояние игровых объектов и перерисовывает игру.
Обработчики нажатия кнопок навешиваются на окно, а не на контрол. Это нужно для избежания проблем с фокусом.
При старте игры обработчики навешиваются, при проигрыше — обработчики снимаются.

/// <summary>
/// Начать игру.
/// </summary>
private void StartGame()
{
  // Показываем игровой экран.
  this.GameMenu.Visibility = System.Windows.Visibility.Collapsed;
  this.GameGrid.Visibility = System.Windows.Visibility.Visible;
  // Навешиваем обработчики кнопок.
  Application.Current.MainWindow.KeyDown += this.GameCanvasKeyDown;
  Application.Current.MainWindow.KeyUp += this.GameCanvasKeyUp;      
  // Инициализируем стартовые значения.
  this.scores = 0;
  this.lives = 5;
  this.InitPlayer();
  this.InitBricks();
  // Запускаем таймер.
  this.timer.Start();
}

/// <summary>
/// Завершить игру.
/// </summary>
private void EndGame()
{
  // Останавливаем таймер.
  this.timer.Stop();
  // Очищаем данные.
  this.GameCanvas.Children.Clear();  
  this.playerDirection = Direction.None;
  // Показываем экран с результатами.
  this.GameGrid.Visibility = System.Windows.Visibility.Collapsed;
  this.GameScoreBoard.Visibility = System.Windows.Visibility.Visible;
  // Снимаем обработчики.
  Application.Current.MainWindow.KeyDown -= this.GameCanvasKeyDown;
  Application.Current.MainWindow.KeyUp -= this.GameCanvasKeyUp;
  
}

/// <summary>
/// Обновить направление движения игрока.
/// </summary>
private void UpdateDirection()
{
  if (Keyboard.IsKeyDown(Key.Left))
	this.playerDirection = Direction.Left;
  else
	if (Keyboard.IsKeyDown(Key.Right))
	  this.playerDirection = Direction.Right;
	else
	  this.playerDirection = Direction.None;
}

/// <summary>
/// Обновить координаты игрока.
/// </summary>
private void UpdateCoordinates()
{
  // Обработка направления движения.
  switch (playerDirection)
  {
	case Direction.Left:
	  playerCoordinates.X -=  GameSettings.PlayerVelocityPerTick;
	  break;
	case Direction.Right:
	  playerCoordinates.X += GameSettings.PlayerVelocityPerTick;
	  break;
  }
  // Обработка столкновений со стенами.
  if (playerCoordinates.X < GameSettings.PlayerRadius)
	playerCoordinates.X = GameSettings.PlayerRadius;
  if (playerCoordinates.X > GameSettings.GameWidth - GameSettings.PlayerRadius)
	playerCoordinates.X = GameSettings.GameWidth - GameSettings.PlayerRadius;
}	

Коллизии и отталкивания

У каждого кирпича хранятся координаты левого верхнего угла. Кирпичи столкнулись, если расстояние по вертикали между ними меньше высоты кирпича и расстояние по горизонтали меньше ширины кирпича.
Столкновения тоже сделали по простому — при столкновении кирпичи обмениваются векторами скоростей. Это конечно некорректно с точки зрения физики, но на сложные расчеты скоростей нам не хватило времени. Да и в оригинальной игре вроде так же сделано.

if (Math.Abs(brick.X - otherbrick.X) < sizeX && 
  Math.Abs(brick.Y - otherbrick.Y) < sizeY)
{
  var otherdx = otherbrick.dX;
  var otherdy = otherbrick.dY;
  otherbrick.dX = brick.dX;
  otherbrick.dY = brick.dY;
  brick.dX = otherdx;
  brick.dY = otherdy;
}

Результат

В итоге у нас получилась вот такая вот игра:
newbrick