I'm writing a simple roguelike in Python 3.10 as a way to learn the language as well as principles of proper software architecture. I'm using wxPython310 (4.1.2a2).
The map is an octagonal grid of cells that player may traverse. My map is a wx.StaticText
that updates once every user input. I set it to DoubleBuffered
, as per advice I found here. It works really nice.
Recently I started implementing field of vision mechanics that makes tiles (grid cells) outside of player's nearby surroundings return 'fog of war' character and only display their actual character representation once a player approaches (the player is denoted as '@' character):
I noticed that if I keep holding a move arrow, the wx.StaticText
doesn't update until I release it. Disabling DoubleBuffered
obviously corrects that behaviour, but makes the screen very blinky (as in, it flickers very rapidly). What's more interesting, I noticed that the wx.StaticText
updates correctly and quickly if a 'fog of war' character is some actual character, rather than just whitespace (in the following example, an underscore):
It's as if wxStaticText
updated slower when there's plenty of whitespace, but quicker if there's a proper character.
My reproducible example code:
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super().__init__(None, *args, **kwargs)
self.Title = 'Stackoverflow example'
self.SetSize(width=1480, height=600)
self.panel = MainPanel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel,1, wx.EXPAND)
self.SetSizer(sizer)
self.Center()
self.Layout()
self.Show()
class MainPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.mymap = MyMap()
self.x = -1
self.moveRight = True
self.SetDoubleBuffered(True)
font = wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.DEFAULT)
font.SetFaceName('Courier New')
self.ST = wx.StaticText(self, label=self.Display(), style=wx.ALIGN_LEFT)
self.ST.SetFont(font)
self.Bind(wx.EVT_KEY_DOWN, self.EvaluateCommand)
def EvaluateCommand(self,e):
key = e.GetKeyCode()
if key == 78: # n for newbie!
self.MoveRectangleOnAMap()
self.ST.SetLabel(self.Display())
elif key == wx.WXK_ESCAPE:
quit()
def MoveRectangleOnAMap(self):
all_tiles = self.mymap.FindRectangleOfTiles((0, 0), (self.mymap.width, self.mymap.height))
for tile in all_tiles:
tile.SetDisplayToDormant()
if self.moveRight:
self.x += 1
if self.x == self.mymap.width - 15:
self.moveRight = False
else:
self.x -= 1
if self.x == 0:
self.moveRight = True
rectangle = self.mymap.FindRectangleOfTiles((self.x, 10), (self.x + 15, 25))
for tile in rectangle:
tile.SetDisplayToActive()
def Display(self):
width = self.mymap.width
height = self.mymap.height
text = ''
for y in range(0,height):
line = ''
for x in range(0,width):
line += self.mymap.FindTileFromCoords((x,y)).display
text += line + '\n'
return text
class Tile():
def __init__(self,coords):
self.coords = coords
self.SetDisplayToDormant()
def SetDisplayToActive(self):
self.display = ' '
def SetDisplayToDormant(self):
self.display = '#'
class MyMap():
def __init__(self):
self.tiles = dict()
self.width = 200
self.height = 35
for x in range(0,self.width):
self.tiles[str(x)] = dict()
for y in range(0,self.height):
self.tiles[str(x)][str(y)]= Tile((x,y))
def FindTileFromCoords(self,coords):
coord_x, coord_y = coords
return self.tiles[str(coord_x)][str(coord_y)]
def FindRectangleOfTiles(self,coords_up_left, coords_down_right):
coords_up_left_x, coords_up_left_y = coords_up_left
coords_down_right_x, coords_down_right_y = coords_down_right
tiles = []
for x in range(coords_up_left_x,coords_down_right_x):
for y in range(coords_up_left_y,coords_down_right_y):
tiles.append(self.FindTileFromCoords((x,y)))
return tiles
app = wx.App()
frame = MyFrame()
app.MainLoop()
If I execute this code and press and hold 'n' on keyboard (n for newbie!), the rectangle moves in nice and steady fashion. If you switch around the characters in Tile
class...:
class Tile():
def __init__(self,coords):
self.coords = coords
self.SetDisplayToDormant()
def SetDisplayToActive(self):
self.display = '#'
def SetDisplayToDormant(self):
self.display = ' '
... and hold 'n' again, the StaticText
label won't update until you release the key.
Could someone explain to me why that is? And is there some smart way to circumvent that problem?
1. Switch whitespace to no-break whitespace.
As VZ. mentioned, wxStaticText
aren't really meant to be used to update big blobs of text. However, he also mentioned that the word wrapping in wxStaticText
is a probable culprit for this stuttery behaviour. This got me thinking, if that's word wrapping that's causing it, how may I circumvent it? Well, it turns out that switching a standard whitespace character (U+0020
) to a no-break space (U+00A0
) indeed makes the widget updates much more smooth! In my reproducible code example, it's especially trivial, since Python 3+ makes all strings utf-8 encoded:
class Tile():
def __init__(self,coords):
self.coords = coords
self.SetDisplayToDormant()
def SetDisplayToActive(self):
self.display = '#'
def SetDisplayToDormant(self):
self.display = '\u00a0'
2. Use GenStaticText
instead.
It turns out using wx.lib.stattext.GenStaticText
instead of wxStaticText
also seems to correct the issue and is probably also way more sensible.