Why is the sprite not moving? [duplicate] - pygame

This question already has answers here:
Why is my PyGame application not running at all?
(2 answers)
Closed 1 year ago.
I am currently programming a pygame game where you move a spaceship around the screen. Currently, I have got to the part where I made the spaceship and tried to make it move. However, when I try to move the spaceship around, the spaceship doesn't move!
Here is my current code:
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 500))
screen.fill((255, 255, 255))
class Spaceship(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("C:/eqodqfe/spaceship.png")
self.image = pygame.transform.scale(self.image, (175, 175))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.rect.center = (self.x, self.y)
spaceship = Spaceship(screen, 400, 400)
screen.blit(spaceship.image, spaceship.rect)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 5
elif key[pygame.K_d]:
spaceship.x += 5
elif key[pygame.K_w]:
spaceship.y += 5
elif key[pygame.K_s]:
spaceship.y -= 5
spaceship.update()
pygame.display.update()
What is wrong with my current code?

You have to draw the Sprties in the application loop:
clock = pygame.time.Clock()
running = True
while running:
# handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 5
elif key[pygame.K_d]:
spaceship.x += 5
elif key[pygame.K_w]:
spaceship.y -= 5
elif key[pygame.K_s]:
spaceship.y += 5
# update the position of the object
spaceship.update()
# clear the display
screen.fill((255, 255, 255))
# draw the object
screen.blit(spaceship.image, spaceship.rect)
# update the display
pygame.display.update()
# limit frames per second
clock.tick(60)
The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
limit frames per second to limit CPU usage
Furthermore I suggest to use a pygame.sprite.Group:
pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.
The former delegates the to the update mehtod of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():
Calls the update() method on all Sprites in the Group [...]
The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]
spaceship = Spaceship(screen, 400, 400)
all_sprites = pygame.sprite.Group()
all_sprites.add(spaceship)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 5
elif key[pygame.K_d]:
spaceship.x += 5
elif key[pygame.K_w]:
spaceship.y += 5
elif key[pygame.K_s]:
spaceship.y -= 5
all_sprites.update()
screen.fill((255, 255, 255))
all_sprites.draw(screen)
pygame.display.update()

Related

How do I use keyboard inputs in pygame?

so I am making a game where this character(a circle) has to pop balloons falling from the sky to get points. But I am having trouble making my character move in the first place.
Code:
import pygame
from pygame.locals import *
pygame.init()
#Variables
white = (255, 255, 255)
blue = (70,130,180)
black = (0,0,0)
x = 400
y = 450
#screen stuff
screenwidth = 800
screenheight = 600
screen = pygame.display.set_mode((screenwidth, screenheight))
pygame.display.set_caption("Balloon Game!")
#end of screen stuff
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
# Draw Character
character = pygame.draw.circle(screen, (blue), (x, y), 50, 50)
#End of Drawing Character
# Making Arrow Keys
keyPressed = pygame.key.get_pressed()
if keyPressed[pygame.K_LEFT]:
character.x -= 1
if keyPressed[pygame.K_RIGHT]:
character.x += 1
pygame.display.update()
I would appreciate it if someone could tell me why it wasn't
working with a fixed code. Thanks!
pygame.draw.circle returns the bounding rectangle (pygame.Rect object) of the circle. However the center of the circle is always (x, y). Therefore you need to change x instead of character.x:
while True:
# [...]
pygame.draw.circle(screen, (blue), (x, y), 50, 50)
keyPressed = pygame.key.get_pressed()
if keyPressed[pygame.K_LEFT]:
x -= 1
if keyPressed[pygame.K_RIGHT]:
x += 1
This code can even be simplified:
while True:
# [...]
pygame.draw.circle(screen, (blue), (x, y), 50, 50)
keyPressed = pygame.key.get_pressed()
x += (keyPressed[pygame.K_RIGHT] - keyPressed[pygame.K_LEFT])

NameError: name 'event' is not defined [duplicate]

I am making a game in pygame 1.9.2.
It's a faily simple game in which a ship moves between five columns of bad guys who attack by moving slowly downward. I am attempting to make it so that the ship moves left and right with the left and right arrow keys. Here is my code:
keys=pygame.key.get_pressed()
if keys[K_LEFT]:
location-=1
if location==-1:
location=0
if keys[K_RIGHT]:
location+=1
if location==5:
location=4
It works too well. The ship moves too fast. It is near impossible to have it move only one location, left or right. How can i make it so the ship only moves once every time the key is pressed?
You can get the events from pygame and then watch out for the KEYDOWN event, instead of looking at the keys returned by get_pressed()(which gives you keys that are currently pressed down, whereas the KEYDOWN event shows you which keys were pressed down on that frame).
What's happening with your code right now is that if your game is rendering at 30fps, and you hold down the left arrow key for half a second, you're updating the location 15 times.
events = pygame.event.get()
for event in events:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
location -= 1
if event.key == pygame.K_RIGHT:
location += 1
To support continuous movement while a key is being held down, you would have to establish some sort of limitation, either based on a forced maximum frame rate of the game loop or by a counter which only allows you to move every so many ticks of the loop.
move_ticker = 0
keys=pygame.key.get_pressed()
if keys[K_LEFT]:
if move_ticker == 0:
move_ticker = 10
location -= 1
if location == -1:
location = 0
if keys[K_RIGHT]:
if move_ticker == 0:
move_ticker = 10
location+=1
if location == 5:
location = 4
Then somewhere during the game loop you would do something like this:
if move_ticker > 0:
move_ticker -= 1
This would only let you move once every 10 frames (so if you move, the ticker gets set to 10, and after 10 frames it will allow you to move again)
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is 1, otherwise 0. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement:
while True:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
x -= speed
if keys[pygame.K_RIGHT]:
x += speed
if keys[pygame.K_UP]:
y -= speed
if keys[pygame.K_DOWN]:
y += speed
This code can be simplified by subtracting "left" from "right" and "up" from "down":
while True:
keys = pygame.key.get_pressed()
x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action or movement:
while True:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
x -= speed
if event.key == pygame.K_RIGHT:
x += speed
if event.key == pygame.K_UP:
y -= speed
if event.key == pygame.K_DOWN:
y += speed
See also Key and Keyboard event
Minimal example of continuous movement: replit.com/#Rabbid76/PyGame-ContinuousMovement
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
rect = pygame.Rect(0, 0, 20, 20)
rect.center = window.get_rect().center
vel = 5
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
print(pygame.key.name(event.key))
keys = pygame.key.get_pressed()
rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * vel
rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * vel
rect.centerx = rect.centerx % window.get_width()
rect.centery = rect.centery % window.get_height()
window.fill(0)
pygame.draw.rect(window, (255, 0, 0), rect)
pygame.display.flip()
pygame.quit()
exit()
Minimal example for a single action: replit.com/#Rabbid76/PyGame-ShootBullet
import pygame
pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()
tank_surf = pygame.Surface((60, 40), pygame.SRCALPHA)
pygame.draw.rect(tank_surf, (0, 96, 0), (0, 00, 50, 40))
pygame.draw.rect(tank_surf, (0, 128, 0), (10, 10, 30, 20))
pygame.draw.rect(tank_surf, (32, 32, 96), (20, 16, 40, 8))
tank_rect = tank_surf.get_rect(midleft = (20, window.get_height() // 2))
bullet_surf = pygame.Surface((10, 10), pygame.SRCALPHA)
pygame.draw.circle(bullet_surf, (64, 64, 62), bullet_surf.get_rect().center, bullet_surf.get_width() // 2)
bullet_list = []
run = True
while run:
clock.tick(60)
current_time = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
bullet_list.insert(0, tank_rect.midright)
for i, bullet_pos in enumerate(bullet_list):
bullet_list[i] = bullet_pos[0] + 5, bullet_pos[1]
if bullet_surf.get_rect(center = bullet_pos).left > window.get_width():
del bullet_list[i:]
break
window.fill((224, 192, 160))
window.blit(tank_surf, tank_rect)
for bullet_pos in bullet_list:
window.blit(bullet_surf, bullet_surf.get_rect(center = bullet_pos))
pygame.display.flip()
pygame.quit()
exit()
import pygame
pygame.init()
pygame.display.set_mode()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); #sys.exit() if sys is imported
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_0:
print("Hey, you pressed the key, '0'!")
if event.key == pygame.K_1:
print("Doing whatever")
In note that K_0 and K_1 aren't the only keys, to see all of them, see pygame documentation, otherwise, hit tab after typing in
pygame.
(note the . after pygame) into an idle program. Note that the K must be capital. Also note that if you don't give pygame a display size (pass no args), then it will auto-use the size of the computer screen/monitor. Happy coding!
I think you can use:
pygame.time.delay(delayTime)
in which delayTime is in milliseconds.
Put it before events.
Try this:
keys=pygame.key.get_pressed()
if keys[K_LEFT]:
if count == 10:
location-=1
count=0
else:
count +=1
if location==-1:
location=0
if keys[K_RIGHT]:
if count == 10:
location+=1
count=0
else:
count +=1
if location==5:
location=4
This will mean you only move 1/10 of the time. If it still moves to fast you could try increasing the value you set "count" too.
The reason behind this is that the pygame window operates at 60 fps (frames per second) and when you press the key for just like 1 sec it updates 60 frames as per the loop of the event block.
clock = pygame.time.Clock()
flag = true
while flag :
clock.tick(60)
Note that if you have animation in your project then the number of images will define the number of values in tick(). Let's say you have a character and it requires 20 sets images for walking and jumping then you have to make tick(20) to move the character the right way.
Just fyi, if you're trying to ensure the ship doesn't go off of the screen with
location-=1
if location==-1:
location=0
you can probably better use
location -= 1
location = max(0, location)
This way if it skips -1 your program doesn't break
make something like this, but based on time delay. i call my function first time immediately and then lunch timer, and while button is pressed i call it every button_press_delta seconds
from time import time
before main loop:
button_press_delta = 0.2
right_button_pressed = 0
while not done:
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
if not right_button_pressed:
call_my_function()
right_button_pressed = 1
right_button_pressed_time_start = time()
if right_button_pressed:
right_button_pressed_time = (
time() - right_button_pressed_time_start)
if right_button_pressed_time > button_press_delta:
call_my_function()
right_button_pressed_time_start = time()
else:
right_button_pressed = 0
You should use clock.tick(10) as stated in the docs.
all of the answers above are too complexicated i would just change the variables by 0.1 instead of 1
this makes the ship 10 times slower
if that is still too fast change the variables by 0.01
this makes the ship 100 times slower
try this
keys=pygame.key.get_pressed()
if keys[K_LEFT]:
location -= 0.1 #or 0.01
if location==-1:
location=0
if keys[K_RIGHT]:
location += 0.1 #or 0.01
if location==5:
location=4
To slow down your game, use pygame.clock.tick(10)

Pygame, making pong

Can anyone help me out, I can't figure out why the code does not run. I feel like it is a really stupid mistake any maybe a better, second pair of eyes could lend me a hand?
It's saying my post is mainly code so I need to add some 'description', so you don't have to read this, I'm just doing this so it will let me post it.
#my pong game
import pygame, sys
pygame.init()
#global variables
screen_width = 1000
screen_height = 800
game_over = False
ball_speed_x = 15
ball_speed_y = 15
ball_width = 15
ball_height = 15
ball_color = (255,0,0)
ball_posx = int(screen_width/2 - (ball_width / 2))
ball_posy = int(screen_height/2 - (ball_width / 2))
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('PONG')
#player blueprint
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.height = 100
self.width = 20
self.vel = 15
self.color = (255,0,0)
self.player = pygame.Rect(self.x, self.y, self.width, self.height)
def draw(self):
pygame.draw.rect(screen, self.color, self.player)
#creating objects
player1 = Player(10, int(screen_height/2 - 5))
player2 = Player(screen_width - 30, int(screen_height/2 - 5))
ball = pygame.Rect(ball_posx, ball_posy, ball_height, ball_width)
def player_animation():
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
player2.y -= player2.vel
if keys[pygame.K_DOWN]:
player2.y += player2.vel
if keys[pygame.K_w]:
player1.y -= player1.vel
if keys[pygame.K_s]:
player1.y += player1.vel
def ball_animation():
global ball_posx, ball_width, ball_height, ball_posy, ball_posx, ball_speed_x, ball_speed_y, screen_width, screen_height
if ball.right >= screen_width - 5:
ball_speed_x *= -1
if ball.left <= 10:
ball_speed_x *= -1
if ball.bottom >= screen_height - 5:
ball_speed_y *= -1
if ball.top <= 5:
ball_speed_y *= -1
if player1.player.colliderect(ball):
ball_speed_x *= -1
if player2.player.colliderect(ball):
ball_speed_x *= -1
ball_posx += ball_speed_x
ball_posy += ball_speed_y
while not game_over:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
screen.fill((0,0,0))
ball_animation()
player_animation()
pygame.draw.ellipse(screen, (255,0,0), ball)
player1.draw()
player2.draw()
pygame.display.update()
pygame.quit()
sys.exit()
Everything in your code works fine apart from the draw functions. In the player class, you create the player's rectangle at the beginning and it's x and y values aren't changed throughout the game, you're simply changing the variable that was used to create the rectangle when instead you should be changing the rectangle's actual x and y variables. This can be fixed by adding these two lines in the player class:
def draw(self):
self.player.y = self.y
self.player.x = self.x
pygame.draw.rect(screen, self.color, self.player)
The self.player.y will update the rectangle's y value to the player's current value so the rectangle is drawn in the right place.
The ball has the same problem, the eclipse is created once but it's x and y values are never changed.
Instead of writing:
ball_posx += ball_speed_x
ball_posy += ball_speed_y
Do :
ball.x += ball_speed_x
ball.y += ball_speed_y
which directly access the eclipses x and y values so it can be redrawn in the right place. I made the changes stated here and everything started moving fine.

Pygame Reset game

I need some help with reseting a game I made. I've got the main loop going and the collision detection working. I'm trying to get an instant restart on the game, one that just resets the score and gets going again - I don't want it to have any user input before it restarts the game again.
MoveAsteroids() simply moves asteroids across the screen which the player has to avoid. It's also the function where score is incremented by 1 each time an asteroid is dodged.
def game_loop():
global score
while not game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
spaceship.change = -5
elif event.key == pygame.K_DOWN:
spaceship.change = 5
if event.type == pygame.KEYUP:
if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
spaceship.change = 0
spaceship.y += spaceship.change
if spaceship.y > window_height - spaceship.height: # Creating borders on the window
spaceship.y = window_height - spaceship.height
elif spaceship.y < 0:
spaceship.y = 0
window.blit(bg_img, (0, 0))
MoveAsteroids()
CollisionDetection()
Score_display("Score: " + str(score * 100), white)
pygame.display.update()
def CollisionDetection():
global score
spaceship_rect = pygame.Rect(spaceship.x, spaceship.y, spaceship.width, spaceship.height)
for x in range(1, 5):
rect = pygame.Rect(asteroids[x].x, asteroids[x].y, asteroids[x].width, asteroids[x].height)
if spaceship_rect.colliderect(rect):
pass
# The part I need help with is this line of code just above^. .colliderect() returns true when a collision happens.
If I get you right you just want to reset the game. Just do
def CollisionDetection():
global score
spaceship_rect = pygame.Rect(spaceship.x, spaceship.y, spaceship.width, spaceship.height)
for x in range(1, 5):
rect = pygame.Rect(asteroids[x].x, asteroids[x].y, asteroids[x].width, asteroids[x].height)
if spaceship_rect.colliderect(rect):
score = 0
// here you reset your spaceship.x and y to the normal state
you could also have a look at sprites. It makes collision detection easier and is nice for larger games with it's groups.

colliderect() triggers 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() on the Worm's surface, it always returns True for some reason, though, even if the worm doesn't touch the rectangle.
So far I've tried:
width = 640
height = 400
screen = pygame.display.set_mode((width, height))
green = pygame.Surface((10,10)) #it's 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's 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 this doesn't work, the green rectangle is moving uncontrollably, even if I don't touch it with the snake. colliderect must not be working since the x-y coordinates of the green rectangle are changing without the snake actually touching it. It should only change location when the snake touches the green rectangle.
I didn't show all of my code, because its little long. If it necessary I can
write out Class Worm as well. Well rect method is not working on
lists, so I couldn't figure how to fix this problem.
Edit: Also I want to know how to resize the snake's segments, since my default size is a pixel (dot). I want it bigger so if it hits a big rectangle, even on a corner of the rectangle or along a side, rectangle will still move.
Edit-2: Here is my Worm class:
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))
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' creeps across the playfield, adding the next pixel in its path to that list and discarding excess pixel locations when it's reached its maximum length. But Worm never defines its own rect and Worm.surface is actually the surface of the display area.
The reason why your target green_rect hops around all the time is because w.surface refers to screen, not the Worm's own pixel area. w.surface.get_rect() is therefore returning a rectangle for the entire display area, as suspected. The green rectangle has nowhere to go that doesn't collide with that rect.
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's 'head' to the newest pixel.
Remember to import pygame, random, sys at the top of your code and throw in pygame.display.flip() (or update, if you have a list of updating rects) at the end and you should be set.
As it stands, your code for a worm that crashes into itself runs, but has no effect.
One more suggestion: you may want to rename Worm.surface to something like Worm.display_surface, since pygame has a thing called Surface as well, and it might make it a bit easier for others to understand if the two were more clearly defined.
If you're looking for a more sophisticated worm consisting of a list of Rects, that's also possible. Just replace the xy-tuples in the Worm.body list with rects and replace if (self.x, self.y) in self.body: with if self.rect.collidelist(self.body) != -1: and it ought to work the same way. (collidelist(<list of Rects>) returns the index of the first colliding rect in that list, or -1 if there are none.)
Just make sure you also change the self.rect in class Worm so that it's more like
`self.rect = pygame.Rect(x,y, segment_width, segment_height)`
...and adjust self.dir_x and self.dir_y so that they're equal to +/- segment_width and segment_height, respectively. You'll need to fill each segment or use pygame.draw.rects instead of using set_at in Worm.draw(), as well.
UPDATE: Variable-size worm segments
Here's some code that uses Rects for the worm's body instead of xy-tuples. I have it set up so that the worm will define the size of each segment during initialization, then move forward in steps of that size (depending the axis of travel). Be aware the worm moves very quickly, has nothing stopping it from escaping the play area, and is quite long at 100 units length!
Here's my solution, in any case-- First is the revised class Worm code. I've marked altered lines with comments, including the lines that are new as of 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 revise the arguments when you assign w, too, to include your desired worm segment dimensions. In this case, I've closen 4 pixels wide by 4 pixels tall.
w = Worm(screen, width/2, height/2, 100, (4,4)) # REVISED
That's what I'd do. Note that you can assign whatever segment size you like (as long as each dimension is an integer greater than zero), even using single-pixel or rectangular (which is, 'non-square') segments if you like.