This question already has an answer here:
How do I get the snake to grow and chain the movement of the snake's body?
(1 answer)
Closed 2 years ago.
I'm a fairly newe programmer and this is the first time I develop a game and I wanted to start with something pretty simple, so I chose the snake game. I have coded everything apart from adding the body part when the food is eaten.
import random
import pygame
from pygame import *
import sys
import os
import time
###objects
class snake:
def __init__(self, win):
self.score = 1
self.length = 25
self.width = 25
self.win = win
self.r = random.randint(0,500)
self.vel = 25
self.update = pygame.display.update()
self.right = True
self.left = False
self.up = False
self.down = False
# 0 = right 1 = left 2 = up 3 = down
self.can = [True, False, True, True]
self.keys = pygame.key.get_pressed()
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.x = self.r
self.y = self.r
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
def move(self, win):
win.fill((0,0,0))
self.keys = pygame.key.get_pressed()
if self.right == True:
self.x += self.vel
if self.left == True:
self.x -= self.vel
if self.up == True:
self.y -= self.vel
if self.down == True:
self.y += self.vel
if self.x > 475:
self.x = 0
if self.x < 0:
self.x = 500
if self.y > 475:
self.y = 0
if self.y < 0:
self.y = 500
if self.keys[pygame.K_RIGHT] and self.can[0] == True:
self.right = True
self.left= False
self.up = False
self.down = False
self.can[1] = False
self.can[0] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_LEFT] and self.can[1] == True:
self.right = False
self.left = True
self.up = False
self.down = False
self.can[0] = False
self.can[1] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_UP] and self.can[2] == True:
self.right = False
self.left = False
self.up = True
self.down = False
self.can[3] = False
self.can[0] = True
self.can[1] = True
self.can[2] = True
if self.keys[pygame.K_DOWN] and self.can[3] == True:
self.right = False
self.left = False
self.up = False
self.down = True
self.can[2] = False
self.can[0] = True
self.can[1] = True
self.can[3] = True
self.length = 25 * self.score
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, self.length, self.width))
def food(self, win):
pygame.draw.rect(self.win, (255,0,0), (self.a, self.b,25,25))
if self.a == self.x and self.b == self.y:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
self.score += 1
###functions
###main game
##variables
screen = (500,500)
W = 25
L = 25
WHITE = 255,255,255
clock = pygame.time.Clock()
##game
pygame.init()
win = pygame.display.set_mode(screen)
title = pygame.display.set_caption("snake game")
update = pygame.display.update()
snake = snake(win)
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
snake.move(win)
snake.food(win)
pygame.display.update()
pygame.quit()
I know the code is a bit messy because I wanted to try to implement OOP, since I never used it.
This is also my first time using pygame, so I be doing something wrong.
So far I have made it so that the snake and food spawn in a random location in an invible grid, and when the head of the snake has the same coordinates of the food, the snake becomes longer (I'm just adding 25 pixels to the snake's body, but when it turns, the whole rectangular shaped snake turns). Also, if the snake reaches the edge of the display, the appears from the opposite side.
The comments below might sound harsh, and I've tried to write them in a neutral way simply pointing out facts and state them as they are. If you are truly a new programmer, this is a pretty good project to learn from and you've done quite good to come this far. So keep an mind that these comments are not meant to be mean, but objective and always comes with a proposed solution to make you an even better programmer, not to bash you.
I also won't go into detail in the whole list as a body thing, others have covered it but I'll use it also in this code.
Here's the result, and blow is the code and a bunch of pointers and tips.
Never re-use variables
First of all, never re-use variable names, as you've overwritten and got lucky with snake = snake() which replaces the whole snake class, and can thus never be re-used again, defeating the whole purpose of OOP and classes. But since you only use it once, it accidentally worked out ok this time. Just keep that in mind for future projects.
Single letter variables
Secondly, I would strongly avoid using single-letter variables unless you really know what you're doing and often that's tied to a math equation or something. I'm quite allergic to the whole concept of self.a and self.b as they don't say anything meaningful, and in a few iterations you probably won't have an idea of what they do either. This is common tho when you're moving quickly and you currently have a grasp on your code - but will bite you in the ass sooner or later (will/should give you bad grades in school or won't land you that dream job you're applying for).
Never mix logic in one function
You've also bundled the food into the player object, which is a big no-no. As well as render logic in the movement logic. So I propose a re-work in the shape of even more OOP where food and player are two separate entities and a function for each logical operation (render, move, eat, etc..).
So I restructured it into this logic:
While I'm at it, I also re-worked the movement mechanics a bit, to use less lines and logic to produce the same thing. I also removed all this logic:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
And replaced it with this, which does the exact same thing, but uses built-ins to produce it. And hopefully the functions/variables are more descriptive than a rogue while loop.
self.r = random.choice(range(0, 500, 25))
And the final result would look something like this:
import random
import pygame
from pygame import *
import sys
import os
import time
# Constants (Used for bitwise operations - https://www.tutorialspoint.com/python/bitwise_operators_example.htm)
UP = 0b0001
DOWN = 0b0010
LEFT = 0b0100
RIGHT = 0b1000
###objects
class Food:
def __init__(self, window, x=None, y=None):
self.window = window
self.width = 25
self.height = 25
self.x, self.y = x, y
if not x or not y: self.new_position()
def draw(self):
pygame.draw.rect(self.window, (255,0,0), (self.x, self.y, 25, 25))
def new_position(self):
self.x, self.y = random.choice(range(0, 500, 25)), random.choice(range(0, 500, 25))
class Snake:
def __init__(self, window):
self.width = 25
self.width = 25
self.height = 25
self.window = window
self.vel = 25
self.update = pygame.display.update()
start_position = random.choice(range(0, 500, 25)), random.choice(range(0, 500, 25))
self.body = [start_position]
self.direction = RIGHT
def move(self, window):
self.keys = pygame.key.get_pressed()
# since key-presses are always 1 or 0, we can multiply each key with their respective value from the
# static map above, LEFT = 4 in binary, so if we multiply 4*1|0 we'll get binary 0100 if it's pressed.
# We can always safely combine 1, 2, 4 and 8 as they will never collide and thus always create a truth map of
# which direction in bitwise friendly representation.
if any((self.keys[pygame.K_UP], self.keys[pygame.K_DOWN], self.keys[pygame.K_LEFT], self.keys[pygame.K_RIGHT])):
self.direction = self.keys[pygame.K_UP]*1 + self.keys[pygame.K_DOWN]*2 + self.keys[pygame.K_LEFT]*4 + self.keys[pygame.K_RIGHT]*8
x, y = self.body[0] # Get the head position, which is always the first in the "history" aka body.
self.body.pop() # Remove the last object from history
# Use modolus to "loop around" when you hit 500 (or the max width/height desired)
# as it will wrap around to 0, try for instance 502 % 500 and it should return "2".
if self.direction & UP:
y = (y - self.vel)%500
elif self.direction & DOWN:
y = (y + self.vel)%500
elif self.direction & LEFT:
x = (x - self.vel)%500
elif self.direction & RIGHT:
x = (x + self.vel)%500 # window.width
self.body.insert(0, (x, y))
def eat(self, food):
x, y = self.body[0] # The head
if x >= food.x and x+self.width <= food.x+food.width:
if y >= food.y and y+self.height <= food.y+food.height:
self.body.append(self.body[-1])
return True
return False
def draw(self):
for x, y in self.body:
pygame.draw.rect(self.window, (0,255,0), (x, y, self.width, self.width))
##variables
clock = pygame.time.Clock()
##game
pygame.init()
window = pygame.display.set_mode((500,500))
pygame.display.set_caption("snake game")
snake = Snake(window)
food = Food(window)
food.new_position()
score = 0
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill((0,0,0)) # Move the render logic OUTSIDE of the player object
snake.move(window)
if snake.eat(food):
score += 1
food.new_position()
snake.draw()
food.draw()
pygame.display.update()
pygame.quit()
draw() now handles all rendering logic within the objects themselves, instead of being entangled in the move().
snake.eat() is now a function that returns True or False based on the snake head (first position in history, aka body) being inside a food object. This function also adds to the body if a eat was successful, perhaps this code should be moved outside as well, but it's one line of code so I skipped on my own rule a bit to keep the code simple.
food.new_position() is a function that simply moves the food to a new position, called when eat() was successful for instance, or if you want to randomly move the food around at a given interval.
move() and finally the move function, which only has one purpose now, and that is to move the snake in a certain direction. It does so by first getting the current head position, then remove the last history item (tail moves with the head) and then adds a new position at the front of the body that is equal to velocity.
The "is inside" logic might look like porridge, but it's quite simple, and the logic is this:
If the snakes head body[0] has it's x greater or equal to the foods x, it means the heads lower upper left corner was at least past or equal to the foods upper left corner. If the heads width (x+width) is less or equal to the foods width, we're at least inside on the X axis. And then we just repeat for the Y axis and that will tell you if the head is inside or outside the boundary of the food.
The movement logic is reworked to make it fractionally faster but also less code and hopefully easier to use once you have a understanding of how it works. I switched to something called bitwise operations. The basic concept is that you can on a "machine level" (bits) do quick operations to determinate if something is true or not with AND operations for instance. To do this, you can compare to bit-sequences and see if at any point two 1 overlap each other, if not, it's False. Here's an overview of the logic used and all the possible combinations of UP, DOWN, LEFT and RIGHT in binary representation:
On a bit level, 1 is simply 0001, 2 would be 0010 and 4 being 0100 and finally 8 being 1000. Knowing this, if we press → (right) we want to convert this into the bit representation that is the static variable RIGHT (1000 in binary). To achieve this, we simply multiply the value pygame gives us when a key is pressed, which is 1. We multiply it by the decimal version of 1000 (RIGHT), which is 8.
So if → is pressed we do 8*1. Which gives us 1000. And we simply repeat this process for all the keys. If we pressed ↑ + → it would result in 1001 because 8*1 + 1*1 and since ← and ↓ weren't pressed, they will become 4*0 and 2*0 resulting in two zeroes at binary positions.
We can then use these binary representations by doing the AND operator shown in the picture above, to determinate if a certain direction was pressed or not, as DOWN will only be True if there's a 1 on the DOWN position, being the second number from the right in this case. Any other binary positional number will result in False in the AND comparitor.
This is quite efficient, and once you get the hang of it - it's pretty useful for other things as well. So it's a good time to learn it in a controlled environment where it hopefully makes sense.
The main thing to take away here (other than what other people have already pointed out, keep the tail in a array/list as a sort of history of positions) is that game objects should be individual objects, and main rendering logic shouldn't be in player objects, only player render specifics should be in the player object (as an example).
And actions such as eat() should be a thing rather than being checked inside the function that handles move(), render() and other things.
And my suggestions are just suggestions. I'm not a game developer by trade, just optimizing things where I can. Hope the concepts come to use or spark an idea or two. Best of luck.
Yo have to mange the body of the snake in a list. Add the current position of the the head at the head of the body list and remove an element at the tail of the list in ever frame.
Add an attribute self.body:
class snake:
def __init__(self, win):
# [...]
self.body = [] # list of body elements
Add the current head to the bode before the head is moved:
class snake:
# [...]
def move(self, win):
# [...]
# move snake
self.body.insert(0, (self.x, self.y))
Remove elements a the end of self.body, as long the length of the snake exceeds the score:
class snake:
# [...]
def move(self, win):
# [...]
# remove element at end
while len(self.body) >= self.score:
del self.body[-1]
Draw the bode of the snake in a loop:
class snake:
# [...]
def move(self, win):
# [...]
# draw smake and body
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, 25, self.width))
for pos in self.body:
pygame.draw.rect(self.win, (0,255,0), (pos[0], pos[1], 25, self.width))
class snake:
class snake:
def __init__(self, win):
self.score = 1
self.length = 25
self.width = 25
self.win = win
self.r = random.randint(0,500)
self.vel = 25
self.update = pygame.display.update()
self.right = True
self.left = False
self.up = False
self.down = False
# 0 = right 1 = left 2 = up 3 = down
self.can = [True, False, True, True]
self.keys = pygame.key.get_pressed()
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.x = self.r
self.y = self.r
self.body = [] # list of body elements
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
def move(self, win):
win.fill((0,0,0))
self.keys = pygame.key.get_pressed()
# move snake
self.body.insert(0, (self.x, self.y))
if self.right == True:
self.x += self.vel
if self.left == True:
self.x -= self.vel
if self.up == True:
self.y -= self.vel
if self.down == True:
self.y += self.vel
if self.x > 475:
self.x = 0
if self.x < 0:
self.x = 500
if self.y > 475:
self.y = 0
if self.y < 0:
self.y = 500
# remove element at end
while len(self.body) >= self.score:
del self.body[-1]
if self.keys[pygame.K_RIGHT] and self.can[0] == True:
self.right = True
self.left= False
self.up = False
self.down = False
self.can[1] = False
self.can[0] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_LEFT] and self.can[1] == True:
self.right = False
self.left = True
self.up = False
self.down = False
self.can[0] = False
self.can[1] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_UP] and self.can[2] == True:
self.right = False
self.left = False
self.up = True
self.down = False
self.can[3] = False
self.can[0] = True
self.can[1] = True
self.can[2] = True
if self.keys[pygame.K_DOWN] and self.can[3] == True:
self.right = False
self.left = False
self.up = False
self.down = True
self.can[2] = False
self.can[0] = True
self.can[1] = True
self.can[3] = True
# draw smake and body
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, 25, self.width))
for pos in self.body:
pygame.draw.rect(self.win, (0,255,0), (pos[0], pos[1], 25, self.width))
def food(self, win):
pygame.draw.rect(self.win, (255,0,0), (self.a, self.b,25,25))
if self.a == self.x and self.b == self.y:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
self.score += 1
I would make a body part object and when the snake gets longer you add a body part. The head does the movement and the body parts follow the head.
Each game turn you just move the head then go over all of the body parts starting from the one closest to the head and move them to their parents location. So head moves 1 block, next part moves the previous head location, third part moves to second parts previous location, ...
The error that shows up in python right now is
Traceback (most recent call last):
File "C:\Users\Eduardo.Graglia\AppData\Local\Programs\Python\Python37\Start.py", line 66, in <module>
snake = snake(self) # create an instance
File "C:\Users\Eduardo.Graglia\AppData\Local\Programs\Python\Python37\Start.py", line 17, in __init__
food.image = pygame.image.load("food.png")
AttributeError: 'str' object has no attribute 'image'
I am very new to python and pygame, so most of the code I've been using is stuff I've taken and adapted from stack overflow
What I was trying to do with the food object was make it so that it was all in one player class because I am confused on how to call methods from other classes and use objects from other classes in one class. If anyone has any alternatives to what I've done please tell me.
import pygame
import os
import time
import math
import random
length = 650
width = 400
headx = 0
heady = 0
class snake(object):
def __init__(self, food):
self.image = pygame.image.load("head.png")
self.x = 0
self.y = 0
food.image = pygame.image.load("food.png")
food.x = 0
food.y = 0
def handle_keys(self):
""" Handles Keys """
key = pygame.key.get_pressed()
dist = 25 # distance moved in 1 frame, try changing it to 5
if key[pygame.K_DOWN]: # down key
self.y += dist # move down
heady = heady - dist
elif key[pygame.K_UP]: # up key
self.y -= dist # move up
heady = heady - dist
if key[pygame.K_RIGHT]: # right key
self.x += dist # move right
headx = headx - dist
elif key[pygame.K_LEFT]: # left key
self.x -= dist # move left
headx = headx - dist
def fooddrop(food, surface):
newfoodx = 1
newfoody = 1
while newfoodx % 25 != 0 or newfoodx !=0:
newfoodx = random.randint(0,650)
while newfoody % 25 != 0 or newfoody !=0:
newfoody = random.randint(0,400)
food.x = newfoodx
food.y = newfoody
surface.blit(food.image,(food.x,food.y))
def draw(self, surface):
""" Draw on surface """
# blit yourself at your current position
surface.blit(self.image, (self.x, self.y))
self = 'null'
food = 'null'
pygame.init()
screen = pygame.display.set_mode((length, width))
snake = snake(self) # create an instance
food = snake(food)
running = True
while running:
# handle every event since the last frame.
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() # quit the screen
running = False
if snake.y > width or snake.y < 0 or snake.x > length or snake.x < 0: #Border Created
pygame.quit() # quit the screen
running = False
time.sleep(0.125)
snake.handle_keys() # handle the keys
screen.fill((0,0,0)) # fill the screen with white
snake.draw(screen) # draw the snake to the screen
snake.fooddrop(screen)
pygame.display.update() # update the screen
clock.tick(40)
Feel free to completely destroy my code because I am new and need to learn.
Thanks in advance!
You have to distinguish between Classes and instances of classes (objects).
Create a class Snake (or whatever name). Please use a leading capital letter for the name of the class to avoid confusions (see Style Guide for Python Code - Class Names).
The constructor of the class has 1 parameter (beside self), the name of the image:
class Snake(object):
def __init__(self, imagename):
self.image = pygame.image.load(imagename)
self.x = 0
self.y = 0
Create 2 instances of the class:
snake = Snake("head.png")
food = Snake("food.png")
So a while ago I learned how to create a class for which had the purpose of creating an "opponent" which would basically fight the player and etc. It was a good tutorial and while I did learn how to create this type of class, I also got issues from the code itself when testing it out.
One of the reasons it didn't work properly was because of flipping my sprites horizontally; I have 2 sprite variables, 1 that loads the images and the other which is supposed to contain a list. A loop then "flips" all the images from the original and stores it inside the empty list. This, however, caused an issue and started to make my sprite "flash" on the screen in both directions so I removed the loop, tried again and it worked but this time it only had one sprite(facing the left).
I also tried to remove the variables and the loop outside the class but that ended up not displaying the image at all.
#Goku Black
walkLeftGB = [ pygame.image.load("GB1.png"), pygame.image.load("GB2.png"), pygame.image.load("GB3.png"), pygame.image.load("GB4.png") ]
walkRightGB = []
for l in walkLeftGB:
walkRightGB.append(pygame.transform.flip(l, True, False))
for x in range(len(walkLeftGB)):
walkLeftGB[x] = pygame.transform.smoothscale(walkLeftGB[x], (372, 493))
for x in range(len(walkRightGB)):
walkRightGB[x] = pygame.transform.smoothscale(walkRightGB[x], (372, 493))
# === CLASSES === (CamelCase names)
class Enemy(object):
global vel
global walkCount1
global walkRightGB, walkLeftGB
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x, self.end]
self.walkCount1 = 0
self.vel = 3
self.walkLeftGB = walkLeftGB
self.walkRightGB = walkRightGB
def draw(self, DS):
self.move()
global walkCount1
if self.walkCount1 + 1 <= 33:
self.walkCount1 = 0
if self.vel > 0:
DS.blit(self.walkRightGB[self.walkCount1 //4], (self.x, self.y))
self.walkCount1 += 1
else:
DS.blit(self.walkLeftGB[self.walkCount1 //4], (self.x, self.y))
self.walkCount1 += 1
def move(self):
global walkCount1
if self.vel > 0:
if self.x + self.vel < self.path[1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount1 = 0
else:
if self.x - self.vel > self.path[0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount1 = 0
man = Enemy(1600, 407, 96, 136, 22)
def redrawGameWindow():
global walkCount
pygame.display.update()
man.draw(DS)
DS.blit(canyon,(0,0))
lastMoved = "left"
if walkCount + 1 >= 27:
walkCount = 0
if left:
DS.blit(walkLeft[walkCount//3],(x,y))
walkCount +=1
lastMoved = "left"
elif right:
DS.blit(walkRight[walkCount//3], (x,y))
walkCount +=1
lastMoved = "right"
else: #this is when its moving neither left or right
if lastMoved == "left":
DS.blit(char2, (x, y))
else:
DS.blit(char, (x, y))
#The "redrawGameWindow" is then called in a loop inside a function.
After I made those changes mentioned above, the output received was now the image not appearing at all, I expected the output for the image to appear(and maybe move)
This does not appear to do what you want it to:
if self.walkCount1 + 1 <= 33:
self.walkCount1 = 0
As a detail, it's weird to add one and compare to 33, when if self.walkCount1 <= 32 would suffice.
More importantly, it looks like you wanted >= there.
You have some hard-coded magic number divisors: // 3 and // 4.
Rather than e.g. 4, it would be much better to
refer to len(self.walkRightGB).
Then you could choose to insert new interpolated walking images,
or delete some, without having to worry about correctness of
other code that may be affected.
Numbers like 27 should also be expressed in more meaningful terms.
It's not clear to me you want // integer division.
Possibly you were looking for % modulo instead.
As written, it looks like there's a danger of the code
trying to access past the end of an image array.
Printing out the counts would help you to debug this,
for example by verifying that incremented count is preserved
across function calls.
So im working on a game for some coursework in my computing course, im almost finished but for the life of me i cant get multiple of the Spider sprite to spawn in at the correct locations or reset properly between levels. I've tried adding different instances to groups before but i always get a different error with each method that i try. the code is below and im fairly new to pygame so sorry for the messy code..
#
class Spider(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load( 'Assets/Level-Assets/Level 1/Enemies/Spider_Down.png')
self.rect = self.image.get_rect()
self.Health = 3
self.Spawned = False
self.range = 100
self.Gravity= 6
self.pos_difference = 0
self.Active = False
self.Fall = False
self.Spiders = []
##################################################################################################################
def Set_Position(self):
if self.Spawned == False:
self.rect.center = (World.mapx, World.mapy)
Enemies.add(self)
self.Spawned = True
else:
self.rect.center = self.rect.center
######################################################################################################################
def update(self):
self.pos_difference = Player.rect.centery - self.rect.centery
if Player.rect.centerx >= self.rect.centerx -4 or Player.rect.centerx >= self.rect.centerx + 4:
if Player.rect.centery > self.rect.centery:
if self.pos_difference < 200:
self.Active = True
self.Fall = True
if self.Active == True:
if self.Fall == True:
self.rect.centery += self.Gravity
This is the code for the Spider, its not actually doing anything until it is called within the World class, which is where i believe the majority of the problem is...
def DisplayWorld(self):
self.MapLoad = False
for Row in range(len(self.newMap)):
for Col in range(len(self.newMap[Row])):
self.mapx = Col * 64
self.mapy = ((Row + self.Height_modifier) * 64)
self.tile_pos = (self.mapx, self.mapy )
if int(self.newMap[Row][Col]) == 1:
self.rect = self.Brick_Center.get_rect(left = (self.mapx) , bottom = (self.mapy))
self.World_sprites.add(World)
self.Camera_Pos_Check()
Player.Collision_Check()
Spider.Collision_Check()
Shoot.Collision_Check()
self.World_sprites.draw(screen)
elif int(self.newMap[Row][Col]) == 2:
Enemies.add(Spider(screen))
def main():
play = True
while play:
if pygame.key.get_pressed()[pygame.K_ESCAPE]:
play = False
if not Sprites.sprites():
Sprites.add(Player,Spider)
print(Sprites)
clock.tick(CLOCKRATE)
pygame.mouse.set_visible(False)
for event in pygame.event.get():
if event.type == pygame.QUIT:
play = False
screen.blit(bgi,(0,0))
screen.blit(bgi,(0,500))
World.findLevel()
Sprites.update()
Enemies.draw(screen)
Sprites.draw(screen)
if Shoot.bullet == True:
Shoot.update()
for b in range(len(Shoot.bullets)):
screen.blit(Shoot.image, (Shoot.bullets[b][0],Shoot.bullets[b][1]))
UI.Health_Display()
pygame.display.flip()
Sprites = pygame.sprite.Group()
Enemies = pygame.sprite.Group()
UI = User_Interface()
World = World()
Player = Proto()
Shoot = Shoot()
Portal = Portals()
Spider = Spider()
main()
I've found your problem: you overwrite your Spider class by an instance of it (Spider()) and then give it the same name. Thus you're consistently adding the same single spider to your enemies list. Instead, you should remove this definition on the bottom of your file and where ever you're adding the (multiple) spider(s), you should create this instance.
In a more general remark, it is considered bad style (and not too great for performance) to use global variables as widely as you do. It'd be much better to pass them around between functions and classes. Also, the CamelCase capitalization you use for all of your variables is commonly only used for classes. I'd recommend checking up on pep8 to learn more about how to use common Python styles. It makes these kinds of problems easier to spot, less likely to occur, and simplifies the reading for anyone involved. Using these points properly might even improve your grade significantly ;).
I have Particle and Explosion classes I am drawing using pygame.
An Explosion represents a bunch of flying Particles.
A Particle fades eventually; when its ttl property becomes 0 it should not be visible and should be removed from the Explosion.particles.
I want the program to delete dead particles so that they are not updated.
The issue I am having is with the Explosion.update() method. It seems it's not removing any Particles. I am experiencing a bug where supposedly 'dead' Particles are still drawn but their movement is not updated.I've experimented on lists in python and verified that the premise of iterating through a 'dead' list to remove from the other list works.Any suggestions on where the fault lies in this code would be greatly appreciated.
Edit: I've attached and shortened both source files for better context.
explosion.py
import sys
import pygame
from particleac import *
class App:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode(SCREEN_SIZE, pygame.HWSURFACE|pygame.DOUBLEBUF)
self.clock = pygame.time.Clock()
self.running = True
self.explosions = []
self.frame_no = 0
def check_input(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEMOTION:
x, y = pygame.mouse.get_pos()
self.explosions.append(Explosion(x, y))
def update_screen(self):
self.screen.fill(BLACK)
dead_explosions = []
# remove explosion if particles all gone
for e in self.explosions:
if len(e.particles) == 0:
dead_explosions.append(e)
else:
for p in e.particles:
pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
p.update()
for e in dead_explosions:
self.explosions.remove(e)
pygame.display.flip()
self.frame_no += 1
self.frame_no %= 60
self.clock.tick(FRAMERATE)
def run(self):
while self.running:
self.check_input()
self.update_screen()
pygame.quit()
app = App()
app.run()
particleac.py
import random
import sys
BLACK = [ 0, 0, 0]
WHITE = [255, 255, 255]
BLUE = [0, 0, 255]
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 400
SCREEN_CENTRE = [SCREEN_WIDTH/2, SCREEN_HEIGHT/2]
FRAMERATE = 40
SCREEN_SIZE = [SCREEN_WIDTH, SCREEN_HEIGHT]
GRAVITY = 1
TERMINAL_VELOCITY = 10
class Particle:
def __init__(self, x=SCREEN_CENTRE[0], y=SCREEN_CENTRE[1], colour=WHITE):
self.x = x
self.y = y
self.colour = colour
self.brightness = 255
self.size = 2
self.x_velocity = random.randrange(-4, 4)
self.y_velocity = random.randrange(-8, 3)
self.ttl = random.randrange(50, 200)
self.decay = (self.ttl / FRAMERATE) * 2
def update(self):
if self.ttl > 0:
self.ttl -= 1
self.brightness -= self.decay
if self.brightness < 0:
self.brightness = 0
self.colour[0] = self.colour[1] = self.colour[2] = self.brightness
self.y_velocity += GRAVITY
self.y += self.y_velocity
self.x += self.x_velocity
if self.y > SCREEN_HEIGHT:
self.y -= SCREEN_HEIGHT
class Explosion:
MAX_NUM_PARTICLES = 2
def __init__(self, x=0, y=0, colour=WHITE):
self.x = x
self.y = y
self.colour = colour
self.x_velocity = random.randrange(-4, 4)
self.y_velocity = random.randrange(-6, -1)
self.num_particles = random.randrange(1, self.MAX_NUM_PARTICLES)
self.particles = []
for i in range(self.MAX_NUM_PARTICLES):
p = Particle(self.x, self.y)
p.colour = self.colour
p.x_velocity += self.x_velocity
p.y_velocity += self.y_velocity
self.particles.append(p)
def update(self):
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.ttl > 0]
sys.stdout.write("len(self.particles) == {}".format(len(self.particles)))
sys.stdout.flush()
Your code works for me, once sys.out.write() is changed to sys.stdout.write(). Since there is that error in your code, are you sure that the code in your post the same as the code that is failing?
You can simplify Explosion.update() to this:
def update(self):
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.ttl > 0]
Possibly this change might fix the problem because you are now dealing with only one list, but I believe that your code should work.
The problem may come from the fact that self.particles is defined at the class level. This may cause some problems elsewhere in your code of you create more than one instance of the Explosion class. Try moving the self.particle in the constructor, and see what happens.
(As a suggestion) Since you build a list anyways, why not copy the ones that are alive?
def update(self):
particles_alive = []
for p in self.particles:
p.update()
sys.out.write("p.ttl == {}\n".format(p.ttl))
if p.ttl == 0:
sys.out.write("Particle died.\n")
else:
particles_alive.append(p)
self.particles = particles_alive
Solution: I was never calling e.update() !Therefore Explosions weren't updating while iterating through them, which meant the dead Particles weren't being removed. Aaargh!I added e.update() to app.update_screen() and removed the incorrect p.update() (which is called for each Particle in the e.update()).
def update_screen(self):
self.screen.fill(BLACK)
dead_explosions = []
for e in self.explosions:
if len(e.particles) == 0:
dead_explosions.append(e)
else:
e.update()
for p in e.particles:
pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
for e in dead_explosions:
self.explosions.remove(e)
pygame.display.flip()
self.clock.tick(FRAMERATE)
I now know to give better context when posting up code. Thank you kindly for your trouble guys!