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() 
\$\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.

\$\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
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.