Related
I don't know if there is a better way to implement ramps.
First i calculate the points that belong to the hipotenuse and use collidepoint to see if there is a collision between the rectangle and any point that belongs to the hipotenuse, then i update the rectangle based on the point where there was a collision.
Being careful when the rectangle is at the top of the ramp.
The rectangle ascends the ramp perfectly, but when the rectangle descends the ramp, the rectangle shakes.
import sys
import pygame
from pygame.locals import *
pygame.init()
fps = 60
fpsClock = pygame.time.Clock()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
def draw_grid():
for y in range(0,height,32):
pygame.draw.line(screen,'red',(0,y),(width,y))
for x in range(0,width,32):
pygame.draw.line(screen,'red',(x,0),(x,height))
class Ramp(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
#self.image.fill('green')
pygame.draw.polygon(self.image, color,
points=[(0, 0), (0, height), (width, height)])
self.rect = self.image.get_rect(topleft=(x, y))
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill('blue')
self.rect = self.image.get_rect(topleft=(x,y))
self.speed = 5
self.direction = pygame.math.Vector2(0,0)
self.gravity = 0.9
self.initial_jump = -20
self.on_ground = True
def apply_gravity(self):
self.direction.y += self.gravity
self.rect.y += self.direction.y
def move(self):
keys=pygame.key.get_pressed()
if keys[K_LEFT]:
self.direction.x = -self.speed
elif keys[K_RIGHT]:
self.direction.x = self.speed
else:
self.direction.x = 0
if keys[K_UP] and self.on_ground:
self.direction.y = self.initial_jump
self.on_ground = False
self.rect.x += self.direction.x
def check_borders(self):
if self.rect.x <= 0:
self.rect.x = 0
if self.rect.right >= width:
self.rect.right = width
if self.rect.bottom >= height:
self.rect.bottom = height
self.direction.y = 0
self.on_ground = True
if self.rect.colliderect(ramp_rect):
if self.direction.x > 0 and abs(self.rect.right-ramp_rect.left) <= 5:
self.rect.right = ramp_rect.left
# ramp stuff
for p in hypotenuse_points:
if self.rect.collidepoint(p):
if self.rect.left >= ramp_rect.left:
self.rect.bottomleft = p
else:
self.rect.bottom = ramp_rect.top
self.on_ground = True
self.direction.y = 0
def update(self):
self.move()
self.apply_gravity()
self.check_borders()
player = pygame.sprite.GroupSingle(Player(12*32,10*32))
ramp = pygame.sprite.GroupSingle(Ramp(5*32,10*32,7*32,5*32,'red'))
ramp_rect = ramp.sprite.rect
m = (ramp_rect.height)/( ramp_rect.width)
x1,y1 = ramp_rect.topleft
hypotenuse_points = []
for x in range(ramp_rect.left,ramp_rect.right):
hypotenuse_points.append((x,m*(x-x1)+y1)) # Point-slope equation
while True:
screen.fill('white')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ramp.draw(screen)
player.update()
player.draw(screen)
#draw_grid()
pygame.draw.lines(screen,'black',False,hypotenuse_points,3)
pygame.display.update()
fpsClock.tick(fps)
There is no problem with your code. Only gravity is too weak. The movement is so fast that gravity is acting too late. Note that instead of moving down the slope, you move to the right and then fall.
Of course there is one problem with your code. Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If this is done every frame, the position error will accumulate over time.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle.
Instead of the list of points I suggest to compute the height of the ramp under the palyer:
if self.rect.colliderect(ramp_rect):
ratio = ramp_rect.height / ramp_rect.width
self.rect.bottom = ramp_rect.bottom - (ramp_rect.right - max(self.rect.left, ramp_rect.left)) * ratio
self.y = self.rect.y
Complete example:
import sys
import pygame
from pygame.locals import *
pygame.init()
fps = 60
fpsClock = pygame.time.Clock()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
def draw_grid():
for y in range(0,height,32):
pygame.draw.line(screen,'red',(0,y),(width,y))
for x in range(0,width,32):
pygame.draw.line(screen,'red',(x,0),(x,height))
class Ramp(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
#self.image.fill('green')
pygame.draw.polygon(self.image, color,
points=[(0, 0), (0, height), (width, height)])
self.rect = self.image.get_rect(topleft=(x, y))
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill('blue')
self.rect = self.image.get_rect(topleft=(x,y))
self.x, self.y = self.rect.topleft
self.speed = 5
self.direction = pygame.math.Vector2(0,0)
self.gravity = 0.9
self.initial_jump = -20
self.on_ground = True
def apply_gravity(self):
self.direction.y += self.gravity
self.y += self.direction.y
self.rect.y = round(self.y)
def move(self):
keys = pygame.key.get_pressed()
self.direction.x = (keys[K_RIGHT] - keys[K_LEFT]) * self.speed
if keys[K_UP] and self.on_ground:
self.direction.y = self.initial_jump
self.on_ground = False
self.x += self.direction.x
self.rect.x = round(self.x)
def check_borders(self):
if self.rect.x <= 0:
self.rect.x = 0
self.x = self.rect.x
if self.rect.right >= width:
self.rect.right = width
self.x = self.rect.x
if self.rect.bottom >= height:
self.rect.bottom = height
self.direction.y = 0
self.on_ground = True
self.y = self.rect.y
if self.rect.colliderect(ramp_rect):
if self.old_rect.right-1 <= ramp_rect.left:
self.rect.right = ramp_rect.left
self.x = self.rect.x
else:
ratio = ramp_rect.height / ramp_rect.width
bottom = ramp_rect.bottom - (ramp_rect.right - max(self.rect.left, ramp_rect.left)) * ratio
if self.on_ground or self.rect.bottom > bottom:
self.rect.bottom = bottom
self.y = self.rect.y
self.direction.y = 0
self.on_ground = True
def update(self):
self.old_rect = self.rect.copy()
self.move()
self.apply_gravity()
self.check_borders()
player = pygame.sprite.GroupSingle(Player(12*32,10*32))
ramp = pygame.sprite.GroupSingle(Ramp(5*32,10*32,7*32,5*32,'red'))
ramp_rect = ramp.sprite.rect
m = (ramp_rect.height)/( ramp_rect.width)
x1,y1 = ramp_rect.topleft
while True:
screen.fill('white')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ramp.draw(screen)
player.update()
player.draw(screen)
pygame.display.update()
fpsClock.tick(fps)
I have a simple shooter game using pygame. I'm having some problems making the bullet's y coordinate slowly increasing up. I know this is something to do with the way I've programmed the Player class even though a bullet Rect is in it. I think I have to change the update function inside it. This is my code:
import pygame, random, sys, time
pygame.init()
#Constants
WIDTH = 800
HEIGHT = 500
BLACK = (0, 0, 0)
WHITE = (255, 255, 255) # Background Colour
RED = (255, 0, 0)
GREEN = (0, 255, 0)
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pygame Shooter Game")
clock = pygame.time.Clock()
fps = 60
run = True
class Player():
def __init__(self, width, colour, x, y):
self.width = width
self.colour = colour
self.x = x
self.y = y
self.vel = 5
self.shoot = False
self.player = pygame.Rect(self.x, self.y, self.width, self.width)
self.cartridge = pygame.Rect(0, 0, self.width/2, self.width/2)
self.bullet = pygame.Rect(0, 0, 10, 20)
self.shoot = False
def draw(self, win):
self.win = win
pygame.draw.rect(self.win, self.colour, self.player) # Draw player(rect)
pygame.draw.rect(self.win, GREEN, self.cartridge) #Draw cartridge
if self.shoot:
pygame.draw.rect(self.win, BLACK, self.bullet)
def move(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and self.x > 0: self.x -= self.vel # We don't do elif cuz we want them to be able to move diagonally
if keys[pygame.K_RIGHT] and self.x < WIDTH-self.width: self.x += self.vel
if keys[pygame.K_UP] and self.y > 0: self.y -= self.vel
if keys[pygame.K_DOWN] and self.y < HEIGHT-self.width: self.y += self.vel
if keys[pygame.K_SPACE]:
self.shoot = True
def update(self):
self.player = pygame.Rect(self.x, self.y, self.width, self.width)
self.cartridge.midbottom = self.player.midtop
self.bullet.midbottom = self.cartridge.midtop
if self.shoot:
while self.bullet.y > 0:
self.bullet.y -= 1
def main(win):
run = True
player = Player(50, RED, WIDTH/2, HEIGHT/2)
while run:
win.fill(WHITE)
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
player.move()
player.update()
player.draw(win)
pygame.display.update()
pygame.quit()
sys.exit()
main(window)
Also, how can I make the create classes for each individual Cartridge and Bullet, to make the whole code more efficient?
update is invoked continuously in the main application loop. Therefore, no additional animation loops are required for the update. Change the loop to a selection (change while to if):
while self.bullet.y > 0:
if self.bullet.y > 0:
self.bullet.y -= 1
The starting position of the bullet must be set when the bullet is shot, rather than continuously when the bullet is updated:
class Player():
# [...]
def move(self):
# [...]
if keys[pygame.K_SPACE]:
self.shoot = True
self.bullet.midbottom = self.cartridge.midtop # <--- INSERT
def update(self):
self.player = pygame.Rect(self.x, self.y, self.width, self.width)
self.cartridge.midbottom = self.player.midtop
# self.bullet.midbottom = self.cartridge.midtop <--- DELETE
if self.shoot:
if self.bullet.y > 0: # <--- if (not while)
self.bullet.y -= 1
See also:
How can i shoot a bullet with space bar?
How do I stop more than 1 bullet firing at once?
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I'm having this error when I made my bullets, I do not know why it is not working because before I made my bullet class it was working perfectly fine. I have tried rewriting this part of the the code but that did not work
self.rect = pygame.Rect(x,y,width,height)
TypeError: Argument must be rect style object
I do not know how to fix the error even though I am defiantly sure I wrote it right. the error is happening in my player class
the player class and were the error is happening
# Class Player
class player:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.speed = 4
self.color = color
self.rect = pygame.Rect(x,y,width,height)
self.ss1 = pygame.image.load("heroplane1.png")
self.ss1 = pygame.transform.scale(self.ss1,(self.ss1.get_width()//9,self.ss1.get_height()//9))
def draw(self):
self.rect.topleft=(self.x,self.y)
pygame.draw.rect(window,self.color,self.rect)
player_rect = self.ss1.get_rect(center = self.rect.center)
player_rect.centerx += -7
player_rect.centery += -6
window.blit(self.ss1,player_rect)
my full code
import pygame
pygame.init()
# Build The Screen
window = pygame.display.set_mode((700,500))
# Name Screen
pygame.display.set_caption("Noobs first Game")
bg = pygame.image.load("skybg1.png")
bg_shift = 0
# Class Player
class player:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.speed = 4
self.color = color
self.rect = pygame.Rect(x,y,width,height)
self.ss1 = pygame.image.load("heroplane1.png")
self.ss1 = pygame.transform.scale(self.ss1,(self.ss1.get_width()//9,self.ss1.get_height()//9))
def draw(self):
self.rect.topleft=(self.x,self.y)
pygame.draw.rect(window,self.color,self.rect)
player_rect = self.ss1.get_rect(center = self.rect.center)
player_rect.centerx += -7
player_rect.centery += -6
window.blit(self.ss1,player_rect)
# Class Enemy
class enemy:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.speed = 4
self.color = color
self.rect = pygame.Rect(x,y,width,height)
self.ss1 = pygame.image.load("enemyplane1.png")
self.ss1 = pygame.transform.scale(self.ss1,(self.ss1.get_width()//9,self.ss1.get_height()//9))
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.rect)
enemy_rect = self.ss1.get_rect(center = self.rect.center)
enemy_rect.centerx += -2
enemy_rect.centery += -6
window.blit(self.ss1,enemy_rect)
# Class Enemy2
class enemy2:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.rect = pygame.Rect(x,y,width,height)
self.ss1 = pygame.image.load("enemyplane2.png")
self.ss1 = pygame.transform.scale(self.ss1,(self.ss1.get_width()//9,self.ss1.get_height()//9))
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.rect)
enemy2_rect = self.ss1.get_rect(center = self.rect.center)
enemy2_rect.centerx += -4
enemy2_rect.centery += -6
window.blit(self.ss1,enemy2_rect)
# Class Enemy3
class enemy3:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.rect = pygame.Rect(x,y,width,height)
self.ss1 = pygame.image.load("enemyplane3.png")
self.ss1 = pygame.transform.scale(self.ss1,(self.ss1.get_width()//9,self.ss1.get_height()//9))
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.rect)
enemy3_rect = self.ss1.get_rect(center = self.rect.center)
enemy3_rect.centerx += -4
enemy3_rect.centery += -6
window.blit(self.ss1,enemy3_rect)
class projectile(object):
def __init__(self, x, y,color):
self.x = x
self.y = y
self.slash = pygame.image.load("herogun1.png")
self.rect = self.slash.get_rect()
self.rect.topleft = ( self.x, self.y )
self.speed = 10
self.color = color
def draw(self, window):
self.rect.topleft = ( self.x,self.y )
window.blit(slash, self.rect)
class enemygun:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.rect = pygame.Rect(x,y,width,height)
self.ss1 = pygame.image.load("enemygun1.png")
self.ss1 = pygame.transform.scale(self.ss1,(self.ss1.get_width()//11,self.ss1.get_height()//11))
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.rect)
enemy3_rect = self.ss1.get_rect(center = self.rect.center)
enemy3_rect.centerx += -7
enemy3_rect.centery += -2
window.blit(self.ss1,enemy3_rect)
# Color
white = (255,255,255)
# Draw Player
playerman = player(5,250,90,40,white)
# For Enemy
enemy1 = enemy(400,100,90,40,white)
enemy4 = enemy(400,400,90,40,white)
# For Enemy2
enemy21 = enemy2(400,300,90,40,white)
# For Enemy3
ememy31 = enemy3(400,400,90,40,white)
egun1 = enemygun(250,300,30,20,white)
enemys = [enemy1,enemy4]
# enemys
enemyGroup = pygame.sprite.Group()
level1 = [
" 1",
" 1",
" 1",
" 1",
" 1",
" 1",
" 1",]
for iy, row in enumerate(level1):
for ix, col in enumerate(row):
if col == "1":
new_enemy = enemy(ix*70,iy*70,90,40,(255,255,255))
enemys.append(new_enemy)
# Redrawwinodw
def redrawwindow():
window.fill((0,0,0))
bg_width = bg.get_width()
bg_offset = bg_shift % bg_width
window.blit(bg, (-bg_offset, 0))
window.blit(bg, (bg_width - bg_offset, 0))
# Draw playerman
playerman.draw()
# Draw enemy
for enemy in enemys:
enemy.draw()
# Draw enemy2
enemy21.draw()
# Draw enemy3
ememy31.draw()
# Draw enemygun
egun1.draw()
# FPS Cnd Clock
fps = (30)
clock = pygame.time.Clock()
bullets = []
# Main Loop
run = True
while run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_f]:
for bullet in bullets:
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.speed
else:
bullets.pop(bullets.index(bullet))
if len(bullets) < 2:
bullets.append(projectile(round(playerman.x+playerman.width//2),round(playerman.y + playerman.height-54),(0,0,0)))
for enemy in enemys:
enemy.x -= enemy.speed
bg_shift += round(3/2)
# Keys For Playerman
keys = pygame.key.get_pressed()
if keys[pygame.K_a] and playerman.x > playerman.speed:
playerman.x -= playerman.speed
if keys[pygame.K_d] and playerman.x < 260 - playerman.width - playerman.speed:
playerman.x += playerman.speed
if keys[pygame.K_w] and playerman.y > playerman.speed:
playerman.y -= playerman.speed
if keys[pygame.K_s] and playerman.y < 500 - playerman.height - playerman.speed:
playerman.y += playerman.speed
if keys[pygame.K_SPACE]:
if playerman.left:
facing = -1
else:
facing = 1
if len(bullets) < 5: # This will make sure we cannot exceed 5 bullets on the screen at once
bullets.append(player(round(playerman.x+playerman.width//2), round(playerman.y + playerman.height//2), 6, (255,255,255), white))
# Update And Other Sutff
redrawwindow()
for bullet in bullets:
bullet.draw()
pygame.display.update()
pygame.quit()
The problem is in the lines
if len(bullets) < 5: # This will make sure we cannot exceed 5 bullets on the screen at once
bullets.append(player(round(playerman.x+playerman.width//2), round(playerman.y + playerman.height//2), 6, (255,255,255), white))
You are trying to pass (255,255,255) as the height. It needs to be a number, not a tuple.
This question already has answers here:
Spawning multiple instances of the same object concurrently in python
(1 answer)
Trying to delay a specific function for spawning enemy after a certain amount of time
(1 answer)
Closed 2 years ago.
I wonder how I can spawn objects (like an enemy) at a random position with a 2 seconds delay/cooldown.
I know how to spawn them at a random coordinate. But what I'm wondering is how I can spawn multiple objects and still keep track of the other ones that are already moving, kind of like you do when you shoot bullets in a pygame.
The time delay/cooldown I can probably solve just by using pygame.time.get_ticks(). So my main question is how I can spawn multiple objects and track them with hitboxes (which I have already made)
Here is the basic part that in this example spawns an asteroid.
class Enemy:
asteroids = [pygame.image.load('rock0.png'), pygame.image.load('rock1.png'), pygame.image.load('rock2.png'),
pygame.image.load('rock3.png'), pygame.image.load('rock4.png')]
def __init__(self, y, width, height):
self.width = width
self.height = height
self.vel = 1.5
self.x = random.randrange(screen_width - self.width * 2)
self.y = y
self.asteroid = random.choice(self.asteroids)
def draw(self, win):
self.move()
win.blit(self.asteroid, (self.x, self.y))
def move(self):
self.y = self.y + self.vel
Here is the entire code for anybody who needs it.
import pygame
import random
pygame.init()
screen_width = 500
screen_height = 500
win = pygame.display.set_mode((screen_width, screen_height))
walk_left = [pygame.image.load('sprite_5.png'), pygame.image.load('sprite_6.png')]
walk_right = [pygame.image.load('sprite_3.png'), pygame.image.load('sprite_4.png')]
standing = [pygame.image.load('sprite_0.png'), pygame.image.load('sprite_1.png'), pygame.image.load('sprite_2.png')]
class Player:
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 4
self.left = False
self.right = False
self.standing = True
self.walk_count = 0
self.hitbox = (self.x + 2, self.y + 26, 123, 45)
def draw(self, win):
if self.walk_count + 1 >= 12:
self.walk_count = 0
if not self.standing:
if self.left:
win.blit(walk_left[self.walk_count // 6], (self.x, self.y))
self.walk_count += 1
elif self.right:
win.blit(walk_right[self.walk_count // 6], (self.x, self.y))
self.walk_count += 1
else:
win.blit(standing[self.walk_count // 4], (self.x, self.y))
self.walk_count += 1
self.hitbox = (self.x + 2, self.y + 26, 123, 45)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
def move():
if keys[pygame.K_LEFT] and man.x > man.vel or keys[pygame.K_a] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.left = False
man.right = True
man.standing = False
else:
man.standing = True
class Enemy:
asteroids = [pygame.image.load('rock0.png'), pygame.image.load('rock1.png'), pygame.image.load('rock2.png'),
pygame.image.load('rock3.png'), pygame.image.load('rock4.png')]
number = [0, 1, 2, 3, 4]
def __init__(self, y, width, height):
self.width = width
self.height = height
self.vel = 1.5
self.x = random.randrange(screen_width - self.width * 2)
self.y = y
self.index = random.choice(self.number)
self.hitbox = (self.x, self.y, self.width, self.height)
def draw(self, win):
self.move()
win.blit(self.asteroids[self.index], (self.x, self.y))
if self.index == 0:
self.hitbox = (self.x + 68, self.y + 68, self.width - 10, self.height - 14)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
elif self.index == 1:
self.hitbox = (self.x + 38, self.y + 47, self.width + 20, self.height - 5)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
elif self.index == 2:
self.hitbox = (self.x + 18, self.y + 12, self.width + 32, self.height + 30)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
elif self.index == 3:
self.hitbox = (self.x + 20, self.y + 32, self.width + 16, self.height + 5)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
else:
self.hitbox = (self.x + 4, self.y + 7, self.width - 24, self.height - 31)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
def move(self):
self.y = self.y + self.vel
class Projectile:
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.vel = 5
def draw(self, win):
pygame.draw.rect(win, self.color, (self.x, self.y, self.height, self. width))
class Unit:
def __init__(self):
self.last = pygame.time.get_ticks()
self.cooldown = 200
def fire(self):
now = pygame.time.get_ticks()
if now - self.last >= self.cooldown:
self.last = now
spawn_bullet()
def spawn_bullet():
if keys[pygame.K_SPACE]:
bullets.append(Projectile((man.x + man.width // 2), (man.y - 7), 7, 3, (255, 0, 0)))
def re_draw():
win.fill((0, 0, 0))
asteroid.draw(win)
man.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
delay = Unit()
man = Player(186, 400, 128, 128)
bullets = []
asteroid = Enemy(10, 64, 64)
run = True
clock = pygame.time.Clock()
while run:
last = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if 0 < bullet.y < 500:
bullet.y -= bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
move()
delay.fire()
clock.tick(60)
re_draw()
pygame.quit()
I recommend to use a timer event. Use pygame.time.set_timer() to repeatedly create an event on the event queue.
Use a pygame.sprite.Group and derive Enemy from pygame.sprite.Sprite to manage multiple enemies. Note it is important to use the attributes .image and .rect in a sprite. e.g.:
class Enemy(pygame.sprite.Sprite):
asteroids = [pygame.image.load('rock0.png'), pygame.image.load('rock1.png'), pygame.image.load('rock2.png'),
pygame.image.load('rock3.png'), pygame.image.load('rock4.png')]
def __init__(self, y, width, height):
super().__init__()
self.width = width
self.height = height
self.vel = 1.5
x = random.randrange(screen_width - self.width * 2)
self.image = random.choice(self.asteroids)
self.rect = self.image.get_rect(center = (x, y))
def move(self):
self.rect.y += self.vel
enemies = pygame.sprite.Group()
my_event_id = pygame.USEREVENT + 1
pygame.time.set_timer(my_event_id, 2000) # 2000 milliseconds = 2 seconds
run = True
while run:
last = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == my_event_id:
# spawn new enemy
enemies.add(Enemy(10, 64, 64))
# [...]
for e in enemies:
e.move()
# [...]
enemies.draw(win)
If you make Projectile a pygame.sprite.Sprite, then you can use and bullets a pygame.sprite.Group, then you can use pygame.sprite.spritecollide() or pygame.sprite.groupcollide() to find hits and kill the enemies. e.g.:
class Projectile(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect(center = (x, y))
self.vel = 5
def move(self):
self.rect.y -= self.vel
bullets = pygame.sprite.Group()
def spawn_bullet():
if keys[pygame.K_SPACE]:
bullets.add(Projectile((man.x + man.width // 2), (man.y - 7), 3, 7, (255, 0, 0)))
def re_draw():
win.fill((0, 0, 0))
enemies.draw(win)
man.draw(win)
bullets.draw(win)
pygame.display.update()
while run:
# [...]
for e in enemies:
e.move()
if e.rect.y > 500:
e.kill()
for b in bullets:
b.move()
if 0 > b.rect.y or b.rect.y > 500:
b.kill()
pygame.sprite.groupcollide(bullets, enemies, True, True)
Full code:
import pygame
import random
pygame.init()
screen_width = 500
screen_height = 500
win = pygame.display.set_mode((screen_width, screen_height))
walk_left = [pygame.image.load('sprite_5.png'), pygame.image.load('sprite_6.png')]
walk_right = [pygame.image.load('sprite_3.png'), pygame.image.load('sprite_4.png')]
standing = [pygame.image.load('sprite_0.png'), pygame.image.load('sprite_1.png'), pygame.image.load('sprite_2.png')]
class Player:
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 4
self.left = False
self.right = False
self.standing = True
self.walk_count = 0
self.hitbox = (self.x + 2, self.y + 26, 123, 45)
def draw(self, win):
if self.walk_count + 1 >= 12:
self.walk_count = 0
if not self.standing:
if self.left:
win.blit(walk_left[self.walk_count // 6], (self.x, self.y))
self.walk_count += 1
elif self.right:
win.blit(walk_right[self.walk_count // 6], (self.x, self.y))
self.walk_count += 1
else:
win.blit(standing[self.walk_count // 4], (self.x, self.y))
self.walk_count += 1
self.hitbox = (self.x + 2, self.y + 26, 123, 45)
pygame.draw.rect(win, (255, 0, 0), self.hitbox, 2)
def move():
if keys[pygame.K_LEFT] and man.x > man.vel or keys[pygame.K_a] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.left = False
man.right = True
man.standing = False
else:
man.standing = True
class Enemy(pygame.sprite.Sprite):
asteroids = [pygame.image.load('rock0.png'), pygame.image.load('rock1.png'), pygame.image.load('rock2.png'),
pygame.image.load('rock3.png'), pygame.image.load('rock4.png')]
def __init__(self, y, width, height):
super().__init__()
self.width = width
self.height = height
self.vel = 1.5
x = random.randrange(screen_width - self.width * 2)
self.image = random.choice(self.asteroids)
self.rect = self.image.get_rect(topleft = (x, y))
def move(self):
self.rect.y += self.vel
class Projectile(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect(topleft = (x, y))
self.vel = 5
def move(self):
self.rect.y -= self.vel
my_event_id = pygame.USEREVENT + 1
pygame.time.set_timer(my_event_id, 2000) # 2000 milliseconds = 2 seconds
class Unit:
def __init__(self):
self.last = pygame.time.get_ticks()
self.cooldown = 200
def fire(self):
now = pygame.time.get_ticks()
if now - self.last >= self.cooldown:
self.last = now
spawn_bullet()
def spawn_bullet():
if keys[pygame.K_SPACE]:
bullets.add(Projectile((man.x + man.width // 2), (man.y - 7), 3, 7, (255, 0, 0)))
def re_draw():
win.fill((0, 0, 0))
enemies.draw(win)
man.draw(win)
bullets.draw(win)
pygame.display.update()
delay = Unit()
man = Player(186, 400, 128, 128)
bullets = pygame.sprite.Group()
enemies = pygame.sprite.Group()
run = True
clock = pygame.time.Clock()
while run:
last = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == my_event_id:
# spawn new enemy
enemies.add(Enemy(10, 64, 64))
for e in enemies:
e.move()
if e.rect.y > 500:
e.kill()
for b in bullets:
b.move()
if 0 > b.rect.y or b.rect.y > 500:
b.kill()
pygame.sprite.groupcollide(bullets, enemies, True, True)
keys = pygame.key.get_pressed()
move()
delay.fire()
clock.tick(60)
re_draw()
pygame.quit()
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 finally figured out how to add body parts to my snake, but they add on in an unusual way. I have been struggling on this for a while, and finally made it so they append. But they don't do it correctly. It seems like they append 1 pixel behind instead of a full bodies length. Does anyone know why?
# Constants
WIN_WIDTH = 500
WIN_HEIGHT = 600
HALF_WIN_WIDTH = WIN_WIDTH / 2
HALF_WIN_HEIGHT = WIN_HEIGHT / 2
FPS = 10
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
DARK_GREEN = (0, 100, 0)
YELLOW = (255, 255, 0)
# Variables
screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pygame.display.set_caption("Snake")
clock = pygame.time.Clock()
running = True
class Text:
def __init__(self, x, y, size, font, color, text):
self.x = x
self.y = y
self.size = size
self.font = font
self.color = color
self.text = text
def draw(self):
self.my_font = pygame.font.SysFont(self.font, self.size)
self.text_surface = self.my_font.render(self.text, True, self.color)
screen.blit(self.text_surface, (self.x, self.y))
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 25
self.height = 25
def draw(self):
self.rect = (self.x, self.y, self.width, self.height)
pygame.draw.rect(screen, BLUE, self.rect)
def events(self):
pass
def update(self):
pass
class Body:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 25
self.height = 25
def draw(self):
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
pygame.draw.rect(screen, YELLOW, self.rect)
# Snake class
class Snake:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 25
self.height = 25
self.direction = 1
self.kill = False
self.collide = False
self.speed = 3
self.score = 0
self.bodies = []
def draw(self):
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
pygame.draw.rect(screen, BLACK, self.rect)
def events(self):
# change direction on key press
self.keys = pygame.key.get_pressed()
if self.keys[pygame.K_UP] and self.direction != 3:
self.direction = 1
if self.keys[pygame.K_DOWN] and self.direction != 1:
self.direction = 3
if self.keys[pygame.K_LEFT] and self.direction != 2:
self.direction = 4
if self.keys[pygame.K_RIGHT] and self.direction != 4:
self.direction = 2
if self.rect.colliderect(food.rect):
self.speed += 0.5
food.x = random.randint(0, WIN_WIDTH)
food.y = random.randint(0, WIN_HEIGHT)
self.score += 5
self.colliide = False
self.bodies.append(Body(0, 0))
# Move the end bodies first in reverse order
for i in range(len(self.bodies)-1, 0, -1):
x = snake.bodies[i-1].x
y = snake.bodies[i-1].y
snake.bodies[i].x = x
snake.bodies[i].y = y
snake.bodies[i].draw()
# Move body 0 to where the head is
if len(snake.bodies) > 0:
x = snake.x
y = snake.y
snake.bodies[0].x = x
snake.bodies[0].y = y
snake.bodies[0].draw()
def update(self):
# move
if self.direction == 1:
self.y -= self.speed
if self.direction == 2:
self.x += self.speed
if self.direction == 3:
self.y += self.speed
if self.direction == 4:
self.x -= self.speed
# if on edge of screen
if self.rect.right > WIN_WIDTH:
self.kill = True
if self.x < 0:
self.kill = True
if self.y < 0:
self.kill = True
if self.rect.bottom > WIN_HEIGHT:
self.kill = True
# Create the snake object
snake = Snake(HALF_WIN_WIDTH, HALF_WIN_HEIGHT)
food = Food(random.randint(0, WIN_WIDTH), random.randint(0, WIN_HEIGHT))
# Main Loop
while running:
score_text = Text(220, 5, 40, 'arial', WHITE, f'Score: {snake.score}')
# Draw
screen.fill(DARK_GREEN)
snake.draw()
food.draw()
score_text.draw()
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if snake.kill:
running = False
snake.events()
# Update
snake.update()
food.update()
clock.tick(60)
pygame.display.update()
Thank you very much!
You have to track the positions which have been met by the snake. Add a list attribute self.position to the class Snake:
class Snake:
def __init__(self, x, y):
# [...]
self.positions = [(self.x, self.y)]
# [...]
Add the new position to the list when the snake moves:
class Snake:
# [...]
def update(self):
# move
if self.direction == 1:
self.y -= self.speed
if self.direction == 2:
self.x += self.speed
if self.direction == 3:
self.y += self.speed
if self.direction == 4:
self.x -= self.speed
# add ne position
if self.x != self.positions[0][0] or self.y != self.positions[0][1]:
self.positions.insert(0, (self.x, self.y))
Update the x and y coordinate of the body along the stored positions in events. Define a distance between the parts of the body (e.g. 35). And use a method getPos to get the position of a part, by its index:
class Snake:
# [...]
def events(self):
# change direction on key press
self.keys = pygame.key.get_pressed()
if self.keys[pygame.K_UP] and self.direction != 3:
self.direction = 1
if self.keys[pygame.K_DOWN] and self.direction != 1:
self.direction = 3
if self.keys[pygame.K_LEFT] and self.direction != 2:
self.direction = 4
if self.keys[pygame.K_RIGHT] and self.direction != 4:
self.direction = 2
if self.rect.colliderect(food.rect):
self.speed += 0.5
food.x = random.randint(100, WIN_WIDTH - 125)
food.y = random.randint(150, WIN_HEIGHT - 175)
self.score += 5
self.colliide = False
self.bodies.append(Body(0, 0))
# Move the end bodies first in reverse order
for i in range(len(self.bodies)):
pos = self.getPos(i+1, 35, i == len(self.bodies)-1)
snake.bodies[i].x = pos[0]
snake.bodies[i].y = pos[1]
snake.bodies[i].draw()
The arguments to method getPos are the index of the body part, the distance between the parts and delToEnd. delToEnd becomes true, when the last part of the body is get and indicates, that the positions at the end of the list, which are "behind" the last part of the snake can be deleted:
class Snake:
# [...]
def getPos(self, i, dist, delToEnd):
lenToI = i * dist
lenAct = 0
px, py = self.positions[-1]
for j in range(len(self.positions)-1):
px, py = self.positions[j]
pnx, pny = self.positions[j+1]
delta = math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
lenAct += delta
if lenAct >= lenToI:
w = (lenAct - lenToI) / delta
px = pnx - (pnx-px) * w
py = pny - (pny-py) * w
if delToEnd:
del self.positions[j:]
break
return (round(px), round(py))