Основы разработки компьютерных игр в XNA Game Studio

Организация многоуровневых игр, конструктор уровней

Разбить на страницы
Показывать лекцию целиком

Примеры к лабораторной работе

Задачи работы

  • Создать игру, игровой процесс которой делится на несколько уровней
  • Разработать конструктор уровней
  • Организация многоуровневой игры

    В рамках этой лабораторной работы мы создадим игру – клон популярной игры Arkanoid. Наша игра будет обладать следующим набором характеристик.

  • Имеется 2 вида блоков – один из них уничтожается при попадании в него мяча, причем, мяч отскакивает, второй – не уничтожается.
  • При уничтожении всех блоков текущего уровня осуществляется переход на следующий уровень.
  • После того, как мяч выйдет за пределы поля – игра окончится и произойдет загрузка первого уровня.
  • Игрок может играть в стандартном режиме и в режиме пользовательского уровня. Пользовательский уровень создается игроком самостоятельно с помощью конструктора уровней
  • Здесь мы создадим каркас игры и конструктор уровней для создания пользовательских уровней.

    Создадим новый проект – P11_1. На рис. 15.1. вы можете видеть окно Project Explorer этого проекта.

    (рис 15.1) Окно Project Explorer для проекта P11_1

    Игровая функциональность и функциональность конструктора уровней реализована в классе Game1. Класс gBaseClass используется в качестве базового класса для игровых объектов. В предыдущих работах мы уже пользовались подобным классом, здесь мы расширили его функциональность для того, чтобы он позволял размещать на экране объекты, которым соответствуют спрайты различных размеров.

    Классы Ball, Bat, Brick1, Brick2 – это игровые компоненты, соответственно, для мяча (Ball), биты (Bat), и двух видов блоков – обычного, который уничтожается при попадании в него мяча (Brick1), и неуничтожимого (Brick2).

    Класс BrickBuilder используется для создания указателя, который применяется в конструкторе уровней.

    На рис. 15.2. вы можете видеть изображения графических ресурсов, примененных в игре.

    (рис 15.2) Игровые ресурсы

    Изображения BrickBuild используются в конструкторе уровней. Указатель с изображением BrickBuild0.png очищает ячейку игрового поля, расположенную под ней. Указатель с BrickBuild1.png устанавливает в текущую позицию игрового поля объект Brick1, указатель с изображением BrickBuild2.png устанавливает в текущую ячейку объект Brick2.

    На рис. 15.3. вы можете видеть игровой экран.

    (рис 15.3) Игровой экран проекта P11_1

    На рис. 15.4 представлен экран в режиме конструктора уровней.

    (рис 15.4) Конструктор уровней

    Рассмотрим коды классов, которые используются в игре.

    В листинге 15.1. приведен код класса Game1.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P11_1
    {
        
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            //Текстуры
            Texture2D txtBack, txtBrick1, txtBrick2, txtBat, txtBall;
            Texture2D txtBrBuild1, txtBrBuild2, txtBrBuild0;
            //Массивы для хранения данных об уровнях
            public byte[,] Layer1, Layer2, Layer3, UserLayer;
            //Прямоугольник для фона
            Rectangle recBack = new Rectangle(0, 0, 512, 640);
            //Прямоугольник для "блока"
            Rectangle recBrick = new Rectangle(0, 0, 64, 16);
            //Прямоугольник для "мяча"
            Rectangle recBall = new Rectangle(0, 0, 10, 10);
            //Прямоугольник для биты
            Rectangle recBat = new Rectangle(0, 0, 96, 16);
            //Номер текущего уровня
            //1 - первый
            //2 - второй
            //3 - третий
            //-1 - пользовательский
            int currentLayer;
            //Установлена в false, если программа находится в 
            //режиме игры, при установке в True включается
            //режим конструктора
            bool IsBuild;
            //Позиция указателя в режиме конструктора
            Vector2 PositionBuild;
            //Тип блока, который будет добавлен на игровое поле
            //при нажатии соответствующей клавиши в режиме 
            //конструктора
            byte typeOfBuildBlock;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
            }
    
            protected override void Initialize()
            {
                //Программа находится в игровом режиме
                IsBuild = false;
                //Позиция для указателя в режиме конструктора (0,0)
                PositionBuild = new Vector2(0, 0);
                //Тип блока по умолчанию в режиме конструктора - 1
                typeOfBuildBlock = 1;
                //пользовательский уровень по умолчанию пуст - ячейки
                //заполнены нулями
                UserLayer = new byte[40, 8]; 
                //Заполняем массивы для разных уровней
                //0 - пустая ячейка
                //1 - блок первого типа (уничтожимый)
                //2 - блок второго типа (неуничтожимый)
                Layer1 = new byte[40, 8]{
                {1,1,1,1,1,1,1,1},
                {0,1,0,1,0,1,0,1},
                {1,0,1,0,1,0,1,0},
                {0,1,0,1,0,1,0,1},
                {1,0,1,0,1,0,1,0},
                {0,1,0,1,0,1,0,1},
                {0,0,0,0,0,0,0,0},
                {2,0,0,2,0,0,2,0},
                {0,1,0,0,0,0,0,1},
                {0,0,1,0,0,0,1,0},
                {0,0,0,1,0,1,0,0},
                {0,0,0,0,1,0,0,0},
                {1,1,1,1,1,1,1,1},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0}
                };
    
                Layer2 = new byte[40, 8]{
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,1,2,1,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0}
                };
    
                Layer3 = new byte[40, 8]{
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,1,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,1,0,1,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,2,0,1,0,2,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,1,0,0,0,1,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0}
                };
    
                //Текущий уровень - 1
                currentLayer = 1;
                //Устанавливаем разрешение игрового окна
                //640х512
                graphics.PreferredBackBufferWidth = 512;
                graphics.PreferredBackBufferHeight = 640;
                graphics.ApplyChanges();
                base.Initialize();
            }
    
            /// <summary>
            /// LoadContent will be called once per game and is the place to load
            /// all of your content.
            /// </summary>
            protected override void LoadContent()
            {
                // Загружаем ресурсы
                spriteBatch = new SpriteBatch(GraphicsDevice);
                Services.AddService(typeof(SpriteBatch), spriteBatch);
                txtBack = Content.Load<Texture2D>("Back");
                txtBrick1 = Content.Load<Texture2D>("Brick1");
                txtBrick2 = Content.Load<Texture2D>("Brick2");
                txtBall = Content.Load<Texture2D>("Ball");
                txtBat = Content.Load<Texture2D>("Bat");
                txtBrBuild0 = Content.Load<Texture2D>("BrickBuild0");
                txtBrBuild1 = Content.Load<Texture2D>("BrickBuild1");
                txtBrBuild2 = Content.Load<Texture2D>("BrickBuild2");
                //Создаем уровень
                AddSprites(Layer1);
                //Добавляем биту и мяч
                addBallAndBAts();
    
            }
            //Процедура создания уровня
            //расставляет элементы в соответствии с
            //массивом
            void AddSprites(byte [,] RenderLayer)
            {
                //Просматриваем массив
                for (int i = 0; i < 40; i++)
                {
                    for (int j = 0; j < 8; j++)
                    {
                        if (RenderLayer[i, j] == 1)
                            Components.Add(new Brick1(this, ref txtBrick1, new Vector2(j, i), recBrick, 64, 16));
                        if (RenderLayer[i, j] == 2)
                            Components.Add(new Brick2(this, ref txtBrick2, new Vector2(j, i), recBrick, 64, 16));
                    }
                }
            }
            //Процедура для установки биты и мяча
            void addBallAndBAts()
            {
                Components.Add(new Ball(this, ref txtBall, new Vector2(300, 550), recBall, 1, 1));
                Components.Add(new Bat(this, ref txtBat, new Vector2(300, (640 - 16)), recBat, 1, 1));
            }
    
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            protected override void Update(GameTime gameTime)
            {
                //получим состояние клавиатуры
                KeyboardState kb = Keyboard.GetState();
                //если IsBuild = false и нажата клавиша A
                //переходим в режим конструктора уровней
                if (kb.IsKeyDown (Keys .A)!IsBuild)
                {
                    IsBuild = true;
                    builder();
                }
                //Если находимся в режиме конструктора уровней
                if (IsBuild)
                {
                    //При нажатой клавише "вверх" уменьшить текущую позицию Y
                    //указателя на 1 если позиция больше 0
                    if (kb.IsKeyDown(Keys.Up))
                    {
                        if (PositionBuild.Y > 0) PositionBuild.Y--;
                    }
                    //При нажатой клавише "вниз" увеличить позицию
                    //Y если она меньше 39
                    if (kb.IsKeyDown(Keys.Down))
                    {
                        if (PositionBuild.Y < 39) PositionBuild.Y++;
                    }
                    //При нажатой клавише "влево"
                    //уменьшить позицию X если текущая позиция
                    //больше 0
                    if (kb.IsKeyDown(Keys.Left))
                    {
                        if (PositionBuild.X >0) PositionBuild.X--;
                    }
                    //При нажатой клавише "вправо"
                    //увеличить позицию X если текущая позиция
                    //меньше 7
                    if (kb.IsKeyDown(Keys.Right))
                    {
                        if (PositionBuild.X < 7) PositionBuild.X++;
                    }
                    //Нажатие клавиши Z устанавливает тип блока в 0
                    //этот блок предназначен для "стирания"
                    //других блоков, то есть - для установки элемента 
                    //массива, соответствующего текущему состоянию 
                    //конструируемого уровня, в 0
                    if (kb.IsKeyDown(Keys.Z))
                    {
                        typeOfBuildBlock = 0;
                    }
                    //При нажатии клавиши X установим 1-й тип блока
                    if (kb.IsKeyDown(Keys.X))
                    {
                        typeOfBuildBlock = 1;
                    }
                    //При нажатии клавиши С установим 2-й тип блока
                    if (kb.IsKeyDown(Keys.C))
                    {
                        typeOfBuildBlock = 2;
                    }
                    //При нажатии клавиши W запишем в 
                    //ячейку массива, соответствующую позиции указателя
                    //текущий тип блока
                                    if (kb.IsKeyDown (Keys .W ))
                    {
                         UserLayer[(int)PositionBuild.Y, (int)PositionBuild.X] = typeOfBuildBlock; 
                    }
                        //очищаем массив компонентов
                        Components.Clear();
                    //выводим пользовательский уровень
                        AddSprites(UserLayer);
                    //уничтожаем указатель и изменяем его в соответствии с типом выбранного блока
                        BrickBuilder br = null;
                        if (typeOfBuildBlock == 1)
                            br = new BrickBuilder(this, ref txtBrBuild1, PositionBuild, recBrick, 64, 16);
                        if (typeOfBuildBlock == 2)
                            br = new BrickBuilder(this, ref txtBrBuild2, PositionBuild, recBrick, 64, 16);
                        if (typeOfBuildBlock == 0)
                            br = new BrickBuilder(this, ref txtBrBuild0, PositionBuild, recBrick, 64, 16);
                        Components.Add(br);
                        //При нажатии клавиши W
                        //отключаем режим конструктора
                        //выводим пользовательский уровень
                        //и начинаем игру
                        if (kb.IsKeyDown(Keys.E))
                        {
                            IsBuild = false;
                            Components.Clear();
                            AddSprites(UserLayer);
                            addBallAndBAts();
                            currentLayer = -1;
                        }
                }
                //Если находимся в режиме игры
                //проверим - завершен ли текущий уровень
                //признак завершения уровня
    
                if (!IsBuild)
                {
                    IsLayerCompleted();
                }
                base.Update(gameTime);
            }
            //Процедура старта конструктора уровней
            //очищаем набор компонентов
            //Создаем и выводим указатель
            public void builder()
            {
                Components.Clear();
                BrickBuilder br = new BrickBuilder (this, ref txtBrBuild1,PositionBuild, recBrick, 64, 16);
                Components.Add(br);
            }
    
            //Процедура проверки на завершенность
            //текущего уровня
            //Если на поле отсутствует мяч - игра начинается сначала
            //если на поле нет ни одного блока типа Brick1 - 
            //переход на следующий уровень или переход на первый уровень
            //с выводом надписи "Вы выиграли"
    
            public void IsLayerCompleted()
            {
                //Предположим, что на поле нет ни одного блока
                //типа Brick1
                bool f = false;
                //Предположим, что на поле нет мяча
                bool Ball = false;
                foreach (gBaseClass spr in this.Components)
                {
                    //Если найден хотя бы один блок типа 1
                    //установим F в True
                    if (spr.GetType() == (typeof(Brick1)))
                    {
                        f = true;
                    }
                    //Если найден мяч - установим Ball в True
                    if (spr.GetType() == (typeof(Ball)))
                    {
                        Ball = true;
                    }
                }
                //Если мяч не обнаружен
                //игра начинается сначала
                if (!Ball)
                {
                    Components.Clear();
                    currentLayer = 1;
                    AddSprites(Layer1);
                    addBallAndBAts();
                 }
                    //иначе
                else
                {
                    //Если не было найдено ни одного блока
                    //типа Brick1
                    if (!f)
                    {
                        //Очистим набор компонентов
                        Components.Clear();
                        //Увеличим номер уровня
                        currentLayer++;
                        //в зависимости от номера
                        //запустим соответствующий уровень
                        if (currentLayer == 2)
                        {
                            AddSprites(Layer2);
                            addBallAndBAts();
                        }
                        if (currentLayer == 3)
                        {
                            AddSprites(Layer3);
                            addBallAndBAts();
                        }
                        //4 - если был пройден 3-й уровень
                        //0 - при прохождении пользовательского уровня
                        if (currentLayer == 4 | currentLayer == 0)
                        {
                            currentLayer = 1;
                            AddSprites(Layer1);
                            addBallAndBAts();
                            this.Window.Title = "Вы выиграли!";
                        }
                    }
                }
            }
    
            protected override void Draw(GameTime gameTime)
            {
                spriteBatch.Begin();
                //выведем фоновое изображение
                spriteBatch.Draw(txtBack, recBack, Color.White);
                //Выведем игровые объекты
                base.Draw(gameTime);
                spriteBatch.End();
            }
        }
    }

    В листинге 15.2. приведен код класса gBaseClass.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
        {
            Texture2D sprTexture;
            public Vector2 sprPosition;
            public Rectangle sprRectangle;
            public gBaseClass(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int X_Coord, int Y_Coord)
                : base(game)
            {
                sprTexture = _sprTexture;
                //Здесь производится перевод индекса элемента массива
                //в координаты на игровом экране
                //Так как спрайты имеют различные размеры, для их размещения на экране
                //используются коэффициенты X_Coord и Y_Coord
                sprPosition.X = _sprPosition.X * X_Coord;
                sprPosition.Y = _sprPosition.Y * Y_Coord;
                sprRectangle = _sprRectangle;
            }
            public override void Draw(GameTime gameTime)
            {
                SpriteBatch sprBatch =
                    (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
                sprBatch.Draw(sprTexture, sprPosition, Color.White);
                base.Draw(gameTime);
            }
        }
    }

    В листинге 15.3. приведен код класса Bat. Он занимается обработкой перемещений биты по горизонтали и контролем за пересечением битой левой и правой границ экрана.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Bat : gBaseClass
        {
            Rectangle scrBounds;
            //True для движения биты влево и False - вправо
            public bool direction;
    
            public Bat(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                scrBounds = new Rectangle(0, 0,
                  game.Window.ClientBounds.Width,
                  game.Window.ClientBounds.Height);
                direction = true;
            }
    
            public override void Update(GameTime gameTime)
            {
                //Нажатия клавиш-стрелок влево и вправо
                //вызывают перемещение объекта в соответствующем
                //направлении
                KeyboardState kb = Keyboard.GetState();
                if (kb.IsKeyDown(Keys.Left))
                {
                    sprPosition.X -= 2;
                    direction = true;
                }
                if (kb.IsKeyDown(Keys.Right))
                {
                    sprPosition.X += 2;
                    direction = false;
                }
                //проверка на столкновение с границами экрана
                Check();
    
                base.Update(gameTime);
            }
            //Процедура проверки столкновения с левой
            //и правой границами экрана
            void Check()
            {
                if (sprPosition.X < scrBounds.Left)
                {
                    sprPosition.X = scrBounds.Left;
                   
                }
                if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
                {
                    sprPosition.X = scrBounds.Width - sprRectangle.Width;
                    
                }
            }
        }
    }

    В листинге 15.4. вы можете видеть код класса Ball. Этот класс отвечает за перемещение мяча и за обработку столкновений мяча и других объектов.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Ball : gBaseClass
        {
            //для хранения границ экрана
            Rectangle scrBounds;
            //скорость мяча
            public Vector2 speed;
            Random rand = new Random();
            public Ball(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                speed = new Vector2(1, -1);
                scrBounds = new Rectangle(0, 0,
                  game.Window.ClientBounds.Width,
                  game.Window.ClientBounds.Height);
            }
    
            
            //Проверка на столкновение с границами экрана
            void Check()
            {
                if (sprPosition.X < scrBounds.Left)
                {
                    sprPosition.X = scrBounds.Left;
                    RandomiseSpeed();
                    speed.X *= -1;
                }
                if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
                {
                    sprPosition.X = scrBounds.Width - sprRectangle.Width;
                    RandomiseSpeed();
                    speed.X *= -1;
                }
                if (sprPosition.Y < scrBounds.Top)
                {
                    sprPosition.Y = scrBounds.Top;
                    RandomiseSpeed();
                    speed.Y *= -1;
                }
                if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
                {
                    this.Dispose();
                }
                
            }
    
            //Проверка на столкновение с объектом
            bool IsCollideWithObject(gBaseClass spr)
            {
                return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X 
                            this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width 
                            this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y 
                            this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
    
    
            }
            //Процедуры случайного изменения скорости при контакте с объектами
            //Для всех объектов - скорость может либо возрасти, либо упасть
            void RandomiseSpeed()
            {
                speed.Y += (float)((rand.NextDouble() - rand.NextDouble())*0.2 );
                speed.X += (float)((rand.NextDouble() - rand.NextDouble())*0.2 );
            }
            //Для объектов типа Brick1 скорость лишь растет
            void RandomiseSpeedB1()
            {
                speed.Y += (float)(0.3*Math.Sign(speed.Y) * Math.Abs(((rand.NextDouble() - rand.NextDouble()))));
                speed.X += (float)(0.3*Math.Sign(speed.X) * Math.Abs(((rand.NextDouble() - rand.NextDouble()))));
            }
            //Проверка на столкновения
           public void IsCollide()
            {
                gBaseClass FindObj=null;
                Bat FindedBat;          
                foreach (gBaseClass spr in Game.Components)
                {
                        if (IsCollideWithObject(spr))
                        {
                            //Если столкнулись с блоком Brick1
                            if (spr.GetType() == (typeof(Brick1)))
                            {
                                //сохраним найденный объект
                                FindObj = spr;
                                //Изменим скорость
                                RandomiseSpeedB1();
                                //Обратим скорость
                                speed *= -1;
                            }
                            //Если столкнулись с блоком 2
                            //обращаем скорость
                            if (spr.GetType() == (typeof(Brick2)))
                            {
                                speed *= -1;
                            }
                            //если столкнулись с битой
                            if (spr.GetType() == (typeof(Bat)))
                            {
                                FindedBat =(Bat) spr;
                                RandomiseSpeed();
                                //Если бита двигалась влево
                                //Мяч отразится от нее влево
                                if (FindedBat.direction)
                                {
                                    speed.Y = Math.Abs(speed.Y);
                                    speed.X = Math.Abs(speed.X);
                                    speed *= -1;
                                }
                                //Если двигалась вправо
                                //отразится вправо
                                else
                                {
                                    speed.Y = Math.Abs(speed.Y);
                                    speed.X = Math.Abs(speed.X);
                                    speed.Y *= -1;
                                }
                                
                                
                            }
    
                        }
                }
                //Если был найден блок Block1
                if (FindObj != null)
                {
                   //Уничтожить его
                    FindObj.Dispose();
                    FindObj = null;
                }
    
            }
    
            
            public override void Update(GameTime gameTime)
            {
                // TODO: Add your update code here
                //Переместить мяч в соответствии со скоростью
                this.sprPosition += this.speed;
                //Ограничить скорость мяча 1.5 пикселями
                if (speed.Y > 2) speed.Y = (float)2;
                if (speed.Y < -2) speed.Y = (float)-2;
                if (speed.X > 2) speed.X = (float)2;
                if (speed.X < -2) speed.X = (float)-2;
                //Проверить столкновение с границами
                Check();
                //проверить столкновение с объектами
                IsCollide();
                base.Update(gameTime);
            }
        }
    }

    Листинг 15.5. содержит коды классов Brick1, Brick2 и BrickBuilder. Эти классы наследованы от gBaseClass и не вносят в его функциональность никаких дополнений.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Brick1 : gBaseClass
        {
            public Brick1(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                // TODO: Construct any child components here
            }
        }
    }
    
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Brick2 : gBaseClass
        {
            public Brick2(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                // TODO: Construct any child components here
            }
    
        }
    }
    
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class BrickBuilder : gBaseClass
        {
            public BrickBuilder(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                // TODO: Construct any child components here
            }
        }
    }

    Выводы

    В этой лабораторной работе мы создали игру, предусматривающую несколько игровых уровней и конструктор уровней для пользователя. Как правило, разработка реальных игр включает в себя создание конструктора уровней, с помощью которого разработчик создает игровые уровни. Этот конструктор можно включить в поставку игры, предоставив пользователю возможность самостоятельно создавать новые уровни к игре. Конструкторы уровней так же разнообразны, как сами игры.

    Задание

    В этой лабораторной работе мы создали макет игры, допускающей использование нескольких уровней и содержащей конструктор уровней. Доработайте этот макет. А именно, выполните следующие улучшения исходного проекта:

  • Переработайте процедуры обработки столкновений мяча с блоками. Мяч должен отскакивать от блоков точно так же, как он отскакивает от границ экрана.
  • Создайте интерфейс игры – меню, систему справки.
  • Предусмотрите сохранение пользовательского уровня в файл (с помощью сериализации специально созданного объекта) и загрузку его из файла
  • Создайте систему подсчета очков, предусмотрите сохранение данных о набранных очках в файле.
  • Улучшите процесс прохождения игры – разработайте дополнительные уровни, сделайте игровой процесс разнообразнее, разработав бонусные объекты, которые появляются при уничтожении некоторых из блоков. Игрок должен "поймать" бонусный объект битой для того, чтобы активировать соответствующий бонус.
  • Страницы:

    Примеры к лабораторной работе

    Задачи работы

  • Создать игру, игровой процесс которой делится на несколько уровней
  • Разработать конструктор уровней
  • Организация многоуровневой игры

    В рамках этой лабораторной работы мы создадим игру – клон популярной игры Arkanoid. Наша игра будет обладать следующим набором характеристик.

  • Имеется 2 вида блоков – один из них уничтожается при попадании в него мяча, причем, мяч отскакивает, второй – не уничтожается.
  • При уничтожении всех блоков текущего уровня осуществляется переход на следующий уровень.
  • После того, как мяч выйдет за пределы поля – игра окончится и произойдет загрузка первого уровня.
  • Игрок может играть в стандартном режиме и в режиме пользовательского уровня. Пользовательский уровень создается игроком самостоятельно с помощью конструктора уровней
  • Здесь мы создадим каркас игры и конструктор уровней для создания пользовательских уровней.

    Создадим новый проект – P11_1. На рис. 15.1. вы можете видеть окно Project Explorer этого проекта.

    (рис 15.1) Окно Project Explorer для проекта P11_1

    Игровая функциональность и функциональность конструктора уровней реализована в классе Game1. Класс gBaseClass используется в качестве базового класса для игровых объектов. В предыдущих работах мы уже пользовались подобным классом, здесь мы расширили его функциональность для того, чтобы он позволял размещать на экране объекты, которым соответствуют спрайты различных размеров.

    Классы Ball, Bat, Brick1, Brick2 – это игровые компоненты, соответственно, для мяча (Ball), биты (Bat), и двух видов блоков – обычного, который уничтожается при попадании в него мяча (Brick1), и неуничтожимого (Brick2).

    Класс BrickBuilder используется для создания указателя, который применяется в конструкторе уровней.

    На рис. 15.2. вы можете видеть изображения графических ресурсов, примененных в игре.

    (рис 15.2) Игровые ресурсы

    Изображения BrickBuild используются в конструкторе уровней. Указатель с изображением BrickBuild0.png очищает ячейку игрового поля, расположенную под ней. Указатель с BrickBuild1.png устанавливает в текущую позицию игрового поля объект Brick1, указатель с изображением BrickBuild2.png устанавливает в текущую ячейку объект Brick2.

    На рис. 15.3. вы можете видеть игровой экран.

    (рис 15.3) Игровой экран проекта P11_1

    На рис. 15.4 представлен экран в режиме конструктора уровней.

    (рис 15.4) Конструктор уровней

    Рассмотрим коды классов, которые используются в игре.

    В листинге 15.1. приведен код класса Game1.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P11_1
    {
        
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            //Текстуры
            Texture2D txtBack, txtBrick1, txtBrick2, txtBat, txtBall;
            Texture2D txtBrBuild1, txtBrBuild2, txtBrBuild0;
            //Массивы для хранения данных об уровнях
            public byte[,] Layer1, Layer2, Layer3, UserLayer;
            //Прямоугольник для фона
            Rectangle recBack = new Rectangle(0, 0, 512, 640);
            //Прямоугольник для "блока"
            Rectangle recBrick = new Rectangle(0, 0, 64, 16);
            //Прямоугольник для "мяча"
            Rectangle recBall = new Rectangle(0, 0, 10, 10);
            //Прямоугольник для биты
            Rectangle recBat = new Rectangle(0, 0, 96, 16);
            //Номер текущего уровня
            //1 - первый
            //2 - второй
            //3 - третий
            //-1 - пользовательский
            int currentLayer;
            //Установлена в false, если программа находится в 
            //режиме игры, при установке в True включается
            //режим конструктора
            bool IsBuild;
            //Позиция указателя в режиме конструктора
            Vector2 PositionBuild;
            //Тип блока, который будет добавлен на игровое поле
            //при нажатии соответствующей клавиши в режиме 
            //конструктора
            byte typeOfBuildBlock;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
            }
    
            protected override void Initialize()
            {
                //Программа находится в игровом режиме
                IsBuild = false;
                //Позиция для указателя в режиме конструктора (0,0)
                PositionBuild = new Vector2(0, 0);
                //Тип блока по умолчанию в режиме конструктора - 1
                typeOfBuildBlock = 1;
                //пользовательский уровень по умолчанию пуст - ячейки
                //заполнены нулями
                UserLayer = new byte[40, 8]; 
                //Заполняем массивы для разных уровней
                //0 - пустая ячейка
                //1 - блок первого типа (уничтожимый)
                //2 - блок второго типа (неуничтожимый)
                Layer1 = new byte[40, 8]{
                {1,1,1,1,1,1,1,1},
                {0,1,0,1,0,1,0,1},
                {1,0,1,0,1,0,1,0},
                {0,1,0,1,0,1,0,1},
                {1,0,1,0,1,0,1,0},
                {0,1,0,1,0,1,0,1},
                {0,0,0,0,0,0,0,0},
                {2,0,0,2,0,0,2,0},
                {0,1,0,0,0,0,0,1},
                {0,0,1,0,0,0,1,0},
                {0,0,0,1,0,1,0,0},
                {0,0,0,0,1,0,0,0},
                {1,1,1,1,1,1,1,1},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0}
                };
    
                Layer2 = new byte[40, 8]{
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,1,2,1,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0}
                };
    
                Layer3 = new byte[40, 8]{
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,1,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,1,0,1,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,2,0,1,0,2,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,1,0,0,0,1,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0},
                {0,0,0,0,0,0,0,0}
                };
    
                //Текущий уровень - 1
                currentLayer = 1;
                //Устанавливаем разрешение игрового окна
                //640х512
                graphics.PreferredBackBufferWidth = 512;
                graphics.PreferredBackBufferHeight = 640;
                graphics.ApplyChanges();
                base.Initialize();
            }
    
            /// <summary>
            /// LoadContent will be called once per game and is the place to load
            /// all of your content.
            /// </summary>
            protected override void LoadContent()
            {
                // Загружаем ресурсы
                spriteBatch = new SpriteBatch(GraphicsDevice);
                Services.AddService(typeof(SpriteBatch), spriteBatch);
                txtBack = Content.Load<Texture2D>("Back");
                txtBrick1 = Content.Load<Texture2D>("Brick1");
                txtBrick2 = Content.Load<Texture2D>("Brick2");
                txtBall = Content.Load<Texture2D>("Ball");
                txtBat = Content.Load<Texture2D>("Bat");
                txtBrBuild0 = Content.Load<Texture2D>("BrickBuild0");
                txtBrBuild1 = Content.Load<Texture2D>("BrickBuild1");
                txtBrBuild2 = Content.Load<Texture2D>("BrickBuild2");
                //Создаем уровень
                AddSprites(Layer1);
                //Добавляем биту и мяч
                addBallAndBAts();
    
            }
            //Процедура создания уровня
            //расставляет элементы в соответствии с
            //массивом
            void AddSprites(byte [,] RenderLayer)
            {
                //Просматриваем массив
                for (int i = 0; i < 40; i++)
                {
                    for (int j = 0; j < 8; j++)
                    {
                        if (RenderLayer[i, j] == 1)
                            Components.Add(new Brick1(this, ref txtBrick1, new Vector2(j, i), recBrick, 64, 16));
                        if (RenderLayer[i, j] == 2)
                            Components.Add(new Brick2(this, ref txtBrick2, new Vector2(j, i), recBrick, 64, 16));
                    }
                }
            }
            //Процедура для установки биты и мяча
            void addBallAndBAts()
            {
                Components.Add(new Ball(this, ref txtBall, new Vector2(300, 550), recBall, 1, 1));
                Components.Add(new Bat(this, ref txtBat, new Vector2(300, (640 - 16)), recBat, 1, 1));
            }
    
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            protected override void Update(GameTime gameTime)
            {
                //получим состояние клавиатуры
                KeyboardState kb = Keyboard.GetState();
                //если IsBuild = false и нажата клавиша A
                //переходим в режим конструктора уровней
                if (kb.IsKeyDown (Keys .A)!IsBuild)
                {
                    IsBuild = true;
                    builder();
                }
                //Если находимся в режиме конструктора уровней
                if (IsBuild)
                {
                    //При нажатой клавише "вверх" уменьшить текущую позицию Y
                    //указателя на 1 если позиция больше 0
                    if (kb.IsKeyDown(Keys.Up))
                    {
                        if (PositionBuild.Y > 0) PositionBuild.Y--;
                    }
                    //При нажатой клавише "вниз" увеличить позицию
                    //Y если она меньше 39
                    if (kb.IsKeyDown(Keys.Down))
                    {
                        if (PositionBuild.Y < 39) PositionBuild.Y++;
                    }
                    //При нажатой клавише "влево"
                    //уменьшить позицию X если текущая позиция
                    //больше 0
                    if (kb.IsKeyDown(Keys.Left))
                    {
                        if (PositionBuild.X >0) PositionBuild.X--;
                    }
                    //При нажатой клавише "вправо"
                    //увеличить позицию X если текущая позиция
                    //меньше 7
                    if (kb.IsKeyDown(Keys.Right))
                    {
                        if (PositionBuild.X < 7) PositionBuild.X++;
                    }
                    //Нажатие клавиши Z устанавливает тип блока в 0
                    //этот блок предназначен для "стирания"
                    //других блоков, то есть - для установки элемента 
                    //массива, соответствующего текущему состоянию 
                    //конструируемого уровня, в 0
                    if (kb.IsKeyDown(Keys.Z))
                    {
                        typeOfBuildBlock = 0;
                    }
                    //При нажатии клавиши X установим 1-й тип блока
                    if (kb.IsKeyDown(Keys.X))
                    {
                        typeOfBuildBlock = 1;
                    }
                    //При нажатии клавиши С установим 2-й тип блока
                    if (kb.IsKeyDown(Keys.C))
                    {
                        typeOfBuildBlock = 2;
                    }
                    //При нажатии клавиши W запишем в 
                    //ячейку массива, соответствующую позиции указателя
                    //текущий тип блока
                                    if (kb.IsKeyDown (Keys .W ))
                    {
                         UserLayer[(int)PositionBuild.Y, (int)PositionBuild.X] = typeOfBuildBlock; 
                    }
                        //очищаем массив компонентов
                        Components.Clear();
                    //выводим пользовательский уровень
                        AddSprites(UserLayer);
                    //уничтожаем указатель и изменяем его в соответствии с типом выбранного блока
                        BrickBuilder br = null;
                        if (typeOfBuildBlock == 1)
                            br = new BrickBuilder(this, ref txtBrBuild1, PositionBuild, recBrick, 64, 16);
                        if (typeOfBuildBlock == 2)
                            br = new BrickBuilder(this, ref txtBrBuild2, PositionBuild, recBrick, 64, 16);
                        if (typeOfBuildBlock == 0)
                            br = new BrickBuilder(this, ref txtBrBuild0, PositionBuild, recBrick, 64, 16);
                        Components.Add(br);
                        //При нажатии клавиши W
                        //отключаем режим конструктора
                        //выводим пользовательский уровень
                        //и начинаем игру
                        if (kb.IsKeyDown(Keys.E))
                        {
                            IsBuild = false;
                            Components.Clear();
                            AddSprites(UserLayer);
                            addBallAndBAts();
                            currentLayer = -1;
                        }
                }
                //Если находимся в режиме игры
                //проверим - завершен ли текущий уровень
                //признак завершения уровня
    
                if (!IsBuild)
                {
                    IsLayerCompleted();
                }
                base.Update(gameTime);
            }
            //Процедура старта конструктора уровней
            //очищаем набор компонентов
            //Создаем и выводим указатель
            public void builder()
            {
                Components.Clear();
                BrickBuilder br = new BrickBuilder (this, ref txtBrBuild1,PositionBuild, recBrick, 64, 16);
                Components.Add(br);
            }
    
            //Процедура проверки на завершенность
            //текущего уровня
            //Если на поле отсутствует мяч - игра начинается сначала
            //если на поле нет ни одного блока типа Brick1 - 
            //переход на следующий уровень или переход на первый уровень
            //с выводом надписи "Вы выиграли"
    
            public void IsLayerCompleted()
            {
                //Предположим, что на поле нет ни одного блока
                //типа Brick1
                bool f = false;
                //Предположим, что на поле нет мяча
                bool Ball = false;
                foreach (gBaseClass spr in this.Components)
                {
                    //Если найден хотя бы один блок типа 1
                    //установим F в True
                    if (spr.GetType() == (typeof(Brick1)))
                    {
                        f = true;
                    }
                    //Если найден мяч - установим Ball в True
                    if (spr.GetType() == (typeof(Ball)))
                    {
                        Ball = true;
                    }
                }
                //Если мяч не обнаружен
                //игра начинается сначала
                if (!Ball)
                {
                    Components.Clear();
                    currentLayer = 1;
                    AddSprites(Layer1);
                    addBallAndBAts();
                 }
                    //иначе
                else
                {
                    //Если не было найдено ни одного блока
                    //типа Brick1
                    if (!f)
                    {
                        //Очистим набор компонентов
                        Components.Clear();
                        //Увеличим номер уровня
                        currentLayer++;
                        //в зависимости от номера
                        //запустим соответствующий уровень
                        if (currentLayer == 2)
                        {
                            AddSprites(Layer2);
                            addBallAndBAts();
                        }
                        if (currentLayer == 3)
                        {
                            AddSprites(Layer3);
                            addBallAndBAts();
                        }
                        //4 - если был пройден 3-й уровень
                        //0 - при прохождении пользовательского уровня
                        if (currentLayer == 4 | currentLayer == 0)
                        {
                            currentLayer = 1;
                            AddSprites(Layer1);
                            addBallAndBAts();
                            this.Window.Title = "Вы выиграли!";
                        }
                    }
                }
            }
    
            protected override void Draw(GameTime gameTime)
            {
                spriteBatch.Begin();
                //выведем фоновое изображение
                spriteBatch.Draw(txtBack, recBack, Color.White);
                //Выведем игровые объекты
                base.Draw(gameTime);
                spriteBatch.End();
            }
        }
    }

    В листинге 15.2. приведен код класса gBaseClass.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
        {
            Texture2D sprTexture;
            public Vector2 sprPosition;
            public Rectangle sprRectangle;
            public gBaseClass(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int X_Coord, int Y_Coord)
                : base(game)
            {
                sprTexture = _sprTexture;
                //Здесь производится перевод индекса элемента массива
                //в координаты на игровом экране
                //Так как спрайты имеют различные размеры, для их размещения на экране
                //используются коэффициенты X_Coord и Y_Coord
                sprPosition.X = _sprPosition.X * X_Coord;
                sprPosition.Y = _sprPosition.Y * Y_Coord;
                sprRectangle = _sprRectangle;
            }
            public override void Draw(GameTime gameTime)
            {
                SpriteBatch sprBatch =
                    (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
                sprBatch.Draw(sprTexture, sprPosition, Color.White);
                base.Draw(gameTime);
            }
        }
    }

    В листинге 15.3. приведен код класса Bat. Он занимается обработкой перемещений биты по горизонтали и контролем за пересечением битой левой и правой границ экрана.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Bat : gBaseClass
        {
            Rectangle scrBounds;
            //True для движения биты влево и False - вправо
            public bool direction;
    
            public Bat(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                scrBounds = new Rectangle(0, 0,
                  game.Window.ClientBounds.Width,
                  game.Window.ClientBounds.Height);
                direction = true;
            }
    
            public override void Update(GameTime gameTime)
            {
                //Нажатия клавиш-стрелок влево и вправо
                //вызывают перемещение объекта в соответствующем
                //направлении
                KeyboardState kb = Keyboard.GetState();
                if (kb.IsKeyDown(Keys.Left))
                {
                    sprPosition.X -= 2;
                    direction = true;
                }
                if (kb.IsKeyDown(Keys.Right))
                {
                    sprPosition.X += 2;
                    direction = false;
                }
                //проверка на столкновение с границами экрана
                Check();
    
                base.Update(gameTime);
            }
            //Процедура проверки столкновения с левой
            //и правой границами экрана
            void Check()
            {
                if (sprPosition.X < scrBounds.Left)
                {
                    sprPosition.X = scrBounds.Left;
                   
                }
                if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
                {
                    sprPosition.X = scrBounds.Width - sprRectangle.Width;
                    
                }
            }
        }
    }

    В листинге 15.4. вы можете видеть код класса Ball. Этот класс отвечает за перемещение мяча и за обработку столкновений мяча и других объектов.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Ball : gBaseClass
        {
            //для хранения границ экрана
            Rectangle scrBounds;
            //скорость мяча
            public Vector2 speed;
            Random rand = new Random();
            public Ball(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                speed = new Vector2(1, -1);
                scrBounds = new Rectangle(0, 0,
                  game.Window.ClientBounds.Width,
                  game.Window.ClientBounds.Height);
            }
    
            
            //Проверка на столкновение с границами экрана
            void Check()
            {
                if (sprPosition.X < scrBounds.Left)
                {
                    sprPosition.X = scrBounds.Left;
                    RandomiseSpeed();
                    speed.X *= -1;
                }
                if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
                {
                    sprPosition.X = scrBounds.Width - sprRectangle.Width;
                    RandomiseSpeed();
                    speed.X *= -1;
                }
                if (sprPosition.Y < scrBounds.Top)
                {
                    sprPosition.Y = scrBounds.Top;
                    RandomiseSpeed();
                    speed.Y *= -1;
                }
                if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
                {
                    this.Dispose();
                }
                
            }
    
            //Проверка на столкновение с объектом
            bool IsCollideWithObject(gBaseClass spr)
            {
                return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X 
                            this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width 
                            this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y 
                            this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
    
    
            }
            //Процедуры случайного изменения скорости при контакте с объектами
            //Для всех объектов - скорость может либо возрасти, либо упасть
            void RandomiseSpeed()
            {
                speed.Y += (float)((rand.NextDouble() - rand.NextDouble())*0.2 );
                speed.X += (float)((rand.NextDouble() - rand.NextDouble())*0.2 );
            }
            //Для объектов типа Brick1 скорость лишь растет
            void RandomiseSpeedB1()
            {
                speed.Y += (float)(0.3*Math.Sign(speed.Y) * Math.Abs(((rand.NextDouble() - rand.NextDouble()))));
                speed.X += (float)(0.3*Math.Sign(speed.X) * Math.Abs(((rand.NextDouble() - rand.NextDouble()))));
            }
            //Проверка на столкновения
           public void IsCollide()
            {
                gBaseClass FindObj=null;
                Bat FindedBat;          
                foreach (gBaseClass spr in Game.Components)
                {
                        if (IsCollideWithObject(spr))
                        {
                            //Если столкнулись с блоком Brick1
                            if (spr.GetType() == (typeof(Brick1)))
                            {
                                //сохраним найденный объект
                                FindObj = spr;
                                //Изменим скорость
                                RandomiseSpeedB1();
                                //Обратим скорость
                                speed *= -1;
                            }
                            //Если столкнулись с блоком 2
                            //обращаем скорость
                            if (spr.GetType() == (typeof(Brick2)))
                            {
                                speed *= -1;
                            }
                            //если столкнулись с битой
                            if (spr.GetType() == (typeof(Bat)))
                            {
                                FindedBat =(Bat) spr;
                                RandomiseSpeed();
                                //Если бита двигалась влево
                                //Мяч отразится от нее влево
                                if (FindedBat.direction)
                                {
                                    speed.Y = Math.Abs(speed.Y);
                                    speed.X = Math.Abs(speed.X);
                                    speed *= -1;
                                }
                                //Если двигалась вправо
                                //отразится вправо
                                else
                                {
                                    speed.Y = Math.Abs(speed.Y);
                                    speed.X = Math.Abs(speed.X);
                                    speed.Y *= -1;
                                }
                                
                                
                            }
    
                        }
                }
                //Если был найден блок Block1
                if (FindObj != null)
                {
                   //Уничтожить его
                    FindObj.Dispose();
                    FindObj = null;
                }
    
            }
    
            
            public override void Update(GameTime gameTime)
            {
                // TODO: Add your update code here
                //Переместить мяч в соответствии со скоростью
                this.sprPosition += this.speed;
                //Ограничить скорость мяча 1.5 пикселями
                if (speed.Y > 2) speed.Y = (float)2;
                if (speed.Y < -2) speed.Y = (float)-2;
                if (speed.X > 2) speed.X = (float)2;
                if (speed.X < -2) speed.X = (float)-2;
                //Проверить столкновение с границами
                Check();
                //проверить столкновение с объектами
                IsCollide();
                base.Update(gameTime);
            }
        }
    }

    Листинг 15.5. содержит коды классов Brick1, Brick2 и BrickBuilder. Эти классы наследованы от gBaseClass и не вносят в его функциональность никаких дополнений.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Brick1 : gBaseClass
        {
            public Brick1(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                // TODO: Construct any child components here
            }
        }
    }
    
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class Brick2 : gBaseClass
        {
            public Brick2(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                // TODO: Construct any child components here
            }
    
        }
    }
    
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P11_1
    {
        public class BrickBuilder : gBaseClass
        {
            public BrickBuilder(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle, int x_c, int y_c)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle, x_c, y_c)
            {
                // TODO: Construct any child components here
            }
        }
    }

    Выводы

    В этой лабораторной работе мы создали игру, предусматривающую несколько игровых уровней и конструктор уровней для пользователя. Как правило, разработка реальных игр включает в себя создание конструктора уровней, с помощью которого разработчик создает игровые уровни. Этот конструктор можно включить в поставку игры, предоставив пользователю возможность самостоятельно создавать новые уровни к игре. Конструкторы уровней так же разнообразны, как сами игры.

    Задание

    В этой лабораторной работе мы создали макет игры, допускающей использование нескольких уровней и содержащей конструктор уровней. Доработайте этот макет. А именно, выполните следующие улучшения исходного проекта:

  • Переработайте процедуры обработки столкновений мяча с блоками. Мяч должен отскакивать от блоков точно так же, как он отскакивает от границ экрана.
  • Создайте интерфейс игры – меню, систему справки.
  • Предусмотрите сохранение пользовательского уровня в файл (с помощью сериализации специально созданного объекта) и загрузку его из файла
  • Создайте систему подсчета очков, предусмотрите сохранение данных о набранных очках в файле.
  • Улучшите процесс прохождения игры – разработайте дополнительные уровни, сделайте игровой процесс разнообразнее, разработав бонусные объекты, которые появляются при уничтожении некоторых из блоков. Игрок должен "поймать" бонусный объект битой для того, чтобы активировать соответствующий бонус.
  • Вернуться к учебному плану