到目前为止,我们的代码都是文字输出。现在我们要做一个可以用键盘控制角色在地图上移动的控制台游戏。

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.cs):

using 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();
        }
    }
}

好了,这节课到此结束,关注我,下期更精彩

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐