Search code examples
pythonpygamerotation

Changing a sprites direction every 10 seconds


I am writing a program where a ball moves randomly across a screen and has a trail which changes color as the trail gets overwritten. This ball is a matrix and not a sprite and uses rect instead of circle.

I have found a question similar to mine but it does not apply in the same way, at least from my understanding. (Link: Changing direction of rotation Pygame)

Basically, instead of the sprite rotating and changing direction, I need the velocity to change by a specific degree every 10 seconds. I found out about the transform.rotate function in pygame, but couldn't figure out how to implement it within my code without having an error pop up.

Here is my use of transform.rotate, which doesn't work:

#change direction every ten seconds
while True:
   newsurf = pg.transform.rotate(ballGrid, -45)
   screen.blit(newball, [[0, 1, 1, 1, 0],
                         [1, 1, 1, 1, 1],
                         [1, 1, 1, 1, 1],
                         [1, 1, 1, 1, 1],
                         [0, 1, 1, 1, 0]])
   pg.display.flip()
   time.sleep(10)

The rest of my code for reference:

import sys
from random import randrange
import pygame as pg

# define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
RED2 = (231, 0, 0)
RED3 = (207,0,0)
RED4 = (183,0,0)
RED5 = (159,0,0)
RED6 = (135,0,0)
RED7 = (111,0,0)
RED8 = (87,0,0)
RED9 = (63,0,0)
RED10 = (39,0,0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# define measurements
WIDTH, HEIGHT, MARGIN = 10, 10, 1
GRIDX, GRIDY = 36, 36

class GridObject(pg.sprite.Sprite):
    def __init__(self, pos, grid, *groups):
        super().__init__(groups)

        # create image from grid
        self.grid = grid
        self.gridsize = (len(grid[0]), len(grid))
        imgsize = self.gridsize[0]*(WIDTH+MARGIN), self.gridsize[1]*(HEIGHT+MARGIN)
        self.image = pg.Surface(imgsize, flags=pg.SRCALPHA)
        self.image.fill((0, 0, 0, 0))
        col = (235, 175, 76)
        for c in range(self.gridsize[0]):
            for r in range(self.gridsize[1]):
                if self.grid[r][c] == 1:
                    rect = [(MARGIN + WIDTH) * c + MARGIN, (MARGIN + HEIGHT) * r + MARGIN, WIDTH, HEIGHT]
                    pg.draw.rect(self.image, col, rect)

        self.rect = self.image.get_rect(center=pos)
        self.vel = pg.math.Vector2(8, 0).rotate(randrange(360))
        self.pos = pg.math.Vector2(pos)


    def update(self, boundrect, hitGrid, hitList):
        self.pos += self.vel
        self.rect.center = self.pos
        if self.rect.left <= boundrect.left or self.rect.right >= boundrect.right:
            self.vel.x *= -1                            
        if self.rect.top <= boundrect.top or self.rect.bottom >= boundrect.bottom:
            self.vel.y *= -1     
        # align rect to grid
        gridpos = round(self.rect.x / (WIDTH+MARGIN)), round(self.rect.y / (HEIGHT+MARGIN))
        self.rect.topleft = gridpos[0] * (WIDTH+MARGIN), gridpos[1] * (HEIGHT+MARGIN)

        # increment touched filed
        oldHitList = hitList[:]
        hitList.clear()
        for c in range(self.gridsize[0]):
            for r in range(self.gridsize[1]):
                p = gridpos[1] + r, gridpos[0] + c
                if p in oldHitList:
                    hitList.append(p)
                elif self.grid[r][c] == 1:
                    if p[0] < len(hitGrid) and p[1] < len(hitGrid[p[0]]):
                        hitList.append(p)
                        if p not in oldHitList:
                            hitGrid[p[0]][p[1]] +=1

ballGrid = [[0, 1, 1, 1, 0],
            [1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1],
            [0, 1, 1, 1, 0]]

def main():
    #overlap = False
    screen = pg.display.set_mode((GRIDX * (WIDTH+MARGIN) + MARGIN, GRIDY * (HEIGHT+MARGIN)))
    # Set title of screen
    pg.display.set_caption("Ball With Grid")
    clock = pg.time.Clock()
    sprite_group = pg.sprite.Group()
    ball = GridObject((screen.get_width()//2, screen.get_height()//2), ballGrid, sprite_group)
    #change direction every ten seconds
    while True:
       newsurf = pg.transform.rotate(ballGrid, -90)
       screen.blit(newball, (100,100))
       pg.display.flip()
       time.sleep(10)

    hitGrid = [[0 for i in range(GRIDX)] for j in range(GRIDY)]
    hitList = []
    done = False
    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            if event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
                hitGrid = [[0 for i in range(GRIDX)] for j in range(GRIDY)]

        screen.fill((0, 0, 0))

        # Draw the grid and add values to the cells
        for row in range(GRIDY):
            for column in range(GRIDX):
                rect = [(MARGIN + WIDTH) * column + MARGIN, (MARGIN + HEIGHT) * row + MARGIN, WIDTH, HEIGHT]
                colorlist = [WHITE, RED, RED2, RED3, RED4, RED5, RED6, RED7, RED8, RED9, RED10, GREEN]
                color = colorlist[min(len(colorlist)-1, hitGrid[row][column])]
                pg.draw.rect(screen, color, rect)
                if color == GREEN:
                    pg.quit()

        sprite_group.update(screen.get_rect(), hitGrid, hitList)
        sprite_group.draw(screen)

        pg.display.flip()
        clock.tick(30)

if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()
    sys.exit()


Solution

  • I recommend to use a timer event. Use pygame.time.set_timer() to repeatedly create an USEREVENT and change the angel of the direction vector (ball.vel) by a certain amount of degree (e.g. 30°):

    # create timer event
    change_delay = 10000 # 10 seconds
    change_event = pg.USEREVENT + 1
    pg.time.set_timer(change_event, change_delay)
    
    # [...]
    
    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
    
            # receive timer event
            if event.type == change_event:
                # change angle by 30°
                ball.vel = ball.vel.rotate(30)
    

    Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event.
    The timer event can be stopped by passing 0 to the time parameter.