5
\$\begingroup\$

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() 
Graipher
41.6k7 gold badges70 silver badges134 bronze badges
asked Apr 4, 2017 at 14:09
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

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 (so f(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.

answered Apr 4, 2017 at 14:46
\$\endgroup\$
2
  • \$\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\$ Commented 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 using camelCase is using PascalCase for variables, because that will break many syntax highlighters for Python (including the one here on SE) and mark these variables as classes. \$\endgroup\$ Commented Apr 4, 2017 at 15:00
1
\$\begingroup\$

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
answered Apr 4, 2017 at 18:50
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.