Colliderect () fires unexpectedly

I am making a classic Snake game. I want to draw a green rectangle ( green_rect

) and whenever my snake touches it, that green rectangle moves to a new random location. When I run colliderect()

in a worm surface

, it always returns True

for some reason, though, even if the worm doesn't touch the rectangle.

So far I have tried:

width  = 640 
height = 400
screen = pygame.display.set_mode((width, height))
green  = pygame.Surface((10,10)) #it the rectangle
green.fill((0,255,0))
green_rect = pygame.Rect((100,100), green.get_size())

  # Our worm.
w = Worm(screen, width/2, height/2, 100)   # I have a class called Worm, described later...

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

        elif event.type == pygame.KEYDOWN:
            w.key_event(event)   # direction control method in the Worm class

    if green_rect.colliderect(w.rect):  # w.rect is coming from Worm class: self.rect=self.surface.get_rect()
        # surface variable is 100 coming from: w = Worm(screen, width/2, height/2, 100) -- it describes the worm length in segments / pixels.
        green_rect.x, green_rect.y = (random.randrange(420),random.randrange(300))


    screen.fill((0, 0, 0))
    screen.blit(green, green_rect)

    w.move() #function for movement in Worm class
    w.draw() #function for drawing the snake in Worm class

      

But it doesn't work, the green rectangle moves uncontrollably even if I don't touch it with the snake. colliderect

shouldn't work as the xy coordinates of the green rectangle change without the snake actually touching it. It only needs to change position when the snake touches the green rectangle.

I haven't shown all of my code because it is a bit long. If necessary, I can write a cool worm. Well rect method doesn't work for lists, so I couldn't figure out how to fix this issue.

Edit . Also I want to know how to resize the snake segments as my default size is pixel (point). I want it to be larger, so if it falls into a large rectangle, even at the corner of the rectangle or along the side, the rectangle will still move.

Edit-2: Here is my class Worm

:

class Worm():
    def __init__(self, surface, x, y, length):
        self.surface = surface
        self.x = x
        self.y = y
        self.length  = length
        self.dir_x   = 0
        self.dir_y   = -1
        self.body    = []
        self.crashed = False


    def key_event(self, event):
        """ Handle key events that affect the worm. """
        if event.key == pygame.K_UP:
            self.dir_x = 0
            self.dir_y = -1
        elif event.key == pygame.K_DOWN:
            self.dir_x = 0
            self.dir_y = 1
        elif event.key == pygame.K_LEFT:
            self.dir_x = -1
            self.dir_y = 0
        elif event.key == pygame.K_RIGHT:
            self.dir_x = 1
            self.dir_y = 0


    def move(self):
        """ Move the worm. """
        self.x += self.dir_x
        self.y += self.dir_y

        if (self.x, self.y) in self.body:
            self.crashed = True

        self.body.insert(0, (self.x, self.y))

        if len(self.body) > self.length:
            self.body.pop()


    def draw(self):
        for x, y in self.body:
            self.surface.set_at((int(x),int(y)), (255, 255, 255))

      

+3


source to share


1 answer


I think I understand what's going on here: Your class Worm

consists of an object that contains a list of xy-tuples, one for each pixel it occupies. The worm crawls around the game board, adding the next pixel on its way to this list, and discarding extra pixel positions when it reaches its maximum length. But it Worm

never defines its own rectangle, but is Worm.surface

actually the surface of the display area.

The reason your target green_rect

overflows all the time is because it w.surface

refers to screen

and not to the Worm's own pixel area. w.surface.get_rect()

so it returns a rectangle for the entire display area as intended. The green rectangle has nowhere to go that doesn't collide with this rectangle.

With that in mind, here's a solution for you:

  # in class Worm...
def __init__(self, surface, x, y, length):
    self.surface = surface
    self.x = x
    self.y = y
    self.length = length
    self.dir_x = 0
    self.dir_y = -1
    self.body = []
    self.crashed = False

    self.rect = pygame.Rect(x,y,1,1)  # the 1,1 in there is the size of each worm segment!
      # ^ Gives the 'head' of the worm a rect to collide into things with.

  #...

def move(self):
    """ Move the worm. """
    self.x += self.dir_x
    self.y += self.dir_y

    if (self.x, self.y) in self.body:
        self.crashed = True

    self.body.insert(0, (self.x, self.y))

    if len(self.body) > self.length:
        self.body.pop()

    self.rect.topleft = self.x, self.y
      # ^ Add this line to move the worm 'head' to the newest pixel.

      

Remember import pygame, random, sys

at the top of the code and type pygame.display.flip()

(or update

if you have a list of fixes to update) at the end and you should be installed.

Be that as it may, your code for the worm that bumps into itself runs, but has no effect.

Another suggestion: you can rename Worm.surface

to something like Worm.display_surface

, since pygame has a thing called Surface

and that might make it a little easier for others to see if the two were more clearly defined.


If you are looking for more sophisticated worm, consisting of list

of Rect

s, it is also possible. Just replace the xy-tuples in the list Worm.body

with Rect

and replace if (self.x, self.y) in self.body:

with if self.rect.collidelist(self.body) != -1:

and it should work the same. ( collidelist(<list of Rects>)

returns the index of the first encountered rectangle in this list, or -1 if it doesn't exist.)

Just make sure you also change self.rect

in class Worm

so that it looks more like



`self.rect = pygame.Rect(x,y, segment_width, segment_height)`

      

... and adjust self.dir_x

and self.dir_y

so they equal +/- segment_width

and segment_height

accordingly. You will need fill

each segment or use pygame.draw.rects

instead of using set_at

in Worm.draw()

.


UPDATE: variable-sized worm segments

Here is the code that uses Rect

xy-tuples for the body of the worm. I configured it so that the worm detects the size of each segment during initialization, and then moves forward in increments of that size (depending on the axis of travel). Keep in mind that the worm moves very fast, nothing stopping it from exiting the playground and is quite long at 100 units of length!

Here's my solution, anyway, is the revised code class Worm

. I have marked the modified lines with comments, including lines that are new from the original answer.

class Worm(pygame.sprite.Sprite): # MODIFIED CONTENTS!
    def __init__(self, surface, x, y, length, (seg_width, seg_height)): # REVISED
        self.surface = surface
        self.x = x
        self.y = y
        self.length = length
        self.dir_x = 0
        self.dir_y = -seg_width # REVISED
        self.body = []
        self.crashed = False

        self.rect = pygame.Rect(x,y,seg_width,seg_height) # NEW/REVISED
        self.segment = pygame.Rect(0,0,seg_width,seg_height) # NEW


    def key_event(self, event):
        """ Handle key events that affect the worm. """
        if event.key == pygame.K_UP:
            self.dir_x = 0
            self.dir_y = -self.segment.height # REVISED
        elif event.key == pygame.K_DOWN:
            self.dir_x = 0
            self.dir_y = self.segment.height # REVISED
        elif event.key == pygame.K_LEFT:
            self.dir_x = -self.segment.width # REVISED
            self.dir_y = 0
        elif event.key == pygame.K_RIGHT:
            self.dir_x = self.segment.width # REVISED
            self.dir_y = 0


    def move(self):
        """ Move the worm. """
        self.x += self.dir_x
        self.y += self.dir_y

        self.rect.topleft = self.x, self.y # NEW/MOVED

        if self.rect.collidelist(self.body) != -1: # REVISED
            self.crashed = True
            print "CRASHED!"

        new_segment = self.segment.copy() # NEW
        self.body.insert(0, new_segment.move(self.x, self.y)) # REVISED

        if len(self.body) > self.length:
            self.body.pop()


    def draw(self): # REVISED
        for SEGMENT in self.body:   # REPLACEMENT
            self.surface.fill((255,255,255), SEGMENT) # REPLACEMENT

      

... you'll have to rethink the arguments when you assign w

to include the worm segment sizes you want . In this case, I'm increasing 4 pixels wide by 4 pixels.

w = Worm(screen, width/2, height/2, 100, (4,4)) # REVISED

      

What would I do. Note that you can assign any segment size you like (as long as each dimension is an integer greater than zero), even if you want to use segments with one pixel or rectangle (which is "not square").

+1


source







All Articles