This is a simple custom font rendering class I wrote. The idea was to code something that could be passed a lengthy string and render that properly in any game that uses pygame.
On top of this, the class can draw each character one at time for a kind of typewriter effect.
Anyway, I'm just looking for feedback or any tip on improving the code. Anything you might notice really.
Thanks for your time.
Here is the custom renderer class:
import pygame
import time
class customRenderer():
def __init__(self,p_screen,p_screenX, p_screenY, p_font,p_fontSize, p_fontColor, p_text, p_startPosX, p_startPosY, p_waitTime):
self.initialStartX = p_startPosX
self.initialStartY = p_startPosY
self.currentScreen = p_screen
self.maxWidth = p_screenX
self.maxHeight = p_screenY
self.font = p_font
self.fontSize = p_fontSize
self.fontColor = p_fontColor
self.text = p_text
self.startPosX = p_startPosX
self.startPosY = p_startPosY
self.waitTime = p_waitTime
#innate properties
self.splitTxt = None
self.done = False
''' splits the string to be used for the render '''
def setSplitText(self):
self.splitTxt = self.text.split(" ")
''' Fully render and blit the string '''
def renderTxt(self):
''' set starting position coords for render blits '''
self.startPosX = self.initialStartX
self.startPosY = self.initialStartY
for word in self.splitTxt:
''' if current word not fully blitted '''
if not self.done:
'''create render and set offsets for x and y pos'''
ren = self.font.render(word, 1, self.fontColor)
offsetX = (ren.get_width() + 8)
offsetY = (ren.get_height() + self.fontSize/2)
''' If wordpos x is greater than render surface width
offset y position, reset x position for word '''
if (self.startPosX + ren.get_width()) >= self.maxWidth - self.initialStartX:
self.startPosY += offsetY
self.startPosX = self.initialStartX
''' blit string render and increment x offset position '''
self.currentScreen.blit(ren,(self.startPosX,self.startPosY))
self.startPosX += offsetX
''' if last word of string is blitted, render blit is done '''
if self.splitTxt.index(word) == len(self.splitTxt) -1:
self.done = True
def typewritter(self):
'''vars'''
interrupted = False
idx = 0
spacer = 0
for word in self.splitTxt:
idx = 0
wordRen = self.font.render(word, 1, self.fontColor)
wordStartX = self.startPosX
for letter in word:
''' if current word not fully blitted '''
if not self.done:
''' if user hasn't pressed start, continue blitting letters '''
if not interrupted:
'''create render'''
ren = self.font.render(letter, 1, self.fontColor)
'''set spacer'''
# if letter is the same as the,
# last letter of a word
if letter == word[len(word)-1]:
#if truly last letter
if idx == len(word)-1:
spacer = 8
else:
spacer = 0
else:
spacer = 0
''' If letter pos x is greater than render surface width
offset y position, reset x positions for word and letter'''
if (wordStartX + wordRen.get_width()) >= self.maxWidth - self.initialStartX:
self.startPosY += offsetY
self.startPosX = self.initialStartX
wordStartX = self.startPosX
''' set blit offsets for x and y positions'''
offsetX = (ren.get_width() + spacer)
offsetY = (ren.get_height() + (self.fontSize/2))
''' blit string render and increment x offset position '''
self.currentScreen.blit(ren,(self.startPosX,self.startPosY))
self.startPosX += offsetX
''' if last char of string is blitted, word is done '''
if self.splitTxt.index(word) == len(self.splitTxt) -1:
if word.index(letter) == len(word) -1:
self.done = True
''' increment, wait and update '''
idx +=1
pygame.time.wait(self.waitTime)
pygame.display.update()
''' class event loop '''
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
interrupted = True
screen.fill((0,0,0))
''' if user pressed start, blit entire string with renderTxt()'''
else:
self.renderTxt()
And here's the client:
#================================= Setup =======================================
#imports
import pygame
import time
from renderWithLineBreak import customRenderer
# Init
pygame.init()
# Game Window size
screen_size = [1600, 900]
screen = pygame.display.set_mode(screen_size)
#conversation array
convoOne = []
# Game Captions
pygame.display.set_caption('My caption here')
#Font
popUpFonts = pygame.font.Font("fonts/Tahoma.ttf", 32)
#test string
string = "When Mr. Bilbo Baggins of Bag End announced that he would shortly be celebrating his eleventy-first birthday with a party of special magnificence, there was much talk and excitement in Hobbiton"
stringTwo = "Bilbo was very rich and very peculiar, and had been the wonder of the Shire for sixty years, ever since his remarkable disappearance and unexpected return. The riches he had brought back from his travels had now become a local legend, and it was popularly believed, whatever the old folk might say, that the Hill at Bag End was full of tunnels stuffed with treasure. "
convoOne.append(string)
convoOne.append(stringTwo)
#Ints
convoIdx = 0
clock = pygame.time.Clock()
#=========================== Program Loop ======================================
done = False
typingDone = False
while done == False:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
if event.key == pygame.K_RETURN:
if not typingDone:
screen.fill((0,0,0))
testWord = customRenderer(screen,screen_size[0],screen_size[1],popUpFonts,32,(255,255,255),convoOne[convoIdx],100,100,75)
testWord.setSplitText()
testWord.typewritter()
if convoIdx < len(convoOne)-1:
convoIdx+=1
else:
typingDone = True
else:
done = True
pygame.display.update()
clock.tick(60)
#========================== End Loop =============================================
pygame.quit()
2 Answers 2
This is mostly a review of the style.
Python has an official style-guide, PEP8.
It recommends using lower_case
for variable and function names and PascalCase
for class names.
It also recommends using whitespace in the following way:
After function and class definitions, two blank lines.
Between class methods, one blank line.
To separate code blocks within a function/method, one blank line.
operators should be surrounded by whitespace (so
idx += 1
), except when used as a keyword in a function-call (sof(a=1, b=2)
). Commas are also followed by a space.
Python has comments, they start with a #
. PEP8 recommends using two spaces after code (before the #) and one space after the #.
Examples:
# Normal, full line comment
some_code.action() # Does some cool stuff
Python also has docstrings, which serve to document code. The whole module, classes and functions/methods can take docstrings. They are described in PEP257.
They should look like this:
class A(object):
"""A normal class"""
def __init__(self, *args):
"""
The constructor
Does some fancy stuff with `args`
"""
self.args = args
As you can see, you can use multi-line strings. The indentation of the first line will be removed from the string.
You can access these strings e.g. by looking at the dunder __doc__
attribute (so A.__doc__ == "A normal class"
) or by using help(A.__init__)
in an interactive shell. Also, a lot of documentation tools will pick up on this.
So, all your strings within the methods should be comments and start with #
.
All your strings directly above methods should be docstrings and be inside the methods (as the first line).
In addition to these comments, you should also use the if __name__ == "__main__":
guard, which allows importing your code from other scripts.
-
\$\begingroup\$ Thanks for elaborating on the proper pythonic standards. I'm following a Programming/Analyst course in college right now and they drill us on camel casing like you wouldn't believe. To hear them tell it, you use it everywhere. That said, most of em have .NET framework and Java backgrounds. \$\endgroup\$Wretch11– Wretch112017年04月04日 14:56:05 +00:00Commented Apr 4, 2017 at 14:56
-
\$\begingroup\$ @Wretch11 Yeah, in programming in general
camelCase
is probably the most language agnostic style. And you are still allowed to use it in Python. It is just not recommended and most Python programmers will expect code to (mostly) adhere to PEP8. Worse than usingcamelCase
is usingPascalCase
for variables, because that will break many syntax highlighters for Python (including the one here on SE) and mark these variables as classes. \$\endgroup\$Graipher– Graipher2017年04月04日 15:00:03 +00:00Commented Apr 4, 2017 at 15:00
Looking over this myself, I'm not sure i need to have a function just to split. I guess i should just do: self.splitTxt = self.text.split(" ")
in the constructor.
Also, not sure updating the whole screen is the best thing here. Using pygame fonts gives a pretty heavy hit to performance already, so maybe I should assign the blit to a rect and just update that rect instead.
Lastly, in the typewriter function where I set the spacer, i'm pretty sure there should be better way of checking the last char of a word, all while eliminating the possibility of a duplicate (i.e A string like "0000"). Right now i'm incrementing a separate index through the loop, just for this condition:
'''set spacer'''
# if letter is the same as the,
# last letter of a word
if letter == word[len(word)-1]:
#if truly last letter
if idx == len(word)-1:
spacer = 8
else:
spacer = 0
else:
spacer = 0