Reputation: 145
I wrote a 3D game with Python to run in a console. To stop it from flickering, I have to write what I want to display to a ConsoleScreenBuffer
. The documentation is this. I know I have to use:
import win32console
buffer = win32console.CreateConsoleScreenBuffer()
but what are the parameters for the CreateConsoleScreenBuffer()
? In the documentation it says:
HANDLE WINAPI CreateConsoleScreenBuffer(
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ const SECURITY_ATTRIBUTES *lpSecurityAttributes,
_In_ DWORD dwFlags,
_Reserved_ LPVOID lpScreenBufferData
);
That's in C. help(win32console.CreateConsoleScreenBuffer)
does not give useful information. The first 2 parameters are ints, the second a "PySECURITY_ATTRIBUTES"-object an the third is also an int. (I think?)
CreateConsoleScreenBuffer(DesiredAccess, ShareMode, SecurityAttributes, Flags)
DesiredAccess=GENERIC_READ and GENERIC_WRITE : int
GENERIC_READ and/or GENERIC_WRITE
ShareMode=FILE_SHARE_READ and FILE_SHARE_WRITE : int
FILE_SHARE_READ and/or FILE_SHARE_WRITE
SecurityAttributes=None : PySECURITY_ATTRIBUTES
Specifies security descriptor and inheritance for handle
Flags=CONSOLE_TEXTMODE_BUFFER : int
CONSOLE_TEXTMODE_BUFFER is currently only valid flag
I didn't find any examples of this being implemented online.
If you know any other methods of making a console draw faster, please tell.
This is my (not jet) game. It flickers, because its not drawn quick enough. The tutorial I was following is in c++ and uses CreateConsoleScreenBuffer to eliminate the flickering, because with it everything is drawn at once and not successively.
import os
import time
import math
import threading
hardMap = [
["#","#","#","#","#","#","#","#","#","#","#","#","#","#","#","#"],
["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".","#",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#",".",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#","#",".",".",".",".",".",".",".",".",".",".",".",".",".","#"],
["#","#","#",".",".",".",".",".",".",".",".",".",".",".","#","#"],
["#","#","#","#","#","#","#","#","#","#","#","#","#","#","#","#"],
]
gameMap = "".join(["".join(hardMap[n]) for n in range(len(hardMap))])
notDone = True
rotationSpeed = 0.02
playerX = 5
playerY = 5
playerA = 0
fov = 4
fov = math.pi / fov
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)
def printScreen(string):
os.system('cls')
time.sleep(0.01)
for x in [string[i:i+screenWidth] for i in range(0,len(string),screenWidth)]:
print("".join(x))
c = 0
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)
"""
with open("log.txt","a") as f:
f.write("x"+str(eyeX)+"\n")
f.write("y"+str(eyeY)+"\n")
f.write("h"+str(mapHeight)+"\n")
f.write("w"+str(mapWidth)+"\n")
f.write("\n")
"""
while not hitWall and distanceToWall < depth:
distanceToWall = 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((screenHeight / 2.0) - (screenHeight / distanceToWall))
floor = screenHeight - ceiling
for y in range(screenHeight):
if y < ceiling:
screen[y * ceiling + 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 / 1: shade = u"\u2591"
else: shade = " "
screen[y * screenWidth + x] = shade
else:
b = 1.0 - ((y - screenHeight / 2.0) / (screenHeight/2))
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)
"""
with open("log.txt","a") as f:
f.write(str("\n".join(screen)))
f.write("\n\n\n\n\n\n\n\n")
"""
time.sleep(max(1./fps - (time.time() - startTime), 0))
Upvotes: 1
Views: 2017
Reputation: 78
I am assuming you are following javid/OLC's Console FPS tutorial.
The simple answer is: you need win32con
along with win32console
, a better answer is below.
A little late but this page: win32console docs @ Tim Golden and PyConsoleScreenBuffer Object @ Tim Golden were very useful to figuring this out. There are two ways of doing this. One is using win32console and win32con
and the other is to use ANSI Escape Sequences. Effectively, all you are trying to do is move the cursor to/print at 0,0 on the console. Here is method one:
import win32console, win32con, time
myConsole = win32console.CreateConsoleScreenBuffer(DesiredAccess = win32con.GENERIC_READ | win32con.GENERIC_WRITE, ShareMode=0, SecurityAttributes=None, Flags=1) # create screen buffer
myConsole.SetConsoleActiveScreenBuffer() # set this buffer to be active
myConsole.WriteConsole("Hello World!") # Effectively the print func.
myConsole.WriteConsoleOutputCharacter(Characters="Hello World!\0", WriteCoord=win32console.PyCOORDType(5,5)) # Print at coordinates
time.sleep(3) # this must be called or you won't see results. This is because after running the code (because there is no loop) the cmd.exe takes the console over.
This should work fine in your console FPS (using WriteConsoleOutputCharacter). Just place the commands where javid places them and this should work fine. The second method is to use colorama/ANSI Sequences to return the cursor to the home position. This method can be summed up in: os.system("echo \033[0;0H")
. You cannot use the escape sequences using the print
function, they are not supported (except using colorama). Here is an example:
from colorama import init
from os import system
init() # I bother with this because I use colors too. Echo prints slower than print, so don't use echo if you can avoid it.
# ... game
# ... generate string to print
print(frame) # or just use you method of printing each line.
system("echo \033[0;0H") # return cursor to home position, ready for next frame.
I hope this helps! - Sky
Upvotes: 3