drop several cubes in pygame - python

I started Python last week and I'm trying to create a game to learn. There are 4 moving circles in the screen, moving from the middle of the screen to the 4 edges. I got this part I think.
Now I'm trying to drop cubes from the 4 edges to the middle. The goal is to stop those cubes from reaching the middle of the screen, by pressing keyboard arrows. If one cube reaches the center, it's game over.
I managed to drop 4 cubes to the middle at the same time, but the idea is to drop randomly a few cubes.
Any idea how I should proceed to create this ? or what should I learn to make this happen ?
here's my work so far:
# ---------- Packages ----------
import pygame, random
pygame.init()
# ---------- Constants ----------
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
FPS = 60
SPEED = 1
SPEED_ENEMIES = 1
CIRCLE_RADIUS = 50
ENNEMY_SIZE = 40
red = (255,000,000)
blue = (000,000,255)
yellow = (255,255,000)
green = (000,128,000)
pink = (255,192,203)
black = (000,000,000)
transparent = (0, 0, 0, 0)
# ---------- Classes ----------
class Ennemies:
def __init__(self, x, y, size=ENNEMY_SIZE, thick=5, color=blue, speed=SPEED_ENEMIES):
self.rect = pygame.Rect(0, 0, 2*size, 2*size)
self.rect.centerx = x
self.rect.centery = y
self.size = size
self.thick = thick
self.color = color
self.speed = speed
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.rect(screen, self.color,(self.rect.centerx,self.rect.centery,self.size,self.size))
def move_up(self):
self.rect.y -= self.speed
if self.rect.centery < int(SCREEN_HEIGHT/2) - self.size/2:
self.color = transparent
def move_down(self):
self.rect.y += self.speed
if self.rect.centery > int(SCREEN_HEIGHT/2) - self.size/2:
self.color = transparent
def move_left(self):
self.rect.x -= self.speed
if self.rect.centerx < int(SCREEN_WIDTH/2) - self.size/2:
self.color = transparent
def move_right(self):
self.rect.x += self.speed
if self.rect.centerx > int(SCREEN_WIDTH/2) - self.size/2:
self.color = transparent
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=5, color=blue, speed=SPEED):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing_top(self):
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
if self.rect.bottom > int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
def swing_bot(self):
self.rect.y -= self.speed
if self.rect.top < int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
if self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
def swing_left(self):
self.rect.x -= self.speed
if self.rect.right > int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
if self.rect.left <= 0 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
def swing_right(self):
self.rect.x -= self.speed
if self.rect.left < int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.rect.right >= SCREEN_WIDTH and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
# ---------- Main ----------
def main():
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
enemyTop = [
Ennemies(int(SCREEN_WIDTH/2)-ENNEMY_SIZE/2, 0, color=yellow)
]
enemyBot = [
Ennemies(int(SCREEN_WIDTH/2)-ENNEMY_SIZE/2, SCREEN_HEIGHT-ENNEMY_SIZE, color=green)
]
enemyLeft = [
Ennemies(0, int(SCREEN_HEIGHT/2)-ENNEMY_SIZE/2, color=blue)
]
enemyRight = [
Ennemies(SCREEN_WIDTH-ENNEMY_SIZE, int(SCREEN_HEIGHT/2)-ENNEMY_SIZE/2, color = red)
]
circleTop = [
Circle(screen_rect.centerx, screen_rect.centery - 2*CIRCLE_RADIUS)
]
circleBot = [
Circle(screen_rect.centerx, screen_rect.centery + 2*CIRCLE_RADIUS)
]
circleRight = [
Circle(screen_rect.centerx + 2*CIRCLE_RADIUS, screen_rect.centery)
]
circleLeft = [
Circle(screen_rect.centerx - 2*CIRCLE_RADIUS, screen_rect.centery)
]
clock = pygame.time.Clock()
game_over = False
while not game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
for item in enemyTop:
item.move_down()
for item in enemyBot:
item.move_up()
for item in enemyLeft:
item.move_right()
for item in enemyRight:
item.move_left()
for item in circleTop:
item.swing_top()
for item in circleBot:
item.swing_bot()
for item in circleLeft:
item.swing_left()
for item in circleRight:
item.swing_right()
screen.fill(black)
for item in enemyTop:
item.draw(screen)
for item in enemyBot:
item.draw(screen)
for item in enemyLeft:
item.draw(screen)
for item in enemyRight:
item.draw(screen)
for item in circleTop:
item.draw(screen)
for item in circleBot:
item.draw(screen)
for item in circleLeft:
item.draw(screen)
for item in circleRight:
item.draw(screen)
pygame.display.update()
clock.tick(FPS)
main()
pygame.quit()
Thanks

I think the answer here is to modify your Enemies object so that it works better as a more generic object.
To this end I modified the class so that a "cube" starts at a random position at the side, and always heads into the middle (I'm, not sure if this is exactly what you planned, but it's my interpretation of your game description).
This is achieved by calculating a direction vector (since it could now be a diagonal line), and then adding this amount (times speed) to the location each "tick". One problem this creates, is that if the component of the direction is very small - say 0.1 pixels/tick, then the addition of this amount is always rounded away to nothing. So the class keeps the location as a floating point number, and only converts back to integer when the Rect is updated.
The code now keeps a list of Enemies objects. This makes it easy to iterate through this list, updating, drawing and checking positions. To add more enemies, simply add extras to this list - perhaps as levels get harder.
The next change was for the Enemy to start in a random position if the supplied co-ordinates are (0,0). This allows the code to re-set the old enemies to a new random starting position, or add new ones.
# ---------- Packages ----------
import pygame, random
pygame.init()
# ---------- Constants ----------
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
FPS = 60
SPEED = 1
SPEED_ENEMIES = 1
CIRCLE_RADIUS = 50
ENEMY_SIZE = 40
red = (255,000,000)
blue = (000,000,255)
yellow = (255,255,000)
green = (000,128,000)
pink = (255,192,203)
black = (000,000,000)
transparent = (0, 0, 0, 0)
# ---------- Classes ----------
class Enemies:
def __init__(self, x, y, size=ENEMY_SIZE, thick=5, color=blue, speed=SPEED_ENEMIES):
self.rect = pygame.Rect(0, 0, size, size)
# if x and y are zero, choose a random starting point
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.size = size
self.thick = thick
self.color = color
self.speed = speed
self.calcDirection()
def calcDirection( self ):
""" Work out the direction vector to (0,0) """
self.x_float = 1.0 * self.rect.centerx # keep these to stop rounding errors
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
#print("Velocity Vector = "+str(self.velocity_vector) )
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw(self, screen):
pygame.draw.rect(screen, self.color, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
""" Pick a random starting point """
self.rect.centerx = random.randint( 0, SCREEN_WIDTH )
self.rect.centery = random.randint( 0, SCREEN_HEIGHT )
side = random.randint( 0, 4 )
if ( side == 0 ): # top
self.rect.centery = SCREEN_HEIGHT
elif ( side == 1 ): # bottom
self.rect.centery = 0
elif ( side == 2 ): # left
self.rect.centerx = 0
else: # right
self.rect.centerx = SCREEN_WIDTH
self.calcDirection()
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=5, color=blue, speed=SPEED):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing_top(self):
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
if self.rect.bottom > int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
def swing_bot(self):
self.rect.y -= self.speed
if self.rect.top < int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
if self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
def swing_left(self):
self.rect.x -= self.speed
if self.rect.right > int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
if self.rect.left <= 0 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
def swing_right(self):
self.rect.x -= self.speed
if self.rect.left < int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.rect.right >= SCREEN_WIDTH and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
# ---------- Main ----------
def main():
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
# Start with 4 enemies
all_enemies = [
Enemies(random.randint(1,SCREEN_WIDTH), 1, color=yellow), # bottom
Enemies(random.randint(1,SCREEN_WIDTH), SCREEN_HEIGHT-ENEMY_SIZE, color=green), # top
Enemies(1, random.randint(1,SCREEN_HEIGHT), color=blue), # left
Enemies(SCREEN_WIDTH-ENEMY_SIZE, random.randint(1,SCREEN_HEIGHT), color = red) # right
]
circleTop = [
Circle(screen_rect.centerx, screen_rect.centery - 2*CIRCLE_RADIUS)
]
circleBot = [
Circle(screen_rect.centerx, screen_rect.centery + 2*CIRCLE_RADIUS)
]
circleRight = [
Circle(screen_rect.centerx + 2*CIRCLE_RADIUS, screen_rect.centery)
]
circleLeft = [
Circle(screen_rect.centerx - 2*CIRCLE_RADIUS, screen_rect.centery)
]
clock = pygame.time.Clock()
game_over = False
while not game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
for item in circleTop:
item.swing_top()
for item in circleBot:
item.swing_bot()
for item in circleLeft:
item.swing_left()
for item in circleRight:
item.swing_right()
screen.fill(black)
for e in all_enemies:
e.update()
# Did the enemy make it to the centre?
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ):
# Game over?
print("Game Over?")
e.randomise()
e.draw( screen )
for item in circleTop:
item.draw(screen)
for item in circleBot:
item.draw(screen)
for item in circleLeft:
item.draw(screen)
for item in circleRight:
item.draw(screen)
pygame.display.update()
clock.tick(FPS)
main()
pygame.quit()

Related

How do i spawn enemies from every side of the screen pygame? [duplicate]

This question already has an answer here:
How do I make my player rotate towards mouse position?
(1 answer)
Closed 2 years ago.
I've been working on some game code for school and was working through some tutorials and figured out how to make the player sprite tracks the mouse but now I'm having trouble figuring out how to spawn enemies from all sides of the screen instead of just the top. (To clarify code is in two parts/files, also completely unrelated). I was also wondering, how I could maybe have the mobs/NPCs chase and rotate towards the player?
import random
import math
import pygame as py
from PlayerSprite import *
WIDTH = 800
HEIGHT = 600
FPS = 60
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# initialize pygame and create window
py.init()
py.mixer.init()
screen = py.display.set_mode((WIDTH, HEIGHT))
py.display.set_caption("Dimensional Drifter")
clock = py.time.Clock()
all_sprites = py.sprite.Group()
NPCs = py.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(8):
n = NPC()
all_sprites.add(n)
NPC.add(n)
# Game loop
running = True
while running:
# keep loop running at the right speed
clock.tick(FPS)
for event in py.event.get():
# check for closing window
if event.type == py.QUIT:
running = False
# Update
all_sprites.update()
#
mouse_x, mouse_y = py.mouse.get_pos()
player.rotate(mouse_x, mouse_y)
# render
screen.fill(BLACK)
all_sprites.draw(screen)
# flip the display
py.display.flip()
py.quit()
import pygame as py
import math
import random
WIDTH = 800
HEIGHT = 600
FPS = 60
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
class Player(py.sprite.Sprite):
def __init__(self):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((50, 50), py.SRCALPHA)
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT / 2
self.Yspeed = 0
self.rotatableimage = self.image
def update(self):
self.Xspeed = 0
self.Yspeed = 0
# line below allow for key press to equate to move of sprite
keypreesed = py.key.get_pressed()
if keypreesed[py.K_a]:
self.Xspeed = - 11
if keypreesed[py.K_d]:
self.Xspeed = 11
if keypreesed[py.K_w]:
self.Yspeed = - 11
if keypreesed[py.K_s]:
self.Yspeed = 11
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# line below allow the sprite to wrap around the screen
if self.rect.left > WIDTH:
self.rect.right = 0
if self.rect.right < 0:
self.rect.left = WIDTH
if self.rect.top > HEIGHT:
self.rect.top = 0
if self.rect.bottom < 0:
self.rect.bottom = HEIGHT
def rotate(self, mouse_x, mouse_y):
rel_x = mouse_x - self.rect.x
rel_y = mouse_y - self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = py.transform.rotate(self.rotatableimage, int(angle))
self.rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery))
return
class NPC(py.sprite.Sprite):
def __init__(self):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((30, 30))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Yspeed = random.randrange(1, 8)
def update(self):
self.rect.y += self.Yspeed
if self.rect.top > HEIGHT + 10:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Yspeed = random.randrange(1, 8)
Thanks in advance.
You have to add a random direction and a separate speed (self.Xspeed, self.Yspeed) for the x and y direction:
self.direction = random.randrange(4)
and to set self.rect.x, self.rect.y, self.Xspeed and self.Yspeed dependent on the random direction:
if self.direction == 0:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Xspeed = 0
self.Yspeed = random.randrange(1, 8)
elif self.direction == 1:
# [...]
elif self.direction == 2:
# [...]
elif self.direction == 3:
# [...]
Create a method spawn, which spawns a enemy with a random direction and a random speed and call it in the constructor of NPC:
class NPC(py.sprite.Sprite):
def __init__(self):
# [...]
self.spawn()
def spawn(self):
self.direction = random.randrange(4)
if self.direction == 0:
# [...]
In update the speed has to be and to the x and y position separately:
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
Furthermore, if the object is out of the window has to be evaluated dependent on the direction. If the object is out of the window, the call spawn() again, thus the object gets a new random direction, position and speed:
if self.direction == 0:
if self.rect.top > HEIGHT + 10:
self.spawn()
elif self.direction == 1:
if self.rect.bottom < -10:
self.spawn()
elif self.direction == 2:
if self.rect.left > WIDTH + 10:
self.spawn()
elif self.direction == 3:
if self.rect.right < -10:
self.spawn()
To rotate the image you have to create surface with per pixel alpha (convert_alpha()) and to keep the original surface:
self.image = py.Surface((30, 30)).convert_alpha()
self.image.fill(RED)
self.originalimage = self.image
Compute the angle from the enemy to the player and rotate the image by pygame.transform.rotate():
(See also How to rotate a triangle to a certain angle in pygame?)
dir_x, dir_y = player.rect.x - self.rect.x, player.rect.y - self.rect.y
self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
self.image = py.transform.rotate(self.originalimage, self.rot)
Class NPC:
class NPC(py.sprite.Sprite):
def __init__(self, player):
py.sprite.Sprite.__init__(self)
self.player = player
self.image = py.Surface((30, 30)).convert_alpha()
self.image.fill(RED)
self.originalimage = self.image
self.rect = self.image.get_rect()
self.spawn()
def spawn(self):
self.direction = random.randrange(4)
if self.direction == 0:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Xspeed = 0
self.Yspeed = random.randrange(1, 8)
elif self.direction == 1:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(HEIGHT, HEIGHT+60)
self.Xspeed = 0
self.Yspeed = -random.randrange(1, 8)
elif self.direction == 2:
self.rect.x = random.randrange(-100, -40)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = random.randrange(1, 8)
self.Yspeed = 0
elif self.direction == 3:
self.rect.x = random.randrange(WIDTH, WIDTH+60)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = -random.randrange(1, 8)
self.Yspeed = 0
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
dir_x, dir_y = self.player.rect.x - self.rect.x, self.player.rect.y - self.rect.y
self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
self.image = py.transform.rotate(self.originalimage, self.rot)
if self.direction == 0:
if self.rect.top > HEIGHT + 10:
self.spawn()
elif self.direction == 1:
if self.rect.bottom < -10:
self.spawn()
elif self.direction == 2:
if self.rect.left > WIDTH + 10:
self.spawn()
elif self.direction == 3:
if self.rect.right < -10:
self.spawn()
player = Player()
all_sprites.add(player)
for i in range(8):
n = NPC(player)
all_sprites.add(n)
NPC.add(n)

Make bullets fire off in the direction the player is facing

I was just getting some help to figure out how to get my player fire bullets when I realized that they only go (kinda expected this but however as only had y value for movement). I don't know how I'll make the bullets fire off in the direction the player is facing.
I have some idea of what to but I just don't know how to do it... I thought I could somehow use the cursor and player tracking that's in this game for the visuals but I don't know how to make that a one-time thing instead of a constant. For diagonal movement of the bullet, I have no clue.
Code below (split into two parts/file Main.py and PlayerSprite.py):
Main:
py.init()
py.mixer.init()
screen = py.display.set_mode((WIDTH, HEIGHT))
py.display.set_caption("Dimensional Drifter")
clock = py.time.Clock()
all_sprites = py.sprite.Group()
NPCs = py.sprite.Group()
bullets = py.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(14):
n = NPC(player)
all_sprites.add(n)
NPCs.add(n)
# Game loop
running = True
while running:
# keep loop running at the right speed
clock.tick(FPS)
for event in py.event.get():
# check for closing window
if event.type == py.QUIT:
running = False
elif event.type == py.KEYDOWN:
if event.key == py.K_SPACE:
New_bullet = player.Shoot()
all_sprites.add(New_bullet)
bullets.add(New_bullet)
# Update
all_sprites.update()
# # check if there a collision between the bullet and NPC
hits = py.sprite.groupcollide(NPCs, bullets, True, True)
# check if there a collision between the player and NPC
hits = py.sprite.spritecollide(player, NPCs, True)
if hits:
running = False
# updates the position of of mouse and rotates it towards the mouse position
mouse_x, mouse_y = py.mouse.get_pos()
player.rotate(mouse_x, mouse_y)
# render
screen.fill(BLACK)
all_sprites.draw(screen)
# flip the display
py.display.flip()
py.quit()
PlayerSprite
import pygame as py
import math
import random
WIDTH = 800
HEIGHT = 600
FPS = 60
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
class Player(py.sprite.Sprite):
def __init__(self):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((40, 40), py.SRCALPHA)
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT / 2
self.Yspeed = 0
self.rotatableimage = self.image
def update(self):
self.Xspeed = 0
self.Yspeed = 0
# line below allow for key press to equate to move of sprite
keypreesed = py.key.get_pressed()
if keypreesed[py.K_a]:
self.Xspeed = - 11
if keypreesed[py.K_d]:
self.Xspeed = 11
if keypreesed[py.K_w]:
self.Yspeed = - 11
if keypreesed[py.K_s]:
self.Yspeed = 11
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# line below allow the sprite to wrap around the screen
if self.rect.left > WIDTH:
self.rect.right = 0
if self.rect.right < 0:
self.rect.left = WIDTH
if self.rect.top > HEIGHT:
self.rect.top = 0
if self.rect.bottom < 0:
self.rect.bottom = HEIGHT
def rotate(self, mouse_x, mouse_y):
rel_x = mouse_x - self.rect.x
rel_y = mouse_y - self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = py.transform.rotate(self.rotatableimage, int(angle))
self.rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery))
return
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top)
class NPC(py.sprite.Sprite):
def __init__(self, player):
py.sprite.Sprite.__init__(self)
self.player = player
self.image = py.Surface((30, 30)).convert_alpha()
self.image.fill(RED)
self.originalimage = self.image
self.rect = self.image.get_rect()
self.spawn()
# allows of spawning from all four side of the screen and set the x, y speed and spawn position
def spawn(self):
self.direction = random.randrange(4)
if self.direction == 0:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = random.randrange(4, 8)
elif self.direction == 1:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(HEIGHT, HEIGHT + 60)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = -random.randrange(4, 8)
elif self.direction == 2:
self.rect.x = random.randrange(-100, -40)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
elif self.direction == 3:
self.rect.x = random.randrange(WIDTH, WIDTH + 60)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = -random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# makes it so that NPC point to wards the player as it passes from side to side
dir_x, dir_y = self.player.rect.x - self.rect.x, self.player.rect.y - self.rect.y
self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
self.image = py.transform.rotate(self.originalimage, self.rot)
# Respawns the NPC when they hit an side
if self.direction == 0:
if self.rect.top > HEIGHT + 10:
self.spawn()
elif self.direction == 1:
if self.rect.bottom < -10:
self.spawn()
elif self.direction == 2:
if self.rect.left > WIDTH + 10:
self.spawn()
elif self.direction == 3:
if self.rect.right < -10:
self.spawn()
class Bullet(py.sprite.Sprite):
def __init__(self, x, y):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Yspeed = -10
def update(self):
self.rect.y += self.Yspeed
# kill if moved of screen
if self.rect.bottom > HEIGHT or self.rect.top < 0:
self.kill()
if self.rect.right > WIDTH or self.rect.left < 0:
self.kill()
Add 2 attributes self.lastX and self.lastY to the class Player and change the attributes when the player changes the direction:
class Player(py.sprite.Sprite):
def __init__(self):
# [...]
self.lastX = 0
self.lastY = -10
def update(self):
# [...]
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
if self.Xspeed != 0 or self.Yspeed != 0:
self.lastX = self.Xspeed
self.lastY = self.Yspeed
Add an argument Xspeed ans Yspeed to the class Bullet
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Xspeed, Yspeed):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Xspeed = Xspeed
self.Yspeed = Yspeed
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# [...]
Set the attributes when the bullet spawns
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.centery, self.lastX, self.lastY)
Alternatively it is also possible to set the speed dependent on the direction to the mouse cursor.
Get the position of the player and the mouse cursor and compute the x and y distance (Vector ):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx = mpos[0] - pos[0]
vy = mpos[1] - pos[1]
If the mouse position and the bullet position are equal, that does not make any sense, thus the bullet is skipped
if vx == 0 and vy == 0:
return None
Of course this vector is far to long, if you would use it for the direction (Xspeed, Yspeed) directly, then the bullet would step to the mouse cursor in one turn.
In the following I use pygame.math.Vector2, because it provides the handy method scale_to_length, that scales a vector to a specified Euclidean length:
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
Now the x and y component of the vector contain the x and y component of the speed. Since the components are floating point values, they are round to integral values:
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Method Shoot:
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx, vy = mpos[0] - pos[0], mpos[1] - pos[1]
if vx == 0 and vy == 0:
return None
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Note, if you set the bullet dependent on the direction to the mouse cursor, then it may be useful to spawn the bullet by a mouse click:
while running:
# [...]
for event in py.event.get():
if event.type == py.QUIT:
# [...]
elif event.type == py.MOUSEBUTTONDOWN:
if event.button == 1:
New_bullet = player.Shoot()
if New_bullet:
all_sprites.add(New_bullet)
bullets.add(New_bullet)
You can use pygames Vector2 to move in any direction. You calculate the angle of the player you can use that.
class Player(py.sprite.Sprite):
def __init__(self):
...
self.angle = 0
def rotate(self, mouse_x, mouse_y):
...
self.angle = -angle #make negative otherwise it will be going away from mouse
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top, py.math.Vector2(1,0).rotate(self.angle))
then in your bullet class, get the direction and add to its position
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Dir):
...
self.Dir = Dir
def update(self):
self.rect.y += self.Dir[1] * self.speed
self.rect.x += self.Dir[0] * self.speed
...

Pygame - drop enemies randomly in time [duplicate]

This question already has an answer here:
How can one continuously generate and track several random objects with a time delay in pygame? [duplicate]
(1 answer)
Closed 2 years ago.
I'm trying to design my first game with Python and Pygame to improve my coding skills.
Basically, the player has 4 circles and the goal is to prevent the Cube (or enemies) from reaching the middle of the screen, by killing them with the keyboard arrows.
The game works fine : circles move perfectly, the cubes are going faster as the levels go up, the scoring system is ok, etc...
The last step I can't figure out is to make the Cubes drop a bit more randomly, otherwise it's too easy. Currently, when a cube is killed, it reappear instantly on one of the 4 edges of the screen. I figured out how to position the cube randomly, but the timing doesn't work.
I tried Python sleep, but it freezes the screen. I tried adding a line to specify a delay with "delay = random.random()", and adding "delay <0.1" if condition in an if statement. My last idea was to use a time counter, equals to clock.tick(), and adding a condition : timecounter >3000 in an if statement. All these attempts don't work for me.
Any idea ? Here's my code so far:
# ---------- Packages and Inits ----------
import pygame, random, math, sys
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
FPS = 60
SPEED = 1
CIRCLE_RADIUS = 50
ENEMY_SIZE = 40
# Colors
RED = (255,000,000)
BLUE = (000,000,255)
YELLOW = (255,255,000)
GREEN = (000,128,000)
BLACK = (000,000,000)
# ---------- Classes ----------
class Enemies:
def __init__( self, x, y, size=ENEMY_SIZE, thick=5, color=BLUE, speed=1, position="top"):
self.rect = pygame.Rect(0, 0, size, size)
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.size = size
self.thick = thick
self.color = color
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw( self, screen):
pygame.draw.rect(screen, self.color, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.color = GREEN
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.color = YELLOW
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 0
self.color = BLUE
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH
self.color = RED
self.position= "right"
self.calcDirection()
def set_speed( self, score):
if score < 25 : self.speed = 0.5
elif score < 50 : self.speed = 0.75
elif score < 100: self.speed = 1
elif score < 250: self.speed = 1.25
elif score < 500: self.speed = 1.5
else: self.speed = 2
return self.speed
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=5, color=BLUE, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom > int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top < int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right > int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 0 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left < int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
def isCollision(self, enemyX, enemyY, circleX, circleY):
distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2)))
if distance < 65:
return True
else:
return False
# ---------- Other Functions ----------
def set_number_of_enemies(score):
if score < 25 : number_of_enemies = 3
elif score < 50 : number_of_enemies = 4
elif score < 100: number_of_enemies = 5
elif score < 250: number_of_enemies = 6
elif score < 500: number_of_enemies = 7
else: number_of_enemies = 8
return number_of_enemies
# ---------- Main ----------
def main():
# Settings
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
myFont = pygame.font.SysFont("monospace", 25)
clock = pygame.time.Clock()
game_over = False
# Variables
lifes = 5
score = 0
number_of_enemies = 3
# We create an empty list of enemies, as we want them to drop randomly
all_enemies = []
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.fill(BLACK) # This has to be inside the while not game_over loop
time_counter = clock.tick()
print(time_counter)
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
# Set number of enemies
number_of_enemies = set_number_of_enemies(score) # Get the number of enemies we want, based on the score
if len(all_enemies) < number_of_enemies and time_counter > 3000: # Add if necessary
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top")) # We add a top enemy, but we randomise it right after
for e in all_enemies:
e.randomise()
time_counter = 0
# Enemies
for e in all_enemies:
e.draw(screen) # Place enemies on the screen
e.set_speed(score) # Set speed difficulty for enemies
e.update() # Move enemies from the edges of the screen towards the center
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated
lifes -=1
e.randomise()
# Scoring and lifepoints systems
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
for c in all_circles:
if event.type == pygame.KEYDOWN:
# LEFT
if event.key == pygame.K_LEFT and c.position == "left":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# RIGHT
if event.key == pygame.K_RIGHT and c.position == "right":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# TOP
if event.key == pygame.K_UP and c.position == "top":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# BOT
if event.key == pygame.K_DOWN and c.position == "bot":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# Game Over condition
if lifes == 0:
game_over = True
# Score / Lifes / Number of Enemies
print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, RED)
screen.blit(print_lifes, (10, SCREEN_HEIGHT-50))
print_score = myFont.render("Score:" + str(round(score)), 1, RED)
screen.blit(print_score, (10, 10))
print_enemies = myFont.render("# of Enemies:" + str(round(number_of_enemies)), 1, RED)
screen.blit(print_enemies, (10, 60))
pygame.display.update()
clock.tick(FPS)
main()
pygame.quit()
I recommend to use a timer event. Use pygame.time.set_timer() to repeatedly create an USEREVENT. e.g.:
milliseconds_delay = 3000
enemy_spawn_event = pygame.USEREVENT + 1
pygame.time.set_timer(enemy_spawn_event, milliseconds_delay)
Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to start at pygame.USEREVENT. In this case pygame.USEREVENT+1 is the event id for the timer event, which spawns the enemies.
Create a new enemy when the event occurs in the event loop:
milliseconds_delay = 3000
enemy_spawn_event = pygame.USEREVENT + 1
pygame.time.set_timer(enemy_spawn_event, milliseconds_delay)
while not game_over:
# [...]
# Scoring and lifepoints systems
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == enemy_spawn_event and len(all_enemies) < number_of_enemies:
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top"))
for e in all_enemies:
e.randomise()
time_counter = 0

How do I center a surface (subsurface) around a rectangle? (scaled sprite hitbox / collision rect)

Currently I have working code which will cycle through a spritesheet, adding each cell/image (9 in total) as a subsurface into a list. As the game updates, I am setting the Player image as the current cell in which the code has indexed by. Meanwhile, I also have a set rectangle which acts as the sprites 'hitbox'/collision rect.
However, setting the subsurface as the new image, I found that the sprite scales from the top-left corner of the collision rect. As the sprite is significantly larger than the collision rect, the collision rect is placed far away from the actual char model/sprite.
I am trying to center the subsurface/sprite image AROUND the collision rect, as opposed from scaling from the top-left corner.
Here is my code:
import pygame as pg
from settings import *
vec = pg.math.Vector2
class Civilian(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.player1group
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pg.Surface((TILESIZE, TILESIZE))
self.rect = self.image.get_rect()
self.vel = vec(0, 0)
self.pos = vec(x , y)
self.move = 0
def animate(self, direction):
if direction == 'right':
self.spritesheet = pg.image.load('walk right.png') # Loading the right directional movement spritesheet into the variable
if direction == 'left':
self.spritesheet = pg.image.load('walk left.png')
if direction == 'up':
self.spritesheet = pg.image.load('walk up.png')
if direction == 'down':
self.spritesheet = pg.image.load('walk down.png')
self.frames = [] # List which will contain each cell of the spritesheet
# Adding the cells to the list #
self.frames.append(self.spritesheet.subsurface(pg.Rect(0, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(61, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(122, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(183, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(244, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(305, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(366, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(427, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(488, 0, 61, 67)).convert_alpha())
# Number of frames/cells
self.frames_number = len(self.frames)
# Current animation frame
self.current_frame = 0
# Frame rectangle
self.frame_rect = self.frames[0].get_rect()
def get_keys(self):
self.vel= vec(0, 0)
keys = pg.key.get_pressed()
if keys[pg.K_a]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
self.vel.x= -PLAYER_SPEED
self.move += 1
self.moving = 'left' # Uses different spritesheet depending on direction
elif keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= PLAYER_SPEED
self.move += 1
self.moving = 'right'
elif keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -PLAYER_SPEED
self.move += 1
self.moving = 'up'
elif keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= PLAYER_SPEED
self.move += 1
self.moving = 'down'
if self.vel.x != 0 and self.vel.y != 0: # Offsetting increased vecocity when moving diagonally (Has both x and y velocity)
self.vel *= 0.7071
def collide_with_player2(self, dir, ifColliding):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.player2group, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
print("collide x")
self.ifColliding = True
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.player2group, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
print("collide y")
self.ifColliding = True
def collide_with_walls(self, dir):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
def update(self):
# frame updates
self.moving = 'idle'
self.animate('down') # Sets the down spritesheet as default
self.get_keys()
if self.moving == 'up':
self.animate(self.moving) # Uses the up-movement spritesheet if char moving upwards
if self.moving == 'down':
self.animate(self.moving) # Same as above, different direction
if self.moving == 'left':
self.animate(self.moving)
if self.moving == 'right':
self.animate(self.moving)
# frame updates
self.ifColliding = False
self.pos += self.vel * self.game.dt
self.rect.x = self.pos.x
self.collide_with_walls('x'), self.collide_with_player2('x', self.ifColliding)
self.rect.y = self.pos.y
self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding)
if self.ifColliding == True:
Thief.health -= COL_DAMAGE
print(Thief.health)
self.current_frame = (self.current_frame + self.move) % self.frames_number
if self.moving == 'idle':
self.current_frame = 0
self.image = self.frames[self.current_frame] # Image of sprite changes as program cycles through the sheet
In short, Id like to center the self.image surface on the self.rect (Collision rect).
[EDIT]
I have attempted to change references to self.rect within the colliion functions (collide_with_player2, collide_with_walls) to self.col_rect in hopes this would work, but found this not to be the case.
As stated I have created the new rectangle i'd like to use for collision so that self.rect is used for the image blitting, and self.col_rect is used for collision. Although inefficient, I would still like to allow for this as a temporary fix to the problem. I am new to pygame, so I was hoping someone could help me in changing the rectangle used in collision from self.rect, to self.col_rect instead.
Again, any feedback would be greatly appreciated!
Updated code:
import pygame as pg
from settings import *
vec = pg.math.Vector2
class Civilian(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.player1group, game.bothplayers
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pg.Surface((61, 67))
self.rect = self.image.get_rect()
self.col_rect = self.rect.inflate(-40, -40)
self.vel = vec(0, 0)
self.pos = vec(x , y)
self.move = 0
def animate(self, direction):
if direction == 'right':
self.spritesheet = pg.image.load('walk right civ.png') # Loading the right directional movement spritesheet into the variable
if direction == 'left':
self.spritesheet = pg.image.load('walk left civ.png')
if direction == 'up':
self.spritesheet = pg.image.load('walk up civ.png')
if direction == 'down':
self.spritesheet = pg.image.load('walk down civ.png')
self.frames = [] # List which will contain each cell of the spritesheet
# Adding the cells to the list #
self.frames.append(self.spritesheet.subsurface(pg.Rect(0, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(61, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(122, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(183, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(244, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(305, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(366, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(427, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(488, 0, 61, 67)).convert_alpha())
# Number of frames/cells
self.frames_number = len(self.frames)
# Current animation frame
self.current_frame = 0
# Frame rectangle
self.frame_rect = self.frames[0].get_rect()
def get_keys(self):
self.vel= vec(0, 0)
keys = pg.key.get_pressed()
if keys[pg.K_a]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
self.vel.x= -PLAYER_SPEED
self.move += 1
self.moving = 'left' # Uses different spritesheet depending on direction
elif keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= PLAYER_SPEED
self.move += 1
self.moving = 'right'
elif keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -PLAYER_SPEED
self.move += 1
self.moving = 'up'
elif keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= PLAYER_SPEED
self.move += 1
self.moving = 'down'
if self.vel.x != 0 and self.vel.y != 0: # Offsetting increased vecocity when moving diagonally (Has both x and y velocity)
self.vel *= 0.7071
def collide_with_player2(self, dir, ifColliding):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.player2group, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
print("collide x")
self.ifColliding = True
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.player2group, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
print("collide y")
self.ifColliding = True
def collide_with_walls(self, dir):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
def update(self):
# frame updates
self.moving = 'idle'
self.animate('down') # Sets the down spritesheet as default
self.get_keys()
if self.moving == 'up':
self.animate(self.moving) # Uses the up-movement spritesheet if char moving upwards
if self.moving == 'down':
self.animate(self.moving) # Same as above, different direction
if self.moving == 'left':
self.animate(self.moving)
if self.moving == 'right':
self.animate(self.moving)
self.ifColliding = False
self.pos += self.vel * self.game.dt
self.rect.x = self.pos.x
self.collide_with_walls('x'), self.collide_with_player2('x', self.ifColliding)
self.col_rect.centerx = self.rect.centerx
self.rect.y = self.pos.y
self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding)
self.col_rect.centery = self.rect.centery
if self.ifColliding == True:
Thief.health -= COL_DAMAGE
print(Thief.health)
self.current_frame = (self.current_frame + self.move) % self.frames_number
if self.moving == 'idle':
self.current_frame = 0
self.image = self.frames[self.current_frame] # Image of sprite changes as program cycles through the sheet
If you want a scaled collision rect/hitbox, you need to give your sprites a second rect (I call it hitbox here). You have to do that because pygame blits the images/surfaces at the topleft coords of the self.rect. So the first rect self.rect serves as the blit position and the self.hitbox is used for the collision detection.
You also need to define a custom callback function for the collision detection that you have to pass to pygame.sprite.spritecollide as the fourth argument.
def collided(sprite, other):
"""Check if the `hitbox` rects of the two sprites collide."""
return sprite.hitbox.colliderect(other.hitbox)
collided_sprites = pg.sprite.spritecollide(player, enemies, False, collided)
Here's a complete example (the self.rects are the green rectangles and the self.hitboxes are the reds):
import pygame as pg
from pygame.math import Vector2
class Entity(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((70, 50))
self.image.fill((0, 80, 180))
self.rect = self.image.get_rect(center=pos)
# A inflated copy of the rect as the hitbox.
self.hitbox = self.rect.inflate(-42, -22)
self.vel = Vector2(0, 0)
self.pos = Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
self.hitbox.center = self.pos # Also update the hitbox coords.
def collided(sprite, other):
"""Check if the hitboxes of the two sprites collide."""
return sprite.hitbox.colliderect(other.hitbox)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
player = Entity((300, 200), all_sprites)
enemies = pg.sprite.Group(
Entity((100, 250), all_sprites),
Entity((400, 300), all_sprites),
)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
player.pos = event.pos
all_sprites.update()
# Pass the custom collided callback function to spritecollide.
collided_sprites = pg.sprite.spritecollide(
player, enemies, False, collided)
for sp in collided_sprites:
print('Collision', sp)
screen.fill((30, 30, 30))
all_sprites.draw(screen)
for sprite in all_sprites:
# Draw rects and hitboxes.
pg.draw.rect(screen, (0, 230, 0), sprite.rect, 2)
pg.draw.rect(screen, (250, 30, 0), sprite.hitbox, 2)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

pygame spritesheet sprite animation

I am trying to make a walking animation for my sprite (4 directional) by using 4 different sprite sheets (Movement up, down, left and right). As well as this, I would like to replace the coloured square with the image/s but am unsure as how I am supposed to do this. Below is the main.py game file:
import pygame as pg
import sys
from settings import *
from os import path
from sprites import *
from tilemap import *
class Spritesheet:
def __init__(self, filename, cols, rows):
self.sheet = pg.image.load(filename)
self.cols = cols #no. of columns in spritesheet
self.rows = rows #no. of rows
self.totalCellCount = cols*rows
self.rect=self.sheet.get_rect()
w = self.cellWidth = self.rect.width / cols
h = self.cellHeight = self.rect.height / rows
hw, hh = self.cellCenter = (w / 2, h / 2)
self.cells = list([(index % cols * w, index // cols * h, w, h) for index in range(self.totalCellCount)])
self.handle = list([
(0, 0), (-hw, 0), (-w, 0),
(0, -hh), (-hw, -hh), (-w, -hh),
(0, -h), (-hw, -h), (-w, -h),])
def draw(self, surface, cellIndex, x, y, handle = 0):
surface.blit(self.sheet, (x + self.handle[handle][0], y + self.handle[handle][1]), self.cells[cellIndex])
CENTER_HANDLE = 4
index = 0
class Game:
def __init__(self):
pg.init()
self.screen=pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption(TITLE)
self.clock = pg.time.Clock()
pg.key.set_repeat(500, 100)
self.load_data()
def load_data(self):
game_folder = path.dirname(__file__)
img_folder = path.join(game_folder, 'img')
self.map= Map(path.join(game_folder, 'map.txt'))
def new(self):
# initialize all variables and do all the setup for a new game
self.all_sprites = pg.sprite.Group()
self.walls = pg.sprite.Group()
self.player1group = pg.sprite.Group()
self.player2group = pg.sprite.Group()
for row, tiles in enumerate(self.map.data):
for col, tile in enumerate(tiles):
if tile == '1':
Wall(self, col, row)
if tile =='P':
self.player = Civilian(self, col, row)
if tile =='T':
self.player2 = Thief(self, col, row)
def run(self):
# game loop - set self.playing = False to end the game
self.playing = True
while self.playing:
self.dt = self.clock.tick(FPS) / 1000
self.events()
self.update()
self.draw()
def quit(self):
pg.quit() #Calls the quit function, game window closes
sys.exit()
def update(self):
# update portion of the game loop
self.all_sprites.update() #All sprite attributes, position etc are updated
def draw_grid(self):
for x in range(0, WIDTH, TILESIZE):
pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
for y in range(0, HEIGHT, TILESIZE):
pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))
def draw(self):
self.screen.fill(BGCOLOR)
self.draw_grid()
self.all_sprites.draw(self.screen)
pg.display.flip()
def events(self):
# catch all events here
for event in pg.event.get():
if event.type == pg.QUIT:
self.quit()
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
self.quit()
def show_start_screen(self):
pass
def show_go_screen(self):
pass
# create the game object
g = Game()
g.show_start_screen()
while True:
g.new()
g.run()
g.show_go_screen()
Here is the sprites.py file, cont. sprite classes
import pygame as pg
from settings import *
vec = pg.math.Vector2
class Civilian(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.player1group
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pg.Surface((TILESIZE, TILESIZE))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.vel = vec(0, 0)
self.pos = vec(x, y) * TILESIZE
def get_keys(self):
self.vel= vec(0, 0)
keys = pg.key.get_pressed()
if keys[pg.K_a]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
self.vel.x= -PLAYER_SPEED
if keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= PLAYER_SPEED
if keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -PLAYER_SPEED
if keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= PLAYER_SPEED
if self.vel.x != 0 and self.vel.y != 0: # Offsetting increased vecocity when moving diagonally (Has both x and y velocity)
self.vel *= 0.7071
def collide_with_player2(self, dir, ifColliding):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.player2group, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
print("collide x")
self.ifColliding = True
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.player2group, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
print("collide y")
self.ifColliding = True
def collide_with_walls(self, dir):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
def update(self):
self.ifColliding = False
self.get_keys()
self.pos += self.vel * self.game.dt
self.rect.x = self.pos.x
self.collide_with_walls('x'), self.collide_with_player2('x', self.ifColliding)
self.rect.y = self.pos.y
self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding)
if self.ifColliding == True:
Thief.health -= COL_DAMAGE
print(Thief.health)
class Thief(pg.sprite.Sprite):
health = 100
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.player2group
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pg.Surface((TILESIZE, TILESIZE))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.vel = vec(0, 0)
self.pos = vec(x, y) * TILESIZE
s = Spritesheet("spritesheet_thief.png", 9, 4)
def get_keys(self):
self.vel = vec(0, 0)
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left)
self.vel.x= -PLAYER_SPEED
elif keys[pg.K_RIGHT]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= PLAYER_SPEED
elif keys[pg.K_UP]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -PLAYER_SPEED
elif keys[pg.K_DOWN]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= PLAYER_SPEED
elif self.vel.x != 0 and self.vel.y != 0: # Offsetting increased vecocity when moving diagonally (Has both x and y velocity)
self.vel *= 0.7071
def collide_with_player1(self, dir, ifColliding):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.player1group, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
print("collide x")
self.ifColliding = True
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.player1group, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
print("collide y")
self.ifColliding = True
def collide_with_walls(self, dir):
if dir == 'x':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.x > 0:
self.pos.x = collides[0].rect.left - self.rect.width
if self.vel.x < 0:
self.pos.x = collides[0].rect.right
self.vel.x = 0
self.rect.x = self.pos.x
if dir == 'y':
collides = pg.sprite.spritecollide(self, self.game.walls, False)
if collides:
if self.vel.y > 0:
self.pos.y = collides[0].rect.top - self.rect.height
if self.vel.y < 0:
self.pos.y = collides[0].rect.bottom
self.vel.y = 0
self.rect.y = self.pos.y
def update(self):
s.draw(self.game.screen, index % s.totalCellCount, HW, HH, CENTER_HANDLE)
index += 1
self.ifColliding = False
self.get_keys()
self.pos += self.vel * self.game.dt
self.rect.x = self.pos.x
self.collide_with_walls('x'), self.collide_with_player1('x', self.ifColliding)
self.rect.y = self.pos.y
self.collide_with_walls('y'), self.collide_with_player1('y', self.ifColliding)
if Thief.health <= 0:
self.kill()
class Wall(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.walls
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pg.Surface((TILESIZE, TILESIZE))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.x = x
self.y = y
self.rect.x = x * TILESIZE
self.rect.y = y * TILESIZE
Hoping to run through a row of cells within the sprite sheet depending on the direction the sprite is moving in and set that as the sprite image (Giving the illusion that the player sprite is animated)
Any help would be appreciated, sorry if the Question is confusing, first time using the site and not too good at coding at the current stage.
Load four images as
self.image_up = pygame.image.load(...)
self.image_down = pygame.image.load(...)
self.image_left = pygame.image.load(...)
self.image_right = pygame.image.load(...)
and later when you need it replace
self.image = self.image_up
or
self.image = self.image_down
etc.
If you have all positions in one file then you can use pygame.Surface.subsurface to cut off part of image and create new one
spritesheet = pygame.image.load(...)
self.image_up = spritesheet.subsurface( Rect(0,0,10,10) )
self.image_down = spritesheet.subsurface( Rect(...) )
self.image_left = spritesheet.subsurface( Rect(...) )
self.image_right = spritesheet.subsurface( Rect(...) )
My simple example (with all positions in one file) on GitHub: pygame-spritesheet
Use left/right arrows to move object and it will use different image.

Categories

Resources