【问题标题】:Huge flickering in simple console game in C#C# 简单控制台游戏中的巨大闪烁
【发布时间】:2015-04-10 23:40:00
【问题描述】:

我正在用 C# 制作我的第一个控制台游戏,这是一个简单的迷宫游戏,但由于某种原因,我在屏幕上出现了可笑的闪烁量。我试过使用 Thread.Sleep 和 Console.CursorVisible=false;但无济于事。 万一你卡住了,按 1 然后在标题屏幕上输入,这将引导你进入仍处于 pre-alpha 阶段的迷宫。如果它有所作为,我将使用 Visual Studio 2013 作为 IDE。我的问题是如何消除迷宫部分的过度闪烁。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Threading;

class Game
{
    static void Main()
    {
        Console.WriteLine("Select Level (Available levels: 1,2):");
        Console.WriteLine("\n(\\_/)\n(o.o)\n(___)0\n");
        int gameLevel = int.Parse(Console.ReadLine()); // by pressing a number the user can select different labyrinths.
        // Console Height and Width
        Console.BufferHeight = Console.WindowHeight = 25;
        Console.BufferWidth = Console.WindowWidth = 80;
        Console.OutputEncoding = System.Text.Encoding.Unicode;  // Must have this + Change font to Lucida in CMD

        // Reads File:
        string map = File.ReadAllText(String.Format("level{0}.txt", gameLevel));
        string[] mapRows = Regex.Split(map, "\r\n");
        int mapSize = mapRows[0].Length;
        int mapHeight = mapRows.Count() - 1;
        char[,] charMap = new char[mapHeight, mapSize];

        // Creates Matrix:
        for (int row = 0; row < mapHeight; row++)
        {
            for (int col = 0; col < mapSize; col++)
            {
                charMap[row, col] = mapRows[row].ElementAt(col);
            }
        }
        // Rabbit init:
        string rabbitIcon = "\u0150";   //  \u0150   \u014E    \u00D2     \u00D3 --> alternatives
        int rabbitX = 1, rabbitY = 0;
        int carrotCounter = 0;
        // Game Loop:
        while (true)
        {
            DrawLabyrinth(mapHeight, mapSize, charMap);
            MoveRabbit(mapHeight, mapSize, ref rabbitX, ref rabbitY, charMap);
            EatCarrot(rabbitX, rabbitY, charMap,carrotCounter);
            Console.SetCursorPosition(rabbitX, rabbitY);
            Console.Write(rabbitIcon);
            Thread.Sleep(66);
            Console.CursorVisible = false;
            Console.Clear();

        }
    }
    static void EatCarrot(int rabbitX, int rabbitY, char[,] theMap,int carrotCount)
    {
        if (theMap[rabbitY, rabbitX] == '7' || theMap[rabbitY, rabbitX] == '8')
        {
            if (theMap[rabbitY, rabbitX] == '7')
            {
                theMap[rabbitY, rabbitX] = ' ';
                theMap[rabbitY - 1, rabbitX] = ' ';
                carrotCount++;

            }
            else if (theMap[rabbitY, rabbitX] == '8')
            {
                theMap[rabbitY, rabbitX] = ' ';
                theMap[rabbitY + 1, rabbitX] = ' ';
                carrotCount++;
            }
        }

    }

    static void MoveRabbit(int height, int width, ref int rabbitX, ref int rabbitY, char[,] theMap)
    {
        if (Console.KeyAvailable == true)
        {
            ConsoleKeyInfo pressedKey = Console.ReadKey(true);
            while (Console.KeyAvailable) Console.ReadKey(true);
            if (pressedKey.Key == ConsoleKey.LeftArrow || pressedKey.Key == ConsoleKey.A)
            {
                if (theMap[rabbitY, rabbitX - 1] == ' ' || theMap[rabbitY,rabbitX - 1 ] == '7' || theMap[rabbitY,rabbitX - 1 ] == '8')
                {
                    rabbitX -= 1;
                }
            }
            else if (pressedKey.Key == ConsoleKey.RightArrow || pressedKey.Key == ConsoleKey.D)
            {
                if (theMap[rabbitY, rabbitX + 1] == ' ' || theMap[rabbitY,rabbitX + 1 ] == '7' || theMap[rabbitY,rabbitX + 1 ] == '8')
                {
                    rabbitX += 1; 
                }
            }
            else if (pressedKey.Key == ConsoleKey.UpArrow || pressedKey.Key == ConsoleKey.W)
            {
                if (theMap[rabbitY - 1, rabbitX] == ' ' || theMap[rabbitY - 1,rabbitX ] == '7' || theMap[rabbitY - 1,rabbitX ] == '8')
                {
                    rabbitY -= 1;
                }
            }
            else if (pressedKey.Key == ConsoleKey.DownArrow || pressedKey.Key == ConsoleKey.S)
            {
                if (theMap[rabbitY + 1, rabbitX] == ' ' || theMap[rabbitY + 1, rabbitX] == '7' || theMap[rabbitY + 1, rabbitX] == '8')
                {
                    rabbitY += 1;

                }
            }
        }
    }
    static void DrawLabyrinth(int height, int width, char[,] array)
    {
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width; j++)
            {
                if (array[i, j] == '1')
                    Console.Write("─");
                else if (array[i, j] == '2')
                    Console.Write("│");
                else if (array[i, j] == '3')
                    Console.Write("┌");
                else if (array[i, j] == '4')
                    Console.Write("┐");
                else if (array[i, j] == '5')
                    Console.Write("└");
                else if (array[i, j] == '6')
                    Console.Write("┘");
                else if (array[i, j] == '7')
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("▼");
                    Console.ForegroundColor = ConsoleColor.White;
                }
                else if (array[i, j] == '8')
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.Write("\u00B8");
                    Console.ForegroundColor = ConsoleColor.White;
                }
                else if (array[i, j] == '9')
                {
                    Console.Write("┬");
                }
                else if (array[i, j] == '0')
                {
                    Console.Write("┴");
                }
                else if (array[i, j] == 'a')
                {
                    Console.Write('├');
                }
                else if (array[i, j] == 'b')
                {
                    Console.Write('┤');
                }
                else if (array[i, j] == 'c')
                {
                    Console.Write('┼');
                }
                else
                {
                    Console.Write(" ");
                }
            }
            Console.WriteLine();
        }
    }
}

【问题讨论】:

  • 在整个画布上绘画时通常不会清除背景...尝试删除.Clear调用,而只是定位光标。
  • 控制台输出很慢。您必须摆脱 Console.Clear() 并编写仅更新已更改字符位置的代码,并尽可能将它们分组。

标签: c# visual-studio-2013 console-application


【解决方案1】:

你的代码最大的问题是你不断刷新屏幕,即使不需要重绘任何东西,因为用户没有移动兔子。

如前所述,你要做的是最小的重绘量,即只有在有东西要重绘的时候才重绘,然后尽量做到尽可能少的量。对于您的示例游戏,在伪代码中应该如下所示:

// One time actions
var maze = ReadMaze(level);
DrawMaze(maze);
DrawRabbit(rabbitX, rabbitY);

// Game loop
while ((var input = GetInput()) != Input.Quit) {
    oldRabbitX = rabbitX, oldRabbitY = rabbitY;
    if (MoveRabbit(input, rabbitX, rabbitY, maze)) {
        EraseRabbit(oldX, oldY);
        DrawRabbit(rabbitX, rabbitY);
        if (IsPositionWithCarrot(rabbitX, rabbitY, maze))
            // This only the erases the carrot on screen.
            EatCarrot(rabbitX, rabbitY, maze);
    }
}

可以在here 找到一篇博客文章,其中包含有关构建 c# 控制台游戏的大量有用信息。

因为我发现这是一个有趣的问题,所以我对您的代码进行了一些重构,以匹配上面的伪代码。这消除了游戏中的所有闪烁。您可以在下面找到此尝试:

public class Game
{
    const string RabbitIcon = "\u0150";   //  \u0150   \u014E    \u00D2     \u00D3 --> alternatives
    static readonly char[] MazeChars = { '─', '│', '┌', '┐', '└', '┘', '▼', '\u00B8', '┬', '┴', '├', '┤', '┼' };
    static readonly ConsoleColor MazeFgColor = ConsoleColor.DarkGray;

    enum Input
    {
        MoveLeft,
        MoveRight,
        MoveUp,
        MoveDown,
        Quit
    };

    public static void Run()
    {
        Console.WriteLine("Select Level (Available levels: 1,2):");
        Console.WriteLine("\n(\\_/)\n(o.o)\n(___)0\n");
        int carrotCounter = 0;
        int gameLevel = int.Parse(Console.ReadLine()); // by pressing a number the user can select different labyrinths.

        // Console Height and Width
        Console.WindowHeight = 25;
        Console.BufferHeight = Console.WindowHeight + 1; // +1 to allow writing last character in the screen corner
        Console.BufferWidth = Console.WindowWidth = 80;
        Console.OutputEncoding = System.Text.Encoding.Unicode;  // Must have this + Change font to Lucida in CMD

        // Reads maze map
        string[] mapRows = File.ReadAllLines(String.Format("game.level{0}.txt", gameLevel));
        if (!mapRows.All(r => r.Length == mapRows[0].Length))
            throw new InvalidDataException("Invalid map");
        var charMap = mapRows.Select(r => r.ToCharArray()).ToArray();

        // Draw maze & rabbit once 
        Console.CursorVisible = false;
        DrawLabyrinth(charMap);
        int rabbitX = 1, rabbitY = 1;
        DrawRabbit(rabbitX, rabbitY, RabbitIcon);

        // Game Loop:
        Input input;
        while ((input = GetInput()) != Input.Quit)
        {
            if (MoveRabbit(input, ref rabbitX, ref rabbitY, charMap) &&
                IsPositionWithCarrot(rabbitX, rabbitY, charMap))
                EatCarrot(rabbitX, rabbitY, charMap, ref carrotCounter);
        }
    }

    static void EatCarrot(int rabbitX, int rabbitY, char[][] theMap, ref int carrotCounter)
    {
        // determine carrot top position.
        var carrotTopY = theMap[rabbitY][rabbitX] == '7' ? rabbitY - 1 : rabbitY;
        // "eat it" from the map.
        theMap[carrotTopY][rabbitX] = ' ';
        theMap[carrotTopY + 1][rabbitX] = ' ';
        // and erase it on screen;
        Console.SetCursorPosition(rabbitX, carrotTopY);
        Console.Write(' ');
        Console.SetCursorPosition(rabbitX, carrotTopY + 1);
        Console.Write(' ');
        // redraw the rabbit
        carrotCounter++;
        DrawRabbit(rabbitX, rabbitY, RabbitIcon);
    }

    static Input GetInput()
    {
        while (true)
        {
            var key = Console.ReadKey(true);
            switch (key.Key)
            {
                case ConsoleKey.LeftArrow:
                case ConsoleKey.A:
                    return Input.MoveLeft;
                case ConsoleKey.RightArrow:
                case ConsoleKey.D:
                    return Input.MoveRight;
                case ConsoleKey.UpArrow:
                case ConsoleKey.W:
                    return Input.MoveUp;
                case ConsoleKey.DownArrow: 
                case ConsoleKey.S:
                    return Input.MoveDown;
                case ConsoleKey.Q:
                    return Input.Quit;
                default:
                    break;
            }
        }
    }

    static bool IsValidRabbitPosition(int x, int y, char[][] theMap)
    {
        return x >= 0 && x < theMap[0].Length && y >= 0 && y < theMap.Length &&
               (theMap[y][x] == ' ' || IsPositionWithCarrot(x, y, theMap));
    }

    static bool IsPositionWithCarrot(int x, int y, char[][] theMap)
    {
        return theMap[y][x] == '7' || theMap[y][x] == '8';
    }

    static void DrawRabbit(int x, int y, string rabbitIcon)
    {
        Console.SetCursorPosition(x, y);
        Console.ForegroundColor = ConsoleColor.DarkYellow;
        Console.Write(rabbitIcon);
        Console.ResetColor();
    }

    static bool MoveRabbit(Input direction, ref int rabbitX, ref int rabbitY, char[][] theMap)
    {
        int newX = rabbitX, newY = rabbitY;
        switch (direction)
        {
            case Input.MoveLeft: newX--; break;
            case Input.MoveRight: newX++; break;
            case Input.MoveUp: newY--; break;
            case Input.MoveDown: newY++; break;
            default: return false;
        }
        if (IsValidRabbitPosition(newX, newY, theMap))
        {
            DrawRabbit(rabbitX, rabbitY, " "); // erase
            rabbitX = newX;
            rabbitY = newY;
            DrawRabbit(rabbitX, rabbitY, RabbitIcon); // draw
            return true;
        }
        return false;
    }

    static void DrawLabyrinth(char[][] theMap)
    {
        Console.Clear();
        for (int y = 0; y < theMap.Length; y++)
        {
            Console.SetCursorPosition(0, y);
            for (int x = 0; x < theMap[0].Length; x++)
            {
                var ndx = theMap[y][x] - '1';
                var c = ndx >= 0 && ndx < MazeChars.Length 
                    ? MazeChars[ndx] 
                    : ' ';

                Console.ForegroundColor = IsPositionWithCarrot(x, y, theMap)
                    ? ndx == 6 ? ConsoleColor.Red : ConsoleColor.Green
                    : MazeFgColor;

                Console.Write(c);
                Console.ResetColor();
            }
        }
        Console.WindowTop = 0; // scroll back up.
    }
}

【讨论】:

    【解决方案2】:

    这是控制台应用程序不是实时游戏(或实际上任何其他动画)的好选择的(许多)原​​因之一。正如您所展示的,您绝对可以做到,但不断清除和重绘 整个窗口 会闪烁。

    因此,真正的解决方案是选择一种对动画效果更好的技术,例如 Windows 窗体甚至更好的 WPF。两者都可以在屏幕上移动一个元素并且只重绘“脏”区域,这在减少闪烁方面是一个巨大的能力。

    如果您打算在控制台应用程序中执行此操作,我会通过移动控制台光标、擦除旧位置并在新位置重绘角色来进行自己的“脏”检查。它仍然不会像真正的图形库那样高效,如果它有很大的尺寸,你的角色可能仍然会闪烁,但它会更好ton

    【讨论】:

    • 是的,但是对于使用良好缓冲区管理和失效(例如 Console.MoveBufferArea)的简单控制台游戏,您可以使其性能足够好。主机游戏以那种怀旧的方式很有趣。
    • 请考虑将 WPF 建议为 第一个项目的更好的 GUI 技术是否是个好主意。 WPF 具有的品质几乎肯定不是初学者需要或欣赏的品质。
    • @Alex 当然,你可以做到,但它不是最好的游戏
    • @zneak 和 Casey,我意识到 WinForms 或 WPF 都不适用于游戏,但我不确定尝试进一步提升 XNA 或 Unity 之类的东西是否是个好主意。此外,我将继续认为,由于涉及的复杂性,general 中的游戏编程并不是一个好的第一个项目,因此它需要一个更复杂的框架这一事实确实已经给定了。跨度>
    • 这是一个大胆的声明。迷宫程序适合 200 行 C#。然后刽子手呢?扫雷呢?战舰,井字游戏呢?早在我们使用任何复杂的框架来制作它们之前,游戏就已经存在了。 OP 只想要一个位图平面,而控制台窗口是一个非常好的起点。
    猜你喜欢
    • 2022-06-23
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-14
    相关资源
    最近更新 更多