从零开发游戏需要学习的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.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();
}
}
}
好了,这节课到此结束,关注我,下期更精彩
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)