В этойстатье мы опишем самые главные компоненты игры, это модель самой игры и представление игры. После чего мы, я надеюсь, с удовольствием сможем поиграть в нашу игру типа Packman полностью основанную на паттерне MVC.
Модель в шаблоне Model-View-Controller
Начнём с программирования модели игры. Модель является «сутью» нашей игры и отвечает за непосредственные алгоритмы, расчёты и тому подобное внутреннее устройство игры. Обычно под Моделью понимается, часть содержащая в себе функциональную логику приложения, иными словами то, что мы обычно называем «Business Layer», «Бизнес-слой» или «Слой бизнес логики». Как именно организован этот слой, по большому счету не важно, однако есть ряд ключевых моментов, на которых мы остановимся позднее. Основная цель паттерна - сделать так, чтобы Модель была полностью независима от остальных частей и практически ничего не знала об их существовании, что позволило бы менять и Контроллер и Представление модели, не трогая саму Модель и даже позволить функционирование нескольких экземпляров Представлений и Контроллеров с одной Моделью одновременно. Вследствие чего, Модель ни при каких условиях не может содержать ссылок на объекты Представления или Контроллера.
Обычно различают несколько типов паттернов в зависимости от роли модели.
Passive Model (пассивная модель) - Модель не имеет вообще никаких способов воздействовать на Представление или Контроллер и только используется ими в качестве источника данных для отображения. Все изменения модели отслеживаются Контроллером и он же отвечает за перерисовку Представления, если это необходимо.
Active Model (активная модель) - Модель имеет возможность оповестить Представление о том, что в ней произошли некие изменения, и Представление может эти изменения отобразить. Как правило, механизм оповещения реализуется на основе паттерна Observer (обозреватель), Модель просто бросает сообщение, а Представления, которые заинтересованы в оповещении, подписываются на эти сообщения, что позволяет сохранить независимость Модели, как от Контроллера так и от Представления, не нарушая тем самым основного свойства паттерна. Классической реализацией паттерна MVC принято считать версию именно с активной Моделью.
Программирование модели игры типа Packman
Мы будем программировать модель нашей игры как активную модель. Для начала создадим массивы объектов игры, таких как яблоки, танки, стены и объект пакмена. Все классы этих сущностей описаны в статьях ранне. В техническом задании было сказано, что каждый объект должен существовать в своём потоке. Поэтому далее мы создаём для каждого движущегося объекта свой поток. Затем наконец-то пишем самый главный метод Start(). Также нам необходимо написать метод проверки на пересечение объектов, и обработчик события перемещение объекта в заданные координаты, соответственно нам нужно подписать каждый элемент с каждым на пересечение и написать. Размещение стен происходит в методе PlaceWalls() в соответствии с картой, заданной массивом GameForm.map. А теперь посмотрим на сам класс.
namespace PackMan { /// <summary> /// Класс игры. /// </summary> class Game { #region Объекты елементов и их свойства /// <summary> /// Список танков /// </summary> List<Tank> arrTank = new List<Tank>(); /// <summary> /// Список стен /// </summary> List<Wall> arrWall = new List<Wall>(); /// <summary> /// Список яблок /// </summary> List<Apple> arrApple = new List<Apple>(); /// <summary> /// Пакмен. /// </summary> PackMan packMan; /// <summary> /// Свойство списка яблок,доступное только для чтения /// </summary> public List<Apple> ArrApple { get { return arrApple; } } /// <summary> /// Свойство списка стен,доступное только для чтения /// </summary> public List<Wall> ArrWall { get { return arrWall; } } /// <summary> /// Свойство пакмена,доступное только для чтения /// </summary> public PackMan PackMan { get { return packMan; } } /// <summary> /// Свойство списка танков,доступное только для чтения /// </summary> public List<Tank> ArrTank { get { return arrTank; } } #endregion #region Вспомогательные поля класса /// <summary> /// Список потоков,в которых запускаются объекты /// </summary> List<Thread> arrThread = new List<Thread>(); /// <summary> /// Поток в котором запускается пакмен. /// </summary> private Thread packManThread; /// <summary> /// Очки. /// </summary> private static int score = 0; /// <summary> /// Свойство очков. /// </summary> public static int Score { get { return Game.score; } set { Game.score = value; } } #endregion /// <summary> /// Конструктор /// </summary> public Game() { //Создание стен for (int i = 0; i < GameForm.countWall; i++) { Rectangle rect = new Rectangle(); arrWall.Add(new Wall(rect.Location)); } PlaceWalls(); //Создание танков for (int i = 0; i < GameForm.countTank; i++) { Rectangle rect = new Rectangle(); do { rect = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new Tank()).Width, (new Tank()).Height); } while (Collides(rect)); arrTank.Add(new Tank(rect.Location)); Thread thread = new Thread(new ThreadStart(arrTank[i].Run)); arrThread.Add(thread); } //Создание яблок for (int i = 0; i < GameForm.countApples; i++) { Rectangle rect = new Rectangle(); do { rect = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new Apple()).Width, (new Apple()).Height); } while (Collides(rect)); arrApple.Add(new Apple(rect.Location)); } Rectangle rect2 = new Rectangle(); do { rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new PackMan()).Width, (new PackMan()).Height); } while (Collides(rect2)); //Создание пакмена. packMan = new PackMan(rect2.Location); packManThread = new Thread(new ThreadStart(PackMan.Run)); SubscribePos(); } /// <summary> /// Начало игры /// </summary> public void Start() { foreach (Thread thread in arrThread) { thread.Start(); } packManThread.Start(); } /// <summary> /// Размещение стен в соответствии с картой,заданной массивом GameForm.map /// </summary> private void PlaceWalls() { int cur = 0; for (int i = 0; i < GameForm.map.Length; i++) for (int j = 0; j < GameForm.map[0].Length; j++) if (GameForm.map[i][j] == '*') { arrWall[cur].Position = new Point(i * arrWall[cur].Width, j * arrWall[cur].Height); cur++; } } /// <summary> /// Проверка на пересечение объектов /// </summary> /// <param name="rect"></param> /// <returns></returns> private bool Collides(Rectangle rect) { if (rect.Left < 0 || rect.Right >= GameForm.MAXX || rect.Top < 0 || rect.Bottom >= GameForm.MAXY) return true; for (int i = 0; i < arrTank.Count; i++) { if (arrTank[i].CollidesWith(rect)) return true; } for (int i = 0; i < arrWall.Count; i++) { if (arrWall[i].CollidesWith(rect)) return true; } for (int i = 0; i < arrApple.Count; i++) { if (arrApple[i].CollidesWith(rect)) return true; } if (packMan != null && packMan.CollidesWith(rect)) return true; return false; } /// <summary> /// Уничтожение объектов /// </summary> public void Dispose() { for (int i = 0; i < arrThread.Count; i++) { arrThread[i].Abort(); } packManThread.Abort(); } /// <summary> /// Подписка каждого элемента с каждым на пересечение /// </summary> public void SubscribePos() { for (int i = 0; i < arrTank.Count; i++) { for (int j = 0; j < arrTank.Count; j++) { if (i != j) { arrTank[i].CheckPosition += new EventHandler(arrTank[j].OnCheckPosition); } } } for (int i = 0; i < arrTank.Count; i++) { for (int j = 0; j < arrWall.Count; j++) { arrTank[i].CheckPosition += new EventHandler(arrWall[j].OnCheckPosition); } } for (int j = 0; j < arrWall.Count; j++) { packMan.CheckPosition += new EventHandler(arrWall[j].OnCheckPosition); } for (int j = 0; j < arrTank.Count; j++) { packMan.CheckPosition += new EventHandler(arrTank[j].OnCheckPosition); } for (int j = 0; j < arrTank.Count; j++) { arrTank[j].CheckPosition += new EventHandler(packMan.OnCheckPosition); } for (int j = 0; j < arrApple.Count; j++) { packMan.CheckPosition += new EventHandler(arrApple[j].OnCheckPosition); } packMan.ReplaceNeeded += new EventHandler(packMan_ReplaceNeeded); for (int j = 0; j < arrApple.Count; j++) { arrApple[j].ReplaceNeeded += new EventHandler(apple_ReplaceNeeded); } } /// <summary> /// Обработчик события перемещение объекта в заданные координаты /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void apple_ReplaceNeeded(object sender, EventArgs e) { Rectangle rect2 = new Rectangle(); do { rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX - (sender as MapObject).Width - 2), GameForm.rand.Next(GameForm.MAXY - (sender as MapObject).Height - 2), (new Apple()).Width, (new Apple()).Height); } while (Collides(rect2)); (sender as Apple).Position = rect2.Location; } /// <summary> /// Обработчик события перемещение объекта в заданные координаты /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void packMan_ReplaceNeeded(object sender, EventArgs e) { Rectangle rect2 = new Rectangle(); do { rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX - (sender as MapObject).Width - 2), GameForm.rand.Next(GameForm.MAXY - (sender as MapObject).Height - 2), (new PackMan()).Width, (new PackMan()).Height); } while (Collides(rect2)); packMan.Position = rect2.Location; } /// <summary> /// Отписка каждого с каждым от событий пересечения /// </summary> private void UnSubscribePos() { for (int i = 0; i < arrTank.Count; i++) { for (int j = 0; j < arrTank.Count; j++) { if (i != j) { arrTank[i].CheckPosition -= new EventHandler(arrTank[i].OnCheckPosition); } } } } } } |
Теперь создадим представление игры. Для этого в классе создаём представление для каждого объекта нашей игры и связываем ссылки представлений объектов с реальными объектами игры. А также создаём событие нажатия на клавишу, описываем его обработчик и подписываем контроллер на событие нажатие клавиши.
namespace PackMan { class ViewGame:View<Game> { #region Создание представлений и их свойств //Game model = new Game(); //internal Game Model //{ // get { return model; } // set { model = value; } //} /// <summary> /// Спмсок представлений танков /// </summary> List<ViewTank> arrViewTank = new List<ViewTank>(); /// <summary> /// Представление пакмена /// </summary> ViewPackMan viewPackMan; /// <summary> /// Список представлений стен /// </summary> List<ViewWall> viewWall = new List<ViewWall>(); /// <summary> /// Список представлений яблок /// </summary> List<ViewApple> viewApple = new List<ViewApple>(); /// <summary> /// Контроллер пакмена /// </summary> PackManController controller = new PackManController(); /// <summary> /// Поле на котором происходит игра /// </summary> Panel panelMap; /// <summary> /// Очки /// </summary> Label point; #endregion /// <summary> /// Конструктор /// </summary> /// <param name="panelMap"></param> public ViewGame(Panel panelMap,Label point) { this.panelMap = panelMap; this.point = point; panelMap.Controls.Add(point); } #region Событие нажатие на клавишу /// <summary> /// Событие нажатия на клавишу /// </summary> private event KeyEventHandler keyPress; /// <summary> /// Обработчик нажатия на клавишу /// </summary> /// <param name="key">Клавиша,которая нажата</param> public virtual void OnKeyPress(Keys key) { if (keyPress != null) keyPress(viewPackMan.Model, new KeyEventArgs(key)); } /// <summary> /// Подписка на событие нажатие клавиши /// </summary> public void SubscribeKeyPress() { this.keyPress += new KeyEventHandler(controller.OnKeyPress); controller.OnKeyPress(viewPackMan, new KeyEventArgs(Keys.Right)); } /// <summary> /// Отписка на событие нажатие клавиши /// </summary> public void UnsubscribeKeyPress() { this.keyPress -= new KeyEventHandler(controller.OnKeyPress); } #endregion /// <summary> /// Обновить. /// </summary> protected override void Update() { Refresh(); } /// <summary> /// Создание всех элементов игры, /// связывание ссылок представлений объектоа с реальными объектами игры /// </summary> private void Refresh() { arrViewTank = new List<ViewTank>(); viewWall = new List<ViewWall>(); viewApple = new List<ViewApple>(); viewPackMan = new ViewPackMan(panelMap); viewPackMan.Model = Model.PackMan; viewPackMan.Model.MapSize = new Point(panelMap.Width, panelMap.Height); viewPackMan.Subscribe(); for (int i = 0; i < Model.ArrTank.Count; i++) { ViewTank viewTanktemp = new ViewTank(panelMap); viewTanktemp.Model = Model.ArrTank[i]; viewTanktemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height); viewTanktemp.Subscribe(); arrViewTank.Add(viewTanktemp); } for (int i = 0; i < Model.ArrWall.Count; i++) { ViewWall viewWalltemp = new ViewWall(panelMap); viewWalltemp.Model = Model.ArrWall[i]; viewWalltemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height); viewWall.Add(viewWalltemp); } for (int i = 0; i < Model.ArrApple.Count; i++) { ViewApple viewAppletemp = new ViewApple(panelMap); viewAppletemp.Model = Model.ArrApple[i]; viewAppletemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height); viewAppletemp.Subscribe(); viewApple.Add(viewAppletemp); } } } |
Следующая статья будет заключающей, где мы опишем контроллер игры и создадим модель и представление формы.
Комментариев нет:
Отправить комментарий