到目前为止我们的代码都是文字输出。现在我们要做一个可以用键盘控制角色在地图上移动的控制台游戏。1. 核心技术实时读取键盘输入之前我们用的Console.ReadKey()会让程序停下来等按键。但游戏需要不停运行同时检测按键。// 关键方法Console.KeyAvailable// 它检查“是否有按键被按下”但不会让程序停下来等while (true){if (Console.KeyAvailable) // 如果有按键按下{ConsoleKeyInfo key Console.ReadKey(true); // true 不显示按下的字符Console.WriteLine(你按下了 key.Key);}// 这里可以放其他持续运行的逻辑}Console.ReadKey(true)的参数true表示拦截按键不把它显示在控制台上。否则你按方向键会在屏幕上出现奇怪的字符。2. 控制台坐标系统控制台是一个网格每个字符占一个格子。(0,0) ────────── X轴列──→││ Y轴行│↓Console.SetCursorPosition(x, y)把光标移到指定位置Console.CursorVisible false隐藏闪烁的光标Console.Clear()清屏举个例子在指定位置画一个字符Console.CursorVisible false;Console.SetCursorPosition(10, 5);Console.Write(); // 在第10列、第5行画一个 3. 游戏主循环所有游戏都有一个主循环它不停地处理输入 → 更新逻辑 → 渲染画面。bool isRunning true; while (isRunning) { // 1. 处理输入 if (Console.KeyAvailable) { ConsoleKeyInfo key Console.ReadKey(true); if (key.Key ConsoleKey.Escape) { isRunning false; // 按 ESC 退出 } // 其他按键处理... } // 2. 更新游戏逻辑 // 移动、碰撞检测、AI 等等... // 3. 渲染画面 // 画地图、画角色、画敌人... // 4. 控制帧率可选让游戏不要太快 Thread.Sleep(50); // 暂停50毫秒大约20帧/秒 }4. 实战控制台勇者移动我们做一个完整的程序一个代表勇者用方向键在地图上移动。完整代码单文件Program.csusing System; using System.Threading; namespace MyGame { class Program { // 玩家位置 static int playerX 10; static int playerY 10; // 地图边界 static int mapWidth 30; static int mapHeight 15; static bool isRunning true; static void Main(string[] args) { // 初始化控制台 Console.CursorVisible false; Console.Title 控制台RPG - 方向键移动ESC退出; DrawBorder(); // 画地图边界 // 游戏主循环 while (isRunning) { // 1. 处理输入 HandleInput(); // 2. 渲染 DrawPlayer(); // 3. 控制帧率 Thread.Sleep(30); } // 游戏结束 Console.SetCursorPosition(0, mapHeight 2); Console.WriteLine(游戏结束按任意键退出...); Console.ReadKey(); } static void HandleInput() { if (Console.KeyAvailable) { ConsoleKeyInfo key Console.ReadKey(true); // 先擦除旧位置的玩家 EraseOldPosition(); // 根据按键移动 switch (key.Key) { case ConsoleKey.UpArrow: if (playerY 1) playerY--; break; case ConsoleKey.DownArrow: if (playerY mapHeight - 2) playerY; break; case ConsoleKey.LeftArrow: if (playerX 1) playerX--; break; case ConsoleKey.RightArrow: if (playerX mapWidth - 2) playerX; break; case ConsoleKey.Escape: isRunning false; break; } } } static void EraseOldPosition() { Console.SetCursorPosition(playerX, playerY); Console.Write( ); // 用空格覆盖掉旧位置的 } static void DrawPlayer() { Console.SetCursorPosition(playerX, playerY); Console.ForegroundColor ConsoleColor.Yellow; Console.Write(); Console.ResetColor(); } static void DrawBorder() { Console.Clear(); // 画上下边界 for (int x 0; x mapWidth; x) { Console.SetCursorPosition(x, 0); Console.Write(#); Console.SetCursorPosition(x, mapHeight - 1); Console.Write(#); } // 画左右边界 for (int y 0; y mapHeight; y) { Console.SetCursorPosition(0, y); Console.Write(#); Console.SetCursorPosition(mapWidth - 1, y); Console.Write(#); } } } }5. 用面向对象重构单文件写所有逻辑太乱了我们按之前学的内容拆分成类。Player.cs—— 玩家类using System;namespace MyGame{class Player{public int X { get; set; }public int Y { get; set; }public char Symbol { get; private set; }public ConsoleColor Color { get; private set; }private int previousX;private int previousY;public Player(int startX, int startY){X startX;Y startY;previousX X;previousY Y;Symbol ;Color ConsoleColor.Yellow;}// 保存当前位置为“旧位置”public void SavePosition(){previousX X;previousY Y;}// 擦除旧位置的显示public void EraseOld(){Console.SetCursorPosition(previousX, previousY);Console.Write( );}// 在当前位置绘制public void Draw(){Console.SetCursorPosition(X, Y);Console.ForegroundColor Color;Console.Write(Symbol);Console.ResetColor();}// 移动带边界检查public void Move(int dx, int dy, int minX, int minY, int maxX, int maxY){SavePosition();int newX X dx;int newY Y dy;if (newX minX newX maxX newY minY newY maxY){X newX;Y newY;}}}}GameMap.cs—— 地图类using System; namespace MyGame { class GameMap { public int Width { get; private set; } public int Height { get; private set; } public GameMap(int width, int height) { Width width; Height height; } public void Draw() { Console.Clear(); for (int x 0; x Width; x) { Console.SetCursorPosition(x, 0); Console.Write(#); Console.SetCursorPosition(x, Height - 1); Console.Write(#); } for (int y 0; y Height; y) { Console.SetCursorPosition(0, y); Console.Write(#); Console.SetCursorPosition(Width - 1, y); Console.Write(#); } } } }InputHandler.cs—— 输入处理类using System; namespace MyGame { class InputHandler { public enum Command { None, MoveUp, MoveDown, MoveLeft, MoveRight, Quit } public Command GetCommand() { if (Console.KeyAvailable) { ConsoleKeyInfo key Console.ReadKey(true); switch (key.Key) { case ConsoleKey.UpArrow: return Command.MoveUp; case ConsoleKey.DownArrow: return Command.MoveDown; case ConsoleKey.LeftArrow: return Command.MoveLeft; case ConsoleKey.RightArrow: return Command.MoveRight; case ConsoleKey.Escape: return Command.Quit; } } return Command.None; } } }Game.cs—— 游戏管理器单例using System; using System.Threading; namespace MyGame { class Game { private static Game instance; public static Game Instance { get { if (instance null) instance new Game(); return instance; } } private Game() { } private bool isRunning; private Player player; private GameMap map; private InputHandler input; public void Start() { Console.CursorVisible false; Console.Title 控制台RPG; map new GameMap(40, 20); player new Player(map.Width / 2, map.Height / 2); input new InputHandler(); isRunning true; map.Draw(); // 主循环 while (isRunning) { Update(); Render(); Thread.Sleep(30); } Console.SetCursorPosition(0, map.Height 1); Console.WriteLine(游戏结束按任意键退出...); Console.ReadKey(); } private void Update() { InputHandler.Command cmd input.GetCommand(); switch (cmd) { case InputHandler.Command.MoveUp: player.Move(0, -1, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.MoveDown: player.Move(0, 1, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.MoveLeft: player.Move(-1, 0, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.MoveRight: player.Move(1, 0, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.Quit: isRunning false; break; } } private void Render() { player.EraseOld(); player.Draw(); } } }Program.cs—— 启动入口namespace MyGame { class Program { static void Main(string[] args) { Game.Instance.Start(); } } }好了这节课到此结束关注我下期更精彩
从零开发游戏需要学习的c#模块,第十一章(rpg小游戏入门,上篇,地图与移动)
到目前为止我们的代码都是文字输出。现在我们要做一个可以用键盘控制角色在地图上移动的控制台游戏。1. 核心技术实时读取键盘输入之前我们用的Console.ReadKey()会让程序停下来等按键。但游戏需要不停运行同时检测按键。// 关键方法Console.KeyAvailable// 它检查“是否有按键被按下”但不会让程序停下来等while (true){if (Console.KeyAvailable) // 如果有按键按下{ConsoleKeyInfo key Console.ReadKey(true); // true 不显示按下的字符Console.WriteLine(你按下了 key.Key);}// 这里可以放其他持续运行的逻辑}Console.ReadKey(true)的参数true表示拦截按键不把它显示在控制台上。否则你按方向键会在屏幕上出现奇怪的字符。2. 控制台坐标系统控制台是一个网格每个字符占一个格子。(0,0) ────────── X轴列──→││ Y轴行│↓Console.SetCursorPosition(x, y)把光标移到指定位置Console.CursorVisible false隐藏闪烁的光标Console.Clear()清屏举个例子在指定位置画一个字符Console.CursorVisible false;Console.SetCursorPosition(10, 5);Console.Write(); // 在第10列、第5行画一个 3. 游戏主循环所有游戏都有一个主循环它不停地处理输入 → 更新逻辑 → 渲染画面。bool isRunning true; while (isRunning) { // 1. 处理输入 if (Console.KeyAvailable) { ConsoleKeyInfo key Console.ReadKey(true); if (key.Key ConsoleKey.Escape) { isRunning false; // 按 ESC 退出 } // 其他按键处理... } // 2. 更新游戏逻辑 // 移动、碰撞检测、AI 等等... // 3. 渲染画面 // 画地图、画角色、画敌人... // 4. 控制帧率可选让游戏不要太快 Thread.Sleep(50); // 暂停50毫秒大约20帧/秒 }4. 实战控制台勇者移动我们做一个完整的程序一个代表勇者用方向键在地图上移动。完整代码单文件Program.csusing System; using System.Threading; namespace MyGame { class Program { // 玩家位置 static int playerX 10; static int playerY 10; // 地图边界 static int mapWidth 30; static int mapHeight 15; static bool isRunning true; static void Main(string[] args) { // 初始化控制台 Console.CursorVisible false; Console.Title 控制台RPG - 方向键移动ESC退出; DrawBorder(); // 画地图边界 // 游戏主循环 while (isRunning) { // 1. 处理输入 HandleInput(); // 2. 渲染 DrawPlayer(); // 3. 控制帧率 Thread.Sleep(30); } // 游戏结束 Console.SetCursorPosition(0, mapHeight 2); Console.WriteLine(游戏结束按任意键退出...); Console.ReadKey(); } static void HandleInput() { if (Console.KeyAvailable) { ConsoleKeyInfo key Console.ReadKey(true); // 先擦除旧位置的玩家 EraseOldPosition(); // 根据按键移动 switch (key.Key) { case ConsoleKey.UpArrow: if (playerY 1) playerY--; break; case ConsoleKey.DownArrow: if (playerY mapHeight - 2) playerY; break; case ConsoleKey.LeftArrow: if (playerX 1) playerX--; break; case ConsoleKey.RightArrow: if (playerX mapWidth - 2) playerX; break; case ConsoleKey.Escape: isRunning false; break; } } } static void EraseOldPosition() { Console.SetCursorPosition(playerX, playerY); Console.Write( ); // 用空格覆盖掉旧位置的 } static void DrawPlayer() { Console.SetCursorPosition(playerX, playerY); Console.ForegroundColor ConsoleColor.Yellow; Console.Write(); Console.ResetColor(); } static void DrawBorder() { Console.Clear(); // 画上下边界 for (int x 0; x mapWidth; x) { Console.SetCursorPosition(x, 0); Console.Write(#); Console.SetCursorPosition(x, mapHeight - 1); Console.Write(#); } // 画左右边界 for (int y 0; y mapHeight; y) { Console.SetCursorPosition(0, y); Console.Write(#); Console.SetCursorPosition(mapWidth - 1, y); Console.Write(#); } } } }5. 用面向对象重构单文件写所有逻辑太乱了我们按之前学的内容拆分成类。Player.cs—— 玩家类using System;namespace MyGame{class Player{public int X { get; set; }public int Y { get; set; }public char Symbol { get; private set; }public ConsoleColor Color { get; private set; }private int previousX;private int previousY;public Player(int startX, int startY){X startX;Y startY;previousX X;previousY Y;Symbol ;Color ConsoleColor.Yellow;}// 保存当前位置为“旧位置”public void SavePosition(){previousX X;previousY Y;}// 擦除旧位置的显示public void EraseOld(){Console.SetCursorPosition(previousX, previousY);Console.Write( );}// 在当前位置绘制public void Draw(){Console.SetCursorPosition(X, Y);Console.ForegroundColor Color;Console.Write(Symbol);Console.ResetColor();}// 移动带边界检查public void Move(int dx, int dy, int minX, int minY, int maxX, int maxY){SavePosition();int newX X dx;int newY Y dy;if (newX minX newX maxX newY minY newY maxY){X newX;Y newY;}}}}GameMap.cs—— 地图类using System; namespace MyGame { class GameMap { public int Width { get; private set; } public int Height { get; private set; } public GameMap(int width, int height) { Width width; Height height; } public void Draw() { Console.Clear(); for (int x 0; x Width; x) { Console.SetCursorPosition(x, 0); Console.Write(#); Console.SetCursorPosition(x, Height - 1); Console.Write(#); } for (int y 0; y Height; y) { Console.SetCursorPosition(0, y); Console.Write(#); Console.SetCursorPosition(Width - 1, y); Console.Write(#); } } } }InputHandler.cs—— 输入处理类using System; namespace MyGame { class InputHandler { public enum Command { None, MoveUp, MoveDown, MoveLeft, MoveRight, Quit } public Command GetCommand() { if (Console.KeyAvailable) { ConsoleKeyInfo key Console.ReadKey(true); switch (key.Key) { case ConsoleKey.UpArrow: return Command.MoveUp; case ConsoleKey.DownArrow: return Command.MoveDown; case ConsoleKey.LeftArrow: return Command.MoveLeft; case ConsoleKey.RightArrow: return Command.MoveRight; case ConsoleKey.Escape: return Command.Quit; } } return Command.None; } } }Game.cs—— 游戏管理器单例using System; using System.Threading; namespace MyGame { class Game { private static Game instance; public static Game Instance { get { if (instance null) instance new Game(); return instance; } } private Game() { } private bool isRunning; private Player player; private GameMap map; private InputHandler input; public void Start() { Console.CursorVisible false; Console.Title 控制台RPG; map new GameMap(40, 20); player new Player(map.Width / 2, map.Height / 2); input new InputHandler(); isRunning true; map.Draw(); // 主循环 while (isRunning) { Update(); Render(); Thread.Sleep(30); } Console.SetCursorPosition(0, map.Height 1); Console.WriteLine(游戏结束按任意键退出...); Console.ReadKey(); } private void Update() { InputHandler.Command cmd input.GetCommand(); switch (cmd) { case InputHandler.Command.MoveUp: player.Move(0, -1, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.MoveDown: player.Move(0, 1, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.MoveLeft: player.Move(-1, 0, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.MoveRight: player.Move(1, 0, 1, 1, map.Width - 2, map.Height - 2); break; case InputHandler.Command.Quit: isRunning false; break; } } private void Render() { player.EraseOld(); player.Draw(); } } }Program.cs—— 启动入口namespace MyGame { class Program { static void Main(string[] args) { Game.Instance.Start(); } } }好了这节课到此结束关注我下期更精彩