Search code examples
pythonpygamebordercollision-detection

creating barrier/buffer between two parts of the screen


I have no idea how to implement this, but I do know how to explain it. What the code is supposed to do is: While the program is running, stop the object moving around the screen from coming off it's designated area. How do I test for collision and reflect the moving object from that blocked off wall?

In relation to my code, I have a grid that makes up most of my screen and a small amount of the screen that holds text. The output of the code I hope to learn is two barriers: 1) for the grid so the moving object doesn't go under the text and 2) for the text so it doesn't print on other part of the screen.

Here is my current code:

import sys
from random import randrange
import pygame as pg
import timeit
import xlrd

# timer
start = timeit.default_timer()

# define main colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# define trail colors
C1 = (31, 119, 180)
C2 = (174, 199, 232)
C3 = (255, 127, 14)
C4 = (255, 187, 120)
C5 = (44, 160, 44)
C6 = (152, 223, 138)
C7 = (214, 39, 40)
C8 = (255, 152, 150)
C9 = (148, 103, 189)
C10 = (197, 176, 213)
C11 = (140, 86, 75)
C12 = (196, 156, 148)
C13 = (227, 119, 194)
C14 = (247, 182, 210)
C15 = (127, 127, 127)
C16 = (199, 199, 199)
C17 = (188, 189, 34)
C18 = (219, 219, 141)
C19 = (23, 190, 207)
C20 = (158, 218, 229)

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

# text

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 = (1, 1, 1)
        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
        global max_hit
        max_hit = 0

        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
                            max_hit = max(max_hit, hitGrid[p[0]][p[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():
    screen = pg.display.set_mode((GRIDX * (WIDTH+MARGIN) + MARGIN, GRIDY * (HEIGHT+MARGIN) + 50))
    # 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)
    hitGrid = [[0 for i in range(GRIDX)] for j in range(GRIDY)]
    hitList = []
    done = False

    # create timer event
    change_delay = 500 # 1/2 second(s)
    change_event = pg.USEREVENT + 1
    pg.time.set_timer(change_event, change_delay)

    angles = [0, 45, 90, 135, 180, 225, 270, 315]

    degreeChange = 0

    print("REMINDER: Day 0 is the first day of the simulation.")
    #text displayed at the bottom of the screen
    # get day and temp from excel file
    book = xlrd.open_workbook('daysTemp.xlsx')
    sheet = book.sheet_by_index(0)

    temps = []
    days = []
    day = 0
    for k in range(1,sheet.nrows):
        temps.append(int(sheet.row_values(k)[-2]))
        days.append(int(sheet.row_values(k)[-1]))

    d = ("day: ", str(days[0]))
    t = ("temp: ", str(temps[0]))

    print("day: ", days[day])
    print("temp: ", temps[days[day]])

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

            # receive timer event
            if event.type == change_event:
                degreeChange += 1
                if degreeChange == 1:
                    degreeChange = degreeChange - 5
                    day += 1
                    print("day: ", days[day])
                    print("temp: ", temps[days[day]])
                    d = ("day: ", str(days[day]))
                    t = ("temp: ", str(temps[day]))
                    if day >= 365:
                        pg.quit()

                for i in ball.vel:
                # change angle by 45°
                    ball.vel = ball.vel.rotate(angles[randrange(0, len(angles))])
                    #print("angle: ", i)

            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(BLACK)

        # 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,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12,C13,C14,C15,C16,C17,C18,C19,C20, WHITE]
                color = colorlist[min(len(colorlist)-1, hitGrid[row][column])]
                pg.draw.rect(screen, color, rect)

        # stops program if the max number is reached
        sprite_group.update(screen.get_rect(), hitGrid, hitList)
        if max_hit >= 21:
            print("\n"+"Program terminated.")
            done = True
            stop = timeit.default_timer()
            end = stop-start
            print("Time (in seconds):", end) 
        sprite_group.draw(screen)

        # create a font object.
        font = pg.font.Font('freesansbold.ttf', 24)
        # create a text suface object,
        text = font.render("".join(d) + ", " + "".join(t), True, GREEN, BLACK)
        # create a rectangular text object
        textRect = text.get_rect()
        # set the center of the rectangular object.
        textRect.center = ((GRIDX * (WIDTH+MARGIN) + 50) // 2, (GRIDY * (HEIGHT+MARGIN) + 50)) 

        screen.blit(text, textRect)
        pg.display.update()
        clock.tick(60)

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

Solution

  • Well it appears that you have almost done it. In update() you have

     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 
    

    which is perfect, the only problem is boundrect seems to be the whole screen

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

    All you need to do is pass a rect that doesnt include the bottom part.

    bounding_box = pg.Rect(0,0,(GRIDX * (WIDTH+MARGIN) + MARGIN,GRIDY * (HEIGHT+MARGIN))
    #this has same width and height as screen but doesnt have +50 for y
    

    then update with new rect

    sprite_group.update(bounding_box, hitGrid, hitList)
    

    As for the text, it looks like it can only be in one place so should be fine