【问题标题】:C# Something faster than Console.Write()?C# 比 Console.Write() 更快的东西?
【发布时间】:2015-07-07 08:11:33
【问题描述】:

我正在制作一个游戏,但使用 Console.Write() 重绘游戏场不是很好。有没有什么方法可以更快地重写整个场而不让它看起来“滞后”?运动场中的几乎所有东西都在移动,但只有在 0 以外的元素处才有对象。(您可以在此处查看完整代码 http://pastebin.com/TkPd37xD,如果我的描述不够,请查看我在说什么)

for (int Y = 0; Y < playfield.GetLength(0); Y++)
{
    for (int X = 0; X < playfield.GetLength(1); X++)
    {
        //destroying the row when it reaches the top
        if (playfield[0, X] != 0)
        {
            for (int i = 0; i < playfield.GetLength(1); i++)
            {
                playfield[0, X] = 0;
                Console.SetCursorPosition(X, 0);
                Console.Write(" ");
            }
        }
        if (playfield[Y, X] == 3)
        {
            playfield[Y - 1, X] = 3;
            playfield[Y, X] = 0;
        }
        else if (playfield[Y, X] == 1)
        {
            Console.SetCursorPosition(X, Y - 1);
            Console.Write("=");
            playfield[Y - 1, X] = 1;
            Console.SetCursorPosition(X, Y);
            Console.Write(" ");
            playfield[Y, X] = 0;
        }
        else if (playfield[Y, X] == 0)
        {
            Console.SetCursorPosition(X, Y);
            Console.Write(" ");
        }
    }
}

【问题讨论】:

  • 只是不要重绘整个屏幕,只重绘改变的部分
  • 我以为这就是我正在做的事情,但显然我不是,刚才我注意到我的最后一个“Else if”是不必要的,因为我在 else if 做同样的事情当 == 1 时,我删除了最后一个 Else if,现在看起来好多了! :)
  • 您尝试过使用字符串缓冲区吗?
  • 最初的 windows 控制台可能不是最快的。

标签: c#


【解决方案1】:

另一种方法是使用链接https://msdn.microsoft.com/en-gb/library/windows/desktop/aa363362%28v=vs.85%29.aspx中指定的API调用

[DllImport("kernel32.dll")]
static extern void OutputDebugString(string lpOutputString);

您还可以通过将 for 循环中的输出缓冲到 StringBuilder 中并在每个 for 循环之后输出它来获得轻微的性能。这可能不是必需的,具体取决于您希望通过程序实现的目标。

【讨论】:

  • 没必要那样做,有一个托管的Debug.WriteLine 在托管的世界里做同样的事情。
【解决方案2】:

如果你隐藏输出 cmd 窗口,windows 不会重绘它,并且你的应用程序运行速度比它在屏幕上打开时更快。

如果这只是一个调试问题,您可以简单地最小化窗口并看到性能的小幅提升。

【讨论】:

  • 从 OP 对术语“playfield”的使用我怀疑这是一个游戏而不是调试输出。
【解决方案3】:

基本上有两种方法:渲染更少,渲染更快。

少渲染通常更棘手,但也往往不那么密集。典型的例子是 Carmack 的 Keen 游戏——PC 没有勇气一次重新渲染整个屏幕,因此 Carmack 确保只重绘屏幕中实际发生变化的部分。在您的情况下,这可以像检查新屏幕和旧屏幕一样简单(当然,不使用Console 方法) - 根据您正在编写的游戏类型,这可以为您节省大量工作。

更快地渲染通常更容易。过去通常的方法是直接访问输出缓冲区——而不是将游戏区放在单独的内存中,而是直接在显卡中——这非常有能力根据需要以最快的速度重绘整个屏幕,当然,否则您将不会在 CRT 屏幕上看到太多内容。这个选项仍然可以作为向后兼容性访问,所以如果你用 Turbo Pascal 编写应用程序,你仍然可以使用它,但它在 C# 中并不是那么容易访问。可以选择先在StringBuilder 中渲染整个屏幕,然后在Console.Write 中一次渲染。它会快很多,但并不完全是恒星。 char[] 将让您获得额外的性能 - 您可以直接将您的运动场表示为 char[][],然后您不必在每次更改某些内容时重新创建 StringBuilder - 您只需 Console.Write 一次即可每个运动场线。

当然,您可以简单地在更改发生后立即写出;根据您正在编写的游戏,这可以从“微不足道但效果很好”到“相当难而且看起来不太好”。由于控制台的缓冲区可以大于窗口大小,您甚至可以将其绘制到缓冲区的隐藏部分,然后使用Console.MoveBufferArea 一次绘制整个更改 - 这通常称为“后缓冲”。不过,我不确定它是否看起来不错 - 现在的控制台窗口允许您在缓冲区中滚动,这对您的用例可能是有害的。

仍有一些方法可以更快地访问控制台缓冲区,但不能完全停留在 .NET 中 - 您需要使用 P/Invokes。关于这个主题的一个很好的答案在这里 - How can I write fast colored output to Console?。在现代系统上,这几乎等同于使用后台缓冲区并一次“绘制”它 - 它非常快。同样,您可以直接将后台缓冲区用于您的游戏数据——它在 20 到 30 年前有效,今天仍然有效;在有限的资源中玩耍是一种很好的做法。你能写出一个只真正使用控制台文本缓冲区的游戏吗?或者至少几乎所有的东西?玩这样的东西很有趣;您可以编写大量这样的游戏,包括俄罗斯方块或 Lode Runner 等游戏。当然,这只适用于Windows,所以如果你想支持其他系统,那就麻烦了。

最后,您可以编写自己的控制台(或者更好的是,使用某人已经编写和测试过的控制台)。如果您想随着时间的推移继续挑战更大的挑战,这是一个很好的做法,并且随着时间的推移,它将允许您使用更强大的技术。典型的例子是像矮人要塞这样的游戏——仍然基于文本,仍然类似于控制台,但实际上是使用 SDL 等技术以图形方式绘制的。这不仅在现代系统上要快得多(因为您没有直接访问文本缓冲区的简单方法),而且还提供了相当容易切换到图形平铺游戏的选项。这是冷却东西的阶梯上的另一个垫脚石:))

【讨论】:

    【解决方案4】:

    This incomplete answerHow can I write fast colored output to Console?(不实现颜色)对于全窗口更新非常快,对于 System.Console 窗口的标准 120x30 大小只需要大约 0.8 毫秒

    int cols = Console.WindowWidth, rows = Console.WindowHeight;
    //some sample text
    byte[] buffer = Enumerable.Repeat((byte)'=', cols * rows).ToArray();
    //because output appends, ensure the window is reset
    Console.SetCursorPosition(0, 0);
    using (Stream stdout = Console.OpenStandardOutput(cols * rows)) {
        stdout.Write(buffer, 0, buffer.Length);
    }
    

    这是我调用Write 1000 次后的表现:


    如果您愿意进行 p-invoke,则可以使用 this other answer 对同一问题的演示来实现颜色,使用 CharInfoWriteConsoleOutput

    这两种方法都适用于“后缓冲”(即构建整个场景然后显示它)。


    更新:这是我想出的处理颜色的方法:

    [StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
    public struct CharUnion {
        [FieldOffset(0)] public char UnicodeChar;
        [FieldOffset(0)] public byte AsciiChar;
    }
    
    [StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
    public struct CharInfo{
        [FieldOffset(0)] public CharUnion Char;
        [FieldOffset(2)] public ushort Attributes;
    
    public ConsoleColor ForegroundColor => (ConsoleColor)((this.Attributes & 0x0F));
    public ConsoleColor BackgroundColor => (ConsoleColor)((this.Attributes & 0xF0) >> 4)
    
        public CharInfo(char character, ConsoleColor? foreground = null, ConsoleColor? background = null) {
            this.Char = new CharUnion() { UnicodeChar = character };
            this.Attributes = (ushort)((int)(foreground ?? 0) | (((ushort)(background ?? 0)) << 4));
        }
        public CharInfo(byte character, ConsoleColor? foreground = null, ConsoleColor? background = null) {
            this.Char = new CharUnion() { AsciiChar = character };
            this.Attributes = (ushort) ((int)(foreground ?? 0) | (((ushort)(background ?? 0)) << 4));
        }
    
        public static bool Equals(CharInfo first, CharInfo second) {
            return first.Char.UnicodeChar == second.Char.UnicodeChar
                && first.Char.AsciiChar == second.Char.AsciiChar
                && first.Attributes == second.Attributes;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-05
      • 2012-04-15
      • 1970-01-01
      • 2012-06-16
      • 2010-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多