Skip to content
Advertisement

Adding an outline around a snake in snake game

Im making a snake game and I thought that it would realy cool if there was an outline to the snake. But just drawing a box around each body part in the snake looks bad. So I want to add an outline to only the edges of the snake, but im lost on how one would do/start it.

The body is an array full of pygame Rectangles:

self.body = [
   pygame.Rect(100, 100, 50, 50)
   pygame.Rect(150, 100, 50, 50)
   pygame.Rect(200, 100, 50, 50)
]

I have it loop through the body and draw each square in the body

for i in self.body:
   #etc

I would assume youd want to draw the outlines of the body when drawing the body but I dont quite know the logic on how it would be done.

Advertisement

Answer

Basically, for each side of a rectangle, you need to check if it is docked to another rectangle in the list, and draw a line segment along that side if it is not docked.

Iterate through all rectangles and create a list of adjacent rectangles:

for i, r in enumerate(rectangles):
    neighbours = rectangles[i-1:i] + rectangles[i+1:i+2]

Since the rectangles do not overlap, they do not collide and a collision check fails. Therefore, a rectangle must be modified, it can either be inflated on one side or moved to one side. In my solution, I choose to move the rectangle by 1 to one side and check that the rectangle does not collide with any of the adjacent rectangles. Use pygame.Rect.collidelist for the collision test. This method returns the index of the first rectangle in the list where a collision was detected, otherwise -1. e.g.:

right_test_rect = r.move(1, 0)
if right_test_rect.collidelist(neighbours) < 0:
    # [...]

Instead of drawing thick lines with pygame.draw.line I decided to draw rectangles thinly with pygame.draw.rect. pygame.draw.line draws a line between 2 coordinates and inflates the line on both sides. However, I would like to specify the exact rectangular area to be filled.


Minimal example:

import pygame

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

def drawOutline(surf, rectangles, color, width):
    for r in rectangles:
        corners = [
            (r.left, r.top), (r.right-width, r.top), 
            (r.left, r.bottom-width), (r.right-width, r.bottom-width),
        ]
        for c in corners:
            pygame.draw.rect(surf, color, (*c, width, width))

    for i, r in enumerate(rectangles):
        neighbours = rectangles[i-1:i] + rectangles[i+1:i+2]
        sides = [
            (r.move(-1,  0), (r.left, r.top+width, width, r.height-2*width)),
            (r.move( 1,  0), (r.right-width, r.top+width, width, r.height-2*width)),
            (r.move( 0, -1), (r.left+width, r.top, r.width-2*width, width)),
            (r.move( 0,  1), (r.left+width, r.bottom-width, r.width-2*width, width)),
        ]
        for test_rect, line in sides:
            if test_rect.collidelist(neighbours) < 0:
                pygame.draw.rect(surf, color, line)

body = [
   pygame.Rect(50, 100, 50, 50),
   pygame.Rect(100, 100, 50, 50),
   pygame.Rect(150, 100, 50, 50),
   pygame.Rect(200, 100, 50, 50),
   pygame.Rect(200, 150, 50, 50),
   pygame.Rect(150, 150, 50, 50),
   pygame.Rect(100, 150, 50, 50),
   pygame.Rect(100, 200, 50, 50),
   pygame.Rect(100, 250, 50, 50),
   pygame.Rect(150, 250, 50, 50),
   pygame.Rect(200, 250, 50, 50),
   pygame.Rect(250, 250, 50, 50),
]

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

    window.fill(0)
    drawOutline(window, body, "yellow", 3)
    pygame.display.flip()

pygame.quit()
exit()
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement