Channels ▼


The PolyArea Project: Part 2

The PyGame Objects

The BaseObject class (defined in serves as a blueprint to objects that the MainLoop class knows how to work with. For each type of object you want to display on the screen you should create a sub-class implement the __init__(), update(), and render() methods and in your program instantiate and add these objects to the MainLoop's self.objects list. MainLoop will take care of the rest and call update() and render() in each iteration. The render() method gets a PyGame screen object and you should use PyGame functions to render the visual representation of your object on the screen.

class BaseObject(object):
  """A base class for visual objects managed by the main loop
  Each object has an __init__(), update() and render() methods.
  To use it sub-class BaseObject implement the methods and add
  your object to the main loop.
  def __init__(self):
    """ """

  def update(self):
    """ """
  def render(self, screen):
    """ """

Here are the object types that PolyArea uses. They are defined in the file.

The Line class is a simple visual object; see Figure 1.

[Click image to view at full size]
Figure 1

It subclasses BaseObject of course and it implements only the __init__() and render() methods. In __init__() it stores the color the width and the two end points of the line (startpos and endpos). In render() it simply calls the pygame.draw.line() method with the screen object and the aforementioned arguments.

class Line(BaseObject):
  def __init__(self, color, startpos, endpos, width=1):
    self.startpos = startpos
    self.endpos = endpos
    self.color = color
    self.width = width
  def render(self, screen):
    pygame.draw.line(screen, self.color, self.startpos, self.endpos, self.width)

The Triangle class is almost as simple as the Line. It draws a filled triangle with a border (Figure 2).

[Click image to view at full size]
Figure 2

The __init__() method stores the triangle vertices (points) the border and background color and the width of the border. The render() method draws a filled polygon with the background color and then another non-filled polygon with the border color and width. I don't really enforce that there are three points, so you can use this class to draw any polygon, but in PolyArea I draw only triangles using this object.

class Triangle(BaseObject):
  def __init__(self,
               bg_color=triangle_bg_color, width=1):
    self.points = points
    self.color = color
    self.bg_color = bg_color
    self.width = width
  def render(self, screen):
    # Draw the filled interior first
    pygame.draw.polygon(screen, self.bg_color, self.points, 0)
    # Draw the border
    pygame.draw.polygon(screen, self.color, self.points, self.width)

People often distinguish text from graphics, but they are not that different. In the end, both are manifested as pixels on the screen. In PyGame, writing text on the screen is a two-stage process where you create a PyGame Text object using your favorite font, then "blit" (short "block image transfer") it to the screen at a particular position (Figure 3):

[Click image to view at full size]
Figure 3

class Text(BaseObject):
  def __init__(self, text, font=None, size=36, color=(0, 0, 0), pos=(0, 0)):
    self.text= text
    self.font = pygame.font.Font(font, size)
    self.color = color
    self.pos = pos
  def render(self, screen):  
    text = self.font.render(self.text, 1, self.color)
    screen.blit(text, self.pos)

The Grid is pretty sophisticated compared to the other objects. It represents an interlocking grid of dashed lines with a border of three pixels. (Why three? Because I said so); see Figure 4. The rect parameter determines the total size of the grid. delta_x and delta_y specify the distance between vertical and horizontal grid lines and the color parameters control the color of background, border, and the dashed lines.

[Click image to view at full size]
Figure 4

class Grid(BaseObject):
  def __init__(self, rect, delta_x, delta_y,
               background_color, border_color, line_color):
    self.rect = rect
    self.delta_x = delta_x
    self.delta_y = delta_y
    self.background_color = background_color
    self.border_color = border_color
    self.line_color = line_color

The render() method does a lot. It's not a couple of calls to pygame.draw.*() functions anymore. The grid is made of dashed lines, which PyGame doesn't support out of the box. So, the render() method defines a nested helper function called dashed_line() that draws a dashed line made of 5-on/5-off pixel segments. Each "on" segment is drawn using pygame.draw.line(). To simplify things, dashed_line() can draw either horizontal or vertical dashed lines (that's all I need for the grid):

  def render(self, screen):
    """Draws a grid on the screen"""
    def dashed_line(screen, line_color, start_pos, end_pos):
      """Draw a dashed line (must be horizontal or vertical)"""
      horiz_line_count = (end_pos[0] - start_pos[0])/10
      vert_line_count = (end_pos[1] - start_pos[1])/10
      if vert_line_count == 0:
        #draw horizontal lines
        for i in range(horiz_line_count):
          y = start_pos[1]
          x1 = i * 10 + start_pos[0]
          x2 = x1 + 5
          pygame.draw.line(screen, line_color, (x1, y), (x2, y))
      elif horiz_line_count == 0:
       #draw vertical lines
       for i in range(vert_line_count):
          x = start_pos[0]
          y1 = i * 10 + start_pos[1]
          y2 = y1 + 5
          pygame.draw.line(screen, line_color, (x, y1), (x, y2))
        raise Exception('Line must be horizontal or vertical')  

The render() method follows a simple flow of erasing the contents of the grid (that facilitates dynamic changes to objects rendered on top of it), drawing the border and then the grid lines. The grid lines are drawn using the dash_line() function. First all the horizontal lines are drawn separated by delta_y pixels from each other and then all the vertical lines.

    # Erase the contents of the grid
    pygame.draw.rect(screen, self.background_color, self.rect)
    # Draw the grid's border
    pygame.draw.rect(screen, self.border_color, self.rect, 3)
    # Draw the grid lines
    y = + self.delta_y
    x = self.rect.left + self.delta_x
    while y < self.rect.bottom:
      dashed_line(screen, self.line_color, (self.rect.left, y), (self.rect.right, y))
      y += self.delta_y
    while x < self.rect.right:
      dashed_line(screen, self.line_color, (x,, (x, self.rect.bottom))
      x += self.delta_x

The Interactive UI

Okay, all the preliminaries are out of the way. You now understand how PyGame is used to draw simple geometric shapes on the screen and how visual objects can be created and displayed using the simple framework of the generic main loop and the BaseObject sub-class. It's time to delve into the actual interactive user interface part and see how it operates hand-in-hand with the algorithmic core discussed in Part 1.

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.