You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中CreateConsoleScreenBuffer参数及控制台3D游戏防闪烁咨询

解决Python控制台3D游戏的闪烁问题:Win32缓冲区及优化方案

Hey there! Let's fix that annoying flicker in your console 3D game. The win32console.CreateConsoleScreenBuffer approach you're exploring is perfect for implementing double-buffering on Windows, and I'll break down the exact parameter usage and how to integrate it into your code. I'll also cover other cross-platform or optimization methods to boost rendering speed.

一、正确使用win32console.CreateConsoleScreenBuffer

Your parameter guess was spot-on! Here's the detailed breakdown of each argument, along with working code to replace your current printScreen function with double-buffering:

First, you'll need to import the necessary Windows API constants from win32con (install via pip install pywin32 if you haven't already):

import win32console
import win32con

参数详解

  • DesiredAccess: Use win32con.GENERIC_READ | win32con.GENERIC_WRITE to grant read/write permissions to the buffer.
  • ShareMode: win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE allows other processes to access the buffer (standard for console scenarios).
  • SecurityAttributes: Pass None to use default security settings.
  • Flags: Only win32con.CONSOLE_TEXTMODE_BUFFER is valid for text-based console buffers.
  • lpScreenBufferData: Reserved parameter, just pass None.

整合到你的游戏代码

Replace your existing printScreen function and add buffer initialization at the top of your code:

# 初始化双缓冲区
buffer = win32console.CreateConsoleScreenBuffer(
    win32con.GENERIC_READ | win32con.GENERIC_WRITE,
    win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
    None,
    win32con.CONSOLE_TEXTMODE_BUFFER,
    None
)

def printScreen(screen):
    # 将屏幕数组转为字符串
    screen_str = "".join(screen)
    # 一次性写入后台缓冲区
    buffer.WriteConsoleOutputCharacter(
        screen_str,
        (0, 0),  # 起始坐标(x, y)
        (screenWidth, screenHeight)  # 写入区域的宽高
    )
    # 切换前台显示为后台缓冲区(无闪烁更新)
    buffer.SetConsoleActiveScreenBuffer()

This works because we first draw everything to an off-screen buffer, then switch the console's active display to this buffer in one step—no more cls and line-by-line printing which causes flicker.

二、其他控制台绘制优化方法

If you want cross-platform support or alternative approaches, here are two great options:

1. 使用Python的curses库(跨平台)

The curses library (install windows-curses on Windows via pip) is designed for efficient console UIs and handles double-buffering under the hood. Here's how to adapt your code:

import curses

# 初始化curses
stdscr = curses.initscr()
curses.curs_set(0)  # 隐藏光标
stdscr.resize(screenHeight, screenWidth)  # 设置窗口大小

def printScreen(screen):
    for y in range(screenHeight):
        # 提取每一行的内容
        line = "".join(screen[y*screenWidth : (y+1)*screenWidth])
        stdscr.addstr(y, 0, line)
    stdscr.refresh()  # 一次性刷新显示

# 游戏循环结束后务必恢复终端设置
# curses.endwin()

2. 优化你的射线追踪逻辑

Your current code does a lot of per-pixel calculations every frame. Small optimizations can boost speed and reduce flicker indirectly:

  • Precompute constants like screenHeight / 2 outside the main loop
  • Avoid redundant calculations inside the ray-casting while loop (e.g., pre-calculate mapWidth and mapHeight once)
  • Use integer operations where possible instead of floating-point (though ray tracing relies heavily on floats, every little bit helps)

修改后的完整游戏代码示例

Here's your game code updated with the Win32 double-buffering approach:

import os
import time
import math
import win32console
import win32con

hardMap = [
    ["#","#","#","#","#","#","#","#","#","#","#","#","#","#","#","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#","#",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
    ["#","#","#",".",".",".",".",".",".",".",".",".",".",".","#","#"],
    ["#","#","#","#","#","#","#","#","#","#","#","#","#","#","#","#"],
]
gameMap = "".join(["".join(hardMap[n]) for n in range(len(hardMap))])
notDone = True
rotationSpeed = 0.02
playerX = 5
playerY = 5
playerA = 0
fov = math.pi / 4  # 直接计算成弧度,避免重复计算
depth = 20
fps = 30
screenWidth = 120
screenHeight = 40
os.system(f'mode con: cols={screenWidth} lines={screenHeight}')
screen = [" " for n in range(screenHeight * screenWidth)]
mapWidth = len(hardMap[0])
mapHeight = len(hardMap)

# 初始化双缓冲区
buffer = win32console.CreateConsoleScreenBuffer(
    win32con.GENERIC_READ | win32con.GENERIC_WRITE,
    win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
    None,
    win32con.CONSOLE_TEXTMODE_BUFFER,
    None
)

def printScreen(screen):
    screen_str = "".join(screen)
    buffer.WriteConsoleOutputCharacter(screen_str, (0, 0), (screenWidth, screenHeight))
    buffer.SetConsoleActiveScreenBuffer()

c = 0
half_screen_height = screenHeight / 2  # 预计算常量
while(notDone):
    c += 1
    playerA = playerA + rotationSpeed
    startTime = time.time()
    for x in range(screenWidth):
        rayAngle = (playerA - fov / 2) + ((x / screenWidth) * fov)
        distanceToWall = 0
        hitWall = False
        shade = " "
        eyeX = math.sin(rayAngle)
        eyeY = math.cos(rayAngle)

        while not hitWall and distanceToWall < depth:
            distanceToWall += 0.1
            testX = int(playerX + eyeX * distanceToWall)
            testY = int(playerY + eyeY * distanceToWall)
            if testX < 0 or testX >= mapWidth or testY < 0 or testY >= mapHeight:
                hitWall = True
                distanceToWall = depth
            elif gameMap[testY * mapWidth + testX] == "#":
                hitWall = True
                ceiling = int(half_screen_height - (screenHeight / distanceToWall))
                floor = screenHeight - ceiling
                for y in range(screenHeight):
                    if y < ceiling:
                        screen[y * screenWidth + x] = " "
                    elif y > ceiling and y <= floor:
                        if distanceToWall <= depth / 4:
                            shade = u"\u2588"
                        elif distanceToWall < depth / 3:
                            shade = u"\u2593"
                        elif distanceToWall < depth / 2:
                            shade = u"\u2592"
                        elif distanceToWall < depth:
                            shade = u"\u2591"
                        else:
                            shade = " "
                        screen[y * screenWidth + x] = shade
                    else:
                        b = 1.0 - ((y - half_screen_height) / half_screen_height)
                        if b < 0.25:
                            shade = "#"
                        elif b < 0.5:
                            shade = "X"
                        elif b < 0.75:
                            shade = "."
                        elif b < 0.9:
                            shade = "-"
                        else:
                            shade = " "
                        screen[y * screenWidth + x] = shade
    printScreen(screen)
    time.sleep(max(1./fps - (time.time() - startTime), 0))

内容的提问来源于stack exchange,提问作者DinosaurMoritz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 13:12:28