HTML Diff
2 added 2 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#Руководства</a></p>
1 <p><a>#Руководства</a></p>
2 <ul><li>17 фев 2020</li>
2 <ul><li>17 фев 2020</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Заключительная часть серии статей про ООП, в которой мы создадим небольшой проект.</p>
4 </ul><p>Заключительная часть серии статей про ООП, в которой мы создадим небольшой проект.</p>
5 <p> vlada_maestro / shutterstock</p>
5 <p> vlada_maestro / shutterstock</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
7 <p>Это будет консольная игра с символьной графикой, в которой можно перемещаться по локации, атаковать NPC, открывать инвентарь и пользоваться предметами.</p>
7 <p>Это будет консольная игра с символьной графикой, в которой можно перемещаться по локации, атаковать NPC, открывать инвентарь и пользоваться предметами.</p>
8 <p>Устроена игра будет так:</p>
8 <p>Устроена игра будет так:</p>
9 <ul><li>При запуске вызывается метод<em>InitGame ()</em>, в котором будут созданы игровые объекты, предметы и прочее.</li>
9 <ul><li>При запуске вызывается метод<em>InitGame ()</em>, в котором будут созданы игровые объекты, предметы и прочее.</li>
10 <li>После будет вызван метод<em>Update ()</em>, который обновляет состояние игры.</li>
10 <li>После будет вызван метод<em>Update ()</em>, который обновляет состояние игры.</li>
11 </ul><p>Внутри метода<em>Update ()</em>должен находиться цикл со следующими действиями:</p>
11 </ul><p>Внутри метода<em>Update ()</em>должен находиться цикл со следующими действиями:</p>
12 <ul><li>Отрисовка локации или инвентаря.</li>
12 <ul><li>Отрисовка локации или инвентаря.</li>
13 <li>Получение нажатой игроком клавиши.</li>
13 <li>Получение нажатой игроком клавиши.</li>
14 <li>Передача клавиши в контроллер.</li>
14 <li>Передача клавиши в контроллер.</li>
15 <li>Контроллер, в зависимости от нажатой клавиши, будет вызывать методы игровых объектов: например,<em>Move ()</em>или<em>Use ().</em></li>
15 <li>Контроллер, в зависимости от нажатой клавиши, будет вызывать методы игровых объектов: например,<em>Move ()</em>или<em>Use ().</em></li>
16 </ul><p>Для управления игрой мы используем три статических класса-контроллера:</p>
16 </ul><p>Для управления игрой мы используем три статических класса-контроллера:</p>
17 <ul><li>LocationController - перехватывает действия игрока на локации.</li>
17 <ul><li>LocationController - перехватывает действия игрока на локации.</li>
18 <li>InventoryController - перехватывает действия игрока в инвентаре.</li>
18 <li>InventoryController - перехватывает действия игрока в инвентаре.</li>
19 <li>GraphicsController - управляет выводом.</li>
19 <li>GraphicsController - управляет выводом.</li>
20 </ul><p>Игровые данные<em>(размеры локации, список объектов)</em>находятся в статическом классе<em>Game</em>. Объекты будут реализованы с помощью классов<em>GameObject</em><em>(базовый)</em>,<em>Player</em>и<em>NPC</em>. За расположение и перемещение по локации пусть отвечает класс<em>Position</em>.</p>
20 </ul><p>Игровые данные<em>(размеры локации, список объектов)</em>находятся в статическом классе<em>Game</em>. Объекты будут реализованы с помощью классов<em>GameObject</em><em>(базовый)</em>,<em>Player</em>и<em>NPC</em>. За расположение и перемещение по локации пусть отвечает класс<em>Position</em>.</p>
21 - <p>Предметы реализуются с помощью классов<em>Item (базовый), Potion</em>и<em>Meal</em>. Если предмет может быть использован, то в нём реализуется интерфейс<em>IUsable</em>.</p>
21 + <p>Предметы реалзуются с помощью классов<em>Item (базовый), Potion</em>и<em>Meal</em>. Если предмет может быть использован, то в нём реализуется интерфейс<em>IUsable</em>.</p>
22 - <p>Проект небольшой, но если впихнуть его весь в статью, то для текста места не останется. Поэтому здесь будут только важные части. Чтобы ознакомиться с проектом полностью, скачайте есь исходный код из <a>репозитория на GitHub</a>.</p>
22 + <p>Проект небольшой, но если впихнуть его весь в статью, то для текста места не останется. Поэтому здесь будут только важные части. Чтобы ознакомиться с проектом полностью, скачайте весь исходный код из <a>репозитория на GitHub</a>.</p>
23 <p>Начнём с класса<em>GameObjects</em>- он будет родительским для<em>Player</em>и<em>NPC:</em></p>
23 <p>Начнём с класса<em>GameObjects</em>- он будет родительским для<em>Player</em>и<em>NPC:</em></p>
24 public class GameObject { private string name; private Position position; private ConsoleColor color; private int hpFull; private int hp; public GameObject() { } public GameObject(string name, Position position, ConsoleColor color) { this.name = name; this.position = position; this.color = color; hpFull = 100; hp = 80; } public void Attack(GameObject obj) { obj.TakeDamage(10); } public void TakeDamage(int dmg) { this.hp -= dmg; Console.Beep(); if(hp &lt;= 0) { Die(); } } private void Die() { Console.WriteLine($"{this.Name} died"); Console.Beep(); Console.ReadKey(); Game.Objects.Remove(this); } public void Heal(int val) { if(hp + val &gt; hpFull) { val = val - (hp + val - hpFull); } hp += val; Console.WriteLine($"\n{name} healed {val} HP!"); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } //Далее идут свойства, которые здесь опущены, чтобы не занимать место }<p>Обратите внимание на метод<em>Die ()</em>- он выполняется, когда у объекта кончается здоровье. Метод удаляет объект из коллекции<em>Game.Objects</em>, что освобождает память. Схожую функцию выполняют<a>деструкторы</a>- они вызываются перед тем, как объект будет удалён из памяти.</p>
24 public class GameObject { private string name; private Position position; private ConsoleColor color; private int hpFull; private int hp; public GameObject() { } public GameObject(string name, Position position, ConsoleColor color) { this.name = name; this.position = position; this.color = color; hpFull = 100; hp = 80; } public void Attack(GameObject obj) { obj.TakeDamage(10); } public void TakeDamage(int dmg) { this.hp -= dmg; Console.Beep(); if(hp &lt;= 0) { Die(); } } private void Die() { Console.WriteLine($"{this.Name} died"); Console.Beep(); Console.ReadKey(); Game.Objects.Remove(this); } public void Heal(int val) { if(hp + val &gt; hpFull) { val = val - (hp + val - hpFull); } hp += val; Console.WriteLine($"\n{name} healed {val} HP!"); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } //Далее идут свойства, которые здесь опущены, чтобы не занимать место }<p>Обратите внимание на метод<em>Die ()</em>- он выполняется, когда у объекта кончается здоровье. Метод удаляет объект из коллекции<em>Game.Objects</em>, что освобождает память. Схожую функцию выполняют<a>деструкторы</a>- они вызываются перед тем, как объект будет удалён из памяти.</p>
25 <p>Класс<em>NPC</em>позволит в дальнейшем реализовать логику для игрового ИИ.<em>Player</em> же содержит методы для управления персонажем игрока. Например, чуть позже мы реализуем в нём использование предметов из инвентаря.</p>
25 <p>Класс<em>NPC</em>позволит в дальнейшем реализовать логику для игрового ИИ.<em>Player</em> же содержит методы для управления персонажем игрока. Например, чуть позже мы реализуем в нём использование предметов из инвентаря.</p>
26 <p>Как уже говорилось выше, созданные объекты будут храниться в коллекции статического класса<em>Game</em>. Вот как это выглядит:</p>
26 <p>Как уже говорилось выше, созданные объекты будут храниться в коллекции статического класса<em>Game</em>. Вот как это выглядит:</p>
27 public static class Game { public static bool Play = true; //Запущена ли игра public static List&lt;GameObject&gt; Objects = new List&lt;GameObject&gt;(); //Игровые объекты public static Player Player; //Ссылка на игрока - сам объект также будет находиться в коллекции Objects public const int Width = 100; //Ширина локации public const int Height = 25; //Высота локации public static GameMode Mode = GameMode.Location; //Режим public static int Selection = -1; //Выбранный предмет в инвентаре }<p>Вы могли заметить тут тип данных<em>GameMode</em>- это класс перечислений, который упрощает создание списков в коде:</p>
27 public static class Game { public static bool Play = true; //Запущена ли игра public static List&lt;GameObject&gt; Objects = new List&lt;GameObject&gt;(); //Игровые объекты public static Player Player; //Ссылка на игрока - сам объект также будет находиться в коллекции Objects public const int Width = 100; //Ширина локации public const int Height = 25; //Высота локации public static GameMode Mode = GameMode.Location; //Режим public static int Selection = -1; //Выбранный предмет в инвентаре }<p>Вы могли заметить тут тип данных<em>GameMode</em>- это класс перечислений, который упрощает создание списков в коде:</p>
28 public enum GameMode { Location, Inventory }<p>Одна из альтернатив классам-перечислениям - числа. То есть мы могли бы просто написать так:</p>
28 public enum GameMode { Location, Inventory }<p>Одна из альтернатив классам-перечислениям - числа. То есть мы могли бы просто написать так:</p>
29 public static int Mode = 0; //0 - Локация, 1 - Инвентарь<p>Но это не очень удобно, потому что вам придётся всё время смотреть, какое число и для чего вы указывали.</p>
29 public static int Mode = 0; //0 - Локация, 1 - Инвентарь<p>Но это не очень удобно, потому что вам придётся всё время смотреть, какое число и для чего вы указывали.</p>
30 <p>Теперь, чтобы заставить объекты и локацию отображаться, напишем класс<em>GraphicsController:</em></p>
30 <p>Теперь, чтобы заставить объекты и локацию отображаться, напишем класс<em>GraphicsController:</em></p>
31 public static class GraphicsController { //Два следующих поля будут использованы для того, чтобы сохранить в себе рамки локации - вычисление количества символов при каждой отрисовке будет потреблять больше ресурсов public static string TopLine = ""; public static string MidLine = ""; public static void Draw(List&lt;GameObject&gt; objects) { Console.Clear(); DrawBorder(); foreach(GameObject obj in objects) { if(obj.HP &gt; 0) { Draw(obj); } } Console.SetCursorPosition(0, Game.Height + 1); } public static void Draw(GameObject obj) { Console.SetCursorPosition(obj.Position.X - obj.Position.WidthHalf, obj.Position.Y - obj.Position.HeightHalf); Console.ForegroundColor = obj.Color; string width = ""; char symbol = ' '; switch(obj.Position.Direction) { case Direction.Up: symbol = '↑'; break; case Direction.Down: symbol = '↓'; break; case Direction.Left: symbol = '←'; break; case Direction.Right: symbol = '→'; break; } for(int i = 0; i &lt; obj.Position.Width; i++) { width += symbol; } for(int i = 0; i &lt; obj.Position.Height; i++) { Console.SetCursorPosition(obj.Position.X - obj.Position.WidthHalf, obj.Position.Y - obj.Position.HeightHalf + i); Console.Write(width); } Console.ForegroundColor = ConsoleColor.White; } public static void DrawBorder() { Console.ForegroundColor = ConsoleColor.White; InitLines(); for(int i = 0; i &lt; Game.Height; i++) { if(i == 0 || i == Game.Height - 1) { Console.WriteLine(TopLine); } else { Console.WriteLine(MidLine); } } Console.WriteLine(Game.Player.Health); } private static void InitLines() { if(TopLine == "") { for(int i = 0; i &lt; Game.Width; i++) { if(i == 0 || i == Game.Width - 1) { TopLine += "+"; MidLine += "|"; } else { TopLine += "="; MidLine += " "; } } } } }<p>Объекты рисуются с помощью символов, которые меняются в зависимости от того, в какую сторону направлен объект.</p>
31 public static class GraphicsController { //Два следующих поля будут использованы для того, чтобы сохранить в себе рамки локации - вычисление количества символов при каждой отрисовке будет потреблять больше ресурсов public static string TopLine = ""; public static string MidLine = ""; public static void Draw(List&lt;GameObject&gt; objects) { Console.Clear(); DrawBorder(); foreach(GameObject obj in objects) { if(obj.HP &gt; 0) { Draw(obj); } } Console.SetCursorPosition(0, Game.Height + 1); } public static void Draw(GameObject obj) { Console.SetCursorPosition(obj.Position.X - obj.Position.WidthHalf, obj.Position.Y - obj.Position.HeightHalf); Console.ForegroundColor = obj.Color; string width = ""; char symbol = ' '; switch(obj.Position.Direction) { case Direction.Up: symbol = '↑'; break; case Direction.Down: symbol = '↓'; break; case Direction.Left: symbol = '←'; break; case Direction.Right: symbol = '→'; break; } for(int i = 0; i &lt; obj.Position.Width; i++) { width += symbol; } for(int i = 0; i &lt; obj.Position.Height; i++) { Console.SetCursorPosition(obj.Position.X - obj.Position.WidthHalf, obj.Position.Y - obj.Position.HeightHalf + i); Console.Write(width); } Console.ForegroundColor = ConsoleColor.White; } public static void DrawBorder() { Console.ForegroundColor = ConsoleColor.White; InitLines(); for(int i = 0; i &lt; Game.Height; i++) { if(i == 0 || i == Game.Height - 1) { Console.WriteLine(TopLine); } else { Console.WriteLine(MidLine); } } Console.WriteLine(Game.Player.Health); } private static void InitLines() { if(TopLine == "") { for(int i = 0; i &lt; Game.Width; i++) { if(i == 0 || i == Game.Width - 1) { TopLine += "+"; MidLine += "|"; } else { TopLine += "="; MidLine += " "; } } } } }<p>Объекты рисуются с помощью символов, которые меняются в зависимости от того, в какую сторону направлен объект.</p>
32 <p>Теперь можно обновить класс<em>Program</em>, чтобы создать первые объекты и отобразить их в консоли:</p>
32 <p>Теперь можно обновить класс<em>Program</em>, чтобы создать первые объекты и отобразить их в консоли:</p>
33 class Program { static void Main(string[] args) { InitGame(); Update(); } static void InitGame() { Game.Player = new Player("Hero", new Position(5, 10, 2, 2), ConsoleColor.White); Game.Objects.Add(Game.Player); Game.Objects.Add(new NPC("Enemy 1", new Position(10, 10, 2, 2), ConsoleColor.Red)); Game.Objects.Add(new NPC("Enemy 2", new Position(15, 10, 2, 2), ConsoleColor.Blue)); Game.Objects.Add(new NPC("Enemy 3", new Position(25, 20, 2, 2), ConsoleColor.Yellow)); Game.Objects.Add(new NPC("Enemy 4", new Position(35, 5, 2, 2), ConsoleColor.Green)); Game.Objects.Add(new NPC("Enemy 5", new Position(40, 3, 2, 2), ConsoleColor.Magenta)); } static void Update() { ConsoleKeyInfo e; while(Game.Play) { switch(Game.Mode) { case GameMode.Location: GraphicsController.Draw(Game.Objects); e = Console.ReadKey(); LocationController.Controll(e); //Этот метод разберём в следующем разделе break; } } } }<p>Вот что должно быть выведено:</p>
33 class Program { static void Main(string[] args) { InitGame(); Update(); } static void InitGame() { Game.Player = new Player("Hero", new Position(5, 10, 2, 2), ConsoleColor.White); Game.Objects.Add(Game.Player); Game.Objects.Add(new NPC("Enemy 1", new Position(10, 10, 2, 2), ConsoleColor.Red)); Game.Objects.Add(new NPC("Enemy 2", new Position(15, 10, 2, 2), ConsoleColor.Blue)); Game.Objects.Add(new NPC("Enemy 3", new Position(25, 20, 2, 2), ConsoleColor.Yellow)); Game.Objects.Add(new NPC("Enemy 4", new Position(35, 5, 2, 2), ConsoleColor.Green)); Game.Objects.Add(new NPC("Enemy 5", new Position(40, 3, 2, 2), ConsoleColor.Magenta)); } static void Update() { ConsoleKeyInfo e; while(Game.Play) { switch(Game.Mode) { case GameMode.Location: GraphicsController.Draw(Game.Objects); e = Console.ReadKey(); LocationController.Controll(e); //Этот метод разберём в следующем разделе break; } } } }<p>Вот что должно быть выведено:</p>
34 <p>Чтобы заставить объект игрока двигаться, реализуем управление:</p>
34 <p>Чтобы заставить объект игрока двигаться, реализуем управление:</p>
35 public static class LocationController { public static void Controll(ConsoleKeyInfo e) { Direction d = Direction.None; switch(e.Key) { case ConsoleKey.UpArrow: d = Direction.Up; break; case ConsoleKey.DownArrow: d = Direction.Down; break; case ConsoleKey.LeftArrow: d = Direction.Left; break; case ConsoleKey.RightArrow: d = Direction.Right; break; case ConsoleKey.A: GameObject obj = null; int dY = 0; int dX = 0; switch(Game.Player.Position.Direction) { case Direction.Up: dY = -1; break; case Direction.Down: dY = 1; break; case Direction.Left: dX = -1; break; case Direction.Right: dX = 1; break; } int tempX = Game.Player.Position.X + dX; int tempY = Game.Player.Position.Y + dY; obj = Game.Player.Position.GetCollision(Game.Objects, tempX, tempY); if(obj != null) { Game.Player.Attack(obj); } break; case ConsoleKey.Escape: Console.SetCursorPosition(0, Game.Height + 1); Console.WriteLine("Are you sure you want to exit? (y/n)"); e = Console.ReadKey(); Console.WriteLine("\nGood Bye!"); if(e.Key == ConsoleKey.Y) { Game.Play = false; } break; case ConsoleKey.I: InventoryController.Open(); break; } if(d != Direction.None) { Game.Player.Position.Move(d); } } }<p>Этот класс проверяет нажатую игроком клавишу и передаёт команды дальше. Например, классу<em>Position</em>, который отвечает за перемещение объектов по локации.</p>
35 public static class LocationController { public static void Controll(ConsoleKeyInfo e) { Direction d = Direction.None; switch(e.Key) { case ConsoleKey.UpArrow: d = Direction.Up; break; case ConsoleKey.DownArrow: d = Direction.Down; break; case ConsoleKey.LeftArrow: d = Direction.Left; break; case ConsoleKey.RightArrow: d = Direction.Right; break; case ConsoleKey.A: GameObject obj = null; int dY = 0; int dX = 0; switch(Game.Player.Position.Direction) { case Direction.Up: dY = -1; break; case Direction.Down: dY = 1; break; case Direction.Left: dX = -1; break; case Direction.Right: dX = 1; break; } int tempX = Game.Player.Position.X + dX; int tempY = Game.Player.Position.Y + dY; obj = Game.Player.Position.GetCollision(Game.Objects, tempX, tempY); if(obj != null) { Game.Player.Attack(obj); } break; case ConsoleKey.Escape: Console.SetCursorPosition(0, Game.Height + 1); Console.WriteLine("Are you sure you want to exit? (y/n)"); e = Console.ReadKey(); Console.WriteLine("\nGood Bye!"); if(e.Key == ConsoleKey.Y) { Game.Play = false; } break; case ConsoleKey.I: InventoryController.Open(); break; } if(d != Direction.None) { Game.Player.Position.Move(d); } } }<p>Этот класс проверяет нажатую игроком клавишу и передаёт команды дальше. Например, классу<em>Position</em>, который отвечает за перемещение объектов по локации.</p>
36 public class Position { private int x; private int y; private int width; private int height; private int widthHalf; private int heightHalf; private Direction direction; public Position(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; this.widthHalf = width / 2; this.heightHalf = height / 2; direction = Direction.Up; } public bool Move(Direction d) { int dX = 0; int dY = 0; direction = d; switch(d) { case Direction.Up: dY = -1; break; case Direction.Down: dY = 1; break; case Direction.Left: dX = -1; break; case Direction.Right: dX = 1; break; } int tempX = x + dX; int tempY = y + dY; bool collided = Collide(Game.Objects, tempX, tempY); if(collided) { return false; } else { this.x = tempX; this.y = tempY; return true; } } public GameObject GetCollision(List&lt;GameObject&gt; objects, int tempX, int tempY) { GameObject obj = null; for(int i = 0; i &lt; objects.Count; i++) { if(objects[i].Position != this) { if(Collide(objects[i].Position, tempX, tempY)) { obj = objects[i]; break; } } } return obj; } public bool Collide(List&lt;GameObject&gt; objects, int tempX, int tempY) { GameObject obj = GetCollision(objects, tempX, tempY); if(obj == null) { if( tempX - widthHalf &lt; 1 || tempX + widthHalf &gt; Game.Width - 1 || tempY - heightHalf &lt; 1 || tempY + heightHalf &gt; Game.Height - 1 ) { return true; } else { return false; } } else { return true; } } public bool Collide(Position obj, int tempX, int tempY) { bool collided = false; if( tempX + widthHalf &gt; obj.X - obj.WidthHalf &amp;&amp; tempX - widthHalf &lt; obj.X + obj.WidthHalf ) { if( tempY + heightHalf &gt; obj.Y - obj.HeightHalf &amp;&amp; tempY - heightHalf &lt; obj.Y + obj.HeightHalf ) { collided = true; } } return collided; } }<p>Можно проверить, как работает перемещение:</p>
36 public class Position { private int x; private int y; private int width; private int height; private int widthHalf; private int heightHalf; private Direction direction; public Position(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; this.widthHalf = width / 2; this.heightHalf = height / 2; direction = Direction.Up; } public bool Move(Direction d) { int dX = 0; int dY = 0; direction = d; switch(d) { case Direction.Up: dY = -1; break; case Direction.Down: dY = 1; break; case Direction.Left: dX = -1; break; case Direction.Right: dX = 1; break; } int tempX = x + dX; int tempY = y + dY; bool collided = Collide(Game.Objects, tempX, tempY); if(collided) { return false; } else { this.x = tempX; this.y = tempY; return true; } } public GameObject GetCollision(List&lt;GameObject&gt; objects, int tempX, int tempY) { GameObject obj = null; for(int i = 0; i &lt; objects.Count; i++) { if(objects[i].Position != this) { if(Collide(objects[i].Position, tempX, tempY)) { obj = objects[i]; break; } } } return obj; } public bool Collide(List&lt;GameObject&gt; objects, int tempX, int tempY) { GameObject obj = GetCollision(objects, tempX, tempY); if(obj == null) { if( tempX - widthHalf &lt; 1 || tempX + widthHalf &gt; Game.Width - 1 || tempY - heightHalf &lt; 1 || tempY + heightHalf &gt; Game.Height - 1 ) { return true; } else { return false; } } else { return true; } } public bool Collide(Position obj, int tempX, int tempY) { bool collided = false; if( tempX + widthHalf &gt; obj.X - obj.WidthHalf &amp;&amp; tempX - widthHalf &lt; obj.X + obj.WidthHalf ) { if( tempY + heightHalf &gt; obj.Y - obj.HeightHalf &amp;&amp; tempY - heightHalf &lt; obj.Y + obj.HeightHalf ) { collided = true; } } return collided; } }<p>Можно проверить, как работает перемещение:</p>
37 <p>А вместе с перемещением и боевую систему:</p>
37 <p>А вместе с перемещением и боевую систему:</p>
38 <p>Инвентарь начинается с класса<em>Item</em>:</p>
38 <p>Инвентарь начинается с класса<em>Item</em>:</p>
39 public class Item { private string name; public Item(string name) { this.name = name; } public string Name { get { return this.name; } } }<p>Классы<em>Potion</em>и<em>Meal</em>практически идентичные, за исключением выводимых надписей. Поэтому здесь будет показан код только одного из классов:</p>
39 public class Item { private string name; public Item(string name) { this.name = name; } public string Name { get { return this.name; } } }<p>Классы<em>Potion</em>и<em>Meal</em>практически идентичные, за исключением выводимых надписей. Поэтому здесь будет показан код только одного из классов:</p>
40 public class Potion : Item, IUsable { private int hpVal; public Potion(string name, int hpVal) :base(name) { this.hpVal = hpVal; } public void Use(Player p) { p.Heal(hpVal); } public string Description { get { return $"A flask of {this.Name} restores {this.hpVal} HP."; } } }<p>Можно заметить, что класс наследует интерфейс<em>IUsable</em>:</p>
40 public class Potion : Item, IUsable { private int hpVal; public Potion(string name, int hpVal) :base(name) { this.hpVal = hpVal; } public void Use(Player p) { p.Heal(hpVal); } public string Description { get { return $"A flask of {this.Name} restores {this.hpVal} HP."; } } }<p>Можно заметить, что класс наследует интерфейс<em>IUsable</em>:</p>
41 public interface IUsable { public void Use(Player p); }<p>Теперь, чтобы пользователь мог посмотреть предметы в инвентаре, нужно добавить в <em>GraphicsController</em>следующий метод:</p>
41 public interface IUsable { public void Use(Player p); }<p>Теперь, чтобы пользователь мог посмотреть предметы в инвентаре, нужно добавить в <em>GraphicsController</em>следующий метод:</p>
42 public static void DrawInventory(List&lt;Item&gt; items) { Console.Clear(); Console.SetCursorPosition(0, 0); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Inventory"); for(int i = 0; i &lt; items.Count; i++) { Console.ForegroundColor = ConsoleColor.Gray; if(i == Game.Selection) { Console.ForegroundColor = ConsoleColor.Blue; } Console.WriteLine(items[i].Name); } Console.ForegroundColor = ConsoleColor.Gray; }<p>Также в классе<em>Program</em>добавьте в<em>switch</em>с режимами следующий вариант:</p>
42 public static void DrawInventory(List&lt;Item&gt; items) { Console.Clear(); Console.SetCursorPosition(0, 0); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Inventory"); for(int i = 0; i &lt; items.Count; i++) { Console.ForegroundColor = ConsoleColor.Gray; if(i == Game.Selection) { Console.ForegroundColor = ConsoleColor.Blue; } Console.WriteLine(items[i].Name); } Console.ForegroundColor = ConsoleColor.Gray; }<p>Также в классе<em>Program</em>добавьте в<em>switch</em>с режимами следующий вариант:</p>
43 case GameMode.Inventory: if(Game.Player.Inventory.Count == 0) { InventoryController.Close(); break; } GraphicsController.DrawInventory(Game.Player.Inventory); e = Console.ReadKey(); InventoryController.Controll(e); break;<p>Управление будет производиться с помощью<em>InventoryController:</em></p>
43 case GameMode.Inventory: if(Game.Player.Inventory.Count == 0) { InventoryController.Close(); break; } GraphicsController.DrawInventory(Game.Player.Inventory); e = Console.ReadKey(); InventoryController.Controll(e); break;<p>Управление будет производиться с помощью<em>InventoryController:</em></p>
44 public static class InventoryController { public static void Controll(ConsoleKeyInfo e) { switch(e.Key) { case ConsoleKey.UpArrow: if(Game.Selection != 0) { Game.Selection--; } break; case ConsoleKey.DownArrow: if(Game.Selection &lt; Game.Player.Inventory.Count - 1) { Game.Selection++; } break; case ConsoleKey.E: Game.Player.Use(Game.Selection); Game.Selection = 0; break; case ConsoleKey.Escape: Close(); break; } } public static void Open() { if(Game.Player.Inventory.Count &gt; 0) { Game.Mode = GameMode.Inventory; Game.Selection = 0; } else { Console.SetCursorPosition(0, Game.Height + 1); Console.WriteLine("Your inventory is empty! \nPress any key to continue..."); Console.ReadKey(); } } public static void Close() { Game.Selection = -1; Game.Mode = GameMode.Location; } }<p>Теперь самое интересное - объект<em>Player:</em></p>
44 public static class InventoryController { public static void Controll(ConsoleKeyInfo e) { switch(e.Key) { case ConsoleKey.UpArrow: if(Game.Selection != 0) { Game.Selection--; } break; case ConsoleKey.DownArrow: if(Game.Selection &lt; Game.Player.Inventory.Count - 1) { Game.Selection++; } break; case ConsoleKey.E: Game.Player.Use(Game.Selection); Game.Selection = 0; break; case ConsoleKey.Escape: Close(); break; } } public static void Open() { if(Game.Player.Inventory.Count &gt; 0) { Game.Mode = GameMode.Inventory; Game.Selection = 0; } else { Console.SetCursorPosition(0, Game.Height + 1); Console.WriteLine("Your inventory is empty! \nPress any key to continue..."); Console.ReadKey(); } } public static void Close() { Game.Selection = -1; Game.Mode = GameMode.Location; } }<p>Теперь самое интересное - объект<em>Player:</em></p>
45 public class Player : GameObject { private List&lt;Item&gt; inventory; public Player(string name, Position position, ConsoleColor color) :base(name, position, color) { inventory = new List&lt;Item&gt;(); } public void Use(int index) { if(inventory[index] is IUsable) { IUsable item = inventory[index] as IUsable; item.Use(this); inventory.RemoveAt(index); } else { Console.WriteLine("You can't use that!"); } } public List&lt;Item&gt; Inventory { get { return this.inventory; } } }<p>Тут вы можете увидеть два новых ключевых слова:</p>
45 public class Player : GameObject { private List&lt;Item&gt; inventory; public Player(string name, Position position, ConsoleColor color) :base(name, position, color) { inventory = new List&lt;Item&gt;(); } public void Use(int index) { if(inventory[index] is IUsable) { IUsable item = inventory[index] as IUsable; item.Use(this); inventory.RemoveAt(index); } else { Console.WriteLine("You can't use that!"); } } public List&lt;Item&gt; Inventory { get { return this.inventory; } } }<p>Тут вы можете увидеть два новых ключевых слова:</p>
46 <ul><li><strong>is</strong> - проверяет, реализован ли в данном объекте указанный интерфейс;</li>
46 <ul><li><strong>is</strong> - проверяет, реализован ли в данном объекте указанный интерфейс;</li>
47 <li><strong>as</strong> - получает реализацию интерфейса.</li>
47 <li><strong>as</strong> - получает реализацию интерфейса.</li>
48 </ul><p>Вот что получилось в итоге:</p>
48 </ul><p>Вот что получилось в итоге:</p>
49 <p>На примере небольшой игры мы посмотрели, как используются особенности объектно-ориентированного программирования, чтобы было проще писать код.</p>
49 <p>На примере небольшой игры мы посмотрели, как используются особенности объектно-ориентированного программирования, чтобы было проще писать код.</p>
50 <p>Сама игра очень маленькая, и закончить её вам предстоит самостоятельно. Ваша задача - заставить NPC двигаться по какому-нибудь паттерну. Если ИИ натыкается на игрока, то он должен атаковать, прекращая движение по паттерну и начиная преследование, пока игрок не убежит на несколько шагов.</p>
50 <p>Сама игра очень маленькая, и закончить её вам предстоит самостоятельно. Ваша задача - заставить NPC двигаться по какому-нибудь паттерну. Если ИИ натыкается на игрока, то он должен атаковать, прекращая движение по паттерну и начиная преследование, пока игрок не убежит на несколько шагов.</p>
51 <p>Надеюсь, эта серия статей была вам полезной и вы смогли разобраться, что же такое ООП, зачем и как его использовать. На этом серия заканчивается, но если вам хочется узнать больше, то можете записаться<a>на наш бесплатный курс по C#</a>. Там вы напишете столько классов, что ООП станет вашей второй кожей и вы сможете мастерски его использовать.</p>
51 <p>Надеюсь, эта серия статей была вам полезной и вы смогли разобраться, что же такое ООП, зачем и как его использовать. На этом серия заканчивается, но если вам хочется узнать больше, то можете записаться<a>на наш бесплатный курс по C#</a>. Там вы напишете столько классов, что ООП станет вашей второй кожей и вы сможете мастерски его использовать.</p>
52 <a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
52 <a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>