Running a if statement only once in pygame

What your code is missing is abstraction.

Instead of a bunch of screen.blit and a lot of if statements, use lists.
Instead of hard-coded values (like cn1+25 < x and cn1 + 50 > x), use data structures (in your case, objects) that contain all information they need.

Here’s a simple example.

Let’s say we have this nice world where the clouds move on a sunny day:

import pygame

screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

cloud = pygame.Surface((50, 20))
cloud.set_colorkey((11, 12, 13))
cloud.fill((11, 12, 13))
pygame.draw.ellipse(cloud, pygame.Color('white'), cloud.get_rect())

cloudmove = 20

while True:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            quit()
    screen.fill(pygame.Color('lightblue'))
    screen.blit(cloud, (cloudmove+120,30))
    cloudmove += 1
    pygame.display.flip()
    clock.tick(30)

enter image description here

But of course we want more than one single cloud. What we don’t want is to duplicate code and raise the complexity. We could do something like this:

import pygame

screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

cloud = pygame.Surface((50, 20))
cloud.set_colorkey((11, 12, 13))
cloud.fill((11, 12, 13))
pygame.draw.ellipse(cloud, pygame.Color('white'), cloud.get_rect())

cloud2 = pygame.Surface((50, 20))
cloud2.set_colorkey((11, 12, 13))
cloud2.fill((11, 12, 13))
pygame.draw.ellipse(cloud2, pygame.Color('white'), cloud2.get_rect())

cloud3 = pygame.Surface((50, 20))
cloud3.set_colorkey((11, 12, 13))
cloud3.fill((11, 12, 13))
pygame.draw.ellipse(cloud3, pygame.Color('white'), cloud3.get_rect())

cloudmove = 20

while True:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            quit()
    screen.fill(pygame.Color('lightblue'))
    screen.blit(cloud, (cloudmove-120,30))
    screen.blit(cloud2, (cloudmove-220,40))
    screen.blit(cloud3, (cloudmove-170,50))
    cloudmove += 1
    pygame.display.flip()
    clock.tick(30)

enter image description here

You can already see where it is going to end if we add a dozen objects more.

So let’s try to use objects here that represent the clouds:

import pygame

screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

class Cloud:
    def __init__(self, x, y):
        self.image = pygame.Surface((50, 20))
        self.image.set_colorkey((11, 12, 13))
        self.image.fill((11, 12, 13))
        pygame.draw.ellipse(self.image, pygame.Color('white'), self.image.get_rect())
        self.x = x
        self.y = y

    def update(self):
        self.x += 1
        if self.x > 300:
            self.x = 0

clouds = [Cloud(0, 30), Cloud(100, 40), Cloud(50, 50)]

while True:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            quit()
    screen.fill(pygame.Color('lightblue'))
    for cloud in clouds:
        screen.blit(cloud.image, (cloud.x, cloud.y))
        cloud.update()
    pygame.display.flip()
    clock.tick(30)

(no new image here because it basically looks the same)

Much better! Now, to add more clouds, we would simply add a new Cloud instance to the clouds list. See how we not only store the image of a cloud in the class, but also the position. And we’ve put the cloud behaviour (moving along the sky and start from the left once we moved off the screen) into the class.

But we can still do better! Pygame offers some handy classes and functions that we can use. Take a look at the following code:

import pygame

screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

class Coin(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((44, 40))
        self.image.set_colorkey((11, 12, 13))
        self.image.fill((11, 12, 13))
        cx, cy = self.image.get_rect().center
        pygame.draw.circle(self.image, pygame.Color('grey'), (cx + 2, cy), 20)
        pygame.draw.circle(self.image, pygame.Color('orange'), (cx - 2, cy), 20)
        self.rect = self.image.get_rect(topleft=(x, y))

    def update(self):
        mouse_pos = pygame.mouse.get_pos()
        if self.rect.collidepoint(mouse_pos) and pygame.mouse.get_pressed()[0]:
            self.kill()

class Cloud(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((50, 20))
        self.image.set_colorkey((11, 12, 13))
        self.image.fill((11, 12, 13))
        pygame.draw.ellipse(self.image, pygame.Color('white'), self.image.get_rect())
        self.rect = self.image.get_rect(topleft=(x, y))

    def update(self):
        self.rect.move_ip(1, 0)
        if not pygame.display.get_surface().get_rect().colliderect(self.rect):
            self.rect.right = 0

stuff = pygame.sprite.Group(Cloud(0, 30), Cloud(100, 40), Cloud(50, 50), 
                             Coin(100, 100), Coin(150, 180), Coin(80, 200))

while True:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            quit()
    screen.fill(pygame.Color('lightblue'))
    stuff.draw(screen)
    stuff.update()
    pygame.display.flip()
    clock.tick(30)

enter image description here

We abstracted drawing and updating our objects away by using pygame’s Group and Sprite classes.

And see how easy it is to add new stuff to our little game. I added coins you can click wo collect, and all the behaviour of the coins is located in the Coin class. Once you click a coin, the kill method of the Sprite class is called, which simply removes the object from all its groups, so it’s no longer in the stuff group, and there is no longer being drawn on the screen, and effectively removed from the game (and answering your question of “Running a if statement only once”).

Leave a Comment