Python中CreateConsoleScreenBuffer参数及控制台3D游戏防闪烁咨询
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_WRITEto grant read/write permissions to the buffer. - ShareMode:
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITEallows other processes to access the buffer (standard for console scenarios). - SecurityAttributes: Pass
Noneto use default security settings. - Flags: Only
win32con.CONSOLE_TEXTMODE_BUFFERis 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 / 2outside the main loop - Avoid redundant calculations inside the ray-casting while loop (e.g., pre-calculate
mapWidthandmapHeightonce) - 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

