I'm making a game and an opponent is supposed to shoot bullets at the player.
I want the bullets to go in the direction of where the player is when the bullet shoots (the player may move but the bullet goes in a constant direction.)
But the bullet just flickers on the opponent.
I use pygame's Vector2 to control the movement of the bullets.
Here's a bullet-spell example:
bulletspell = Spell(
pygame.image.load("Sprites/lightblue-glowey.png"),
((0, pygame.Vector2(-0.5, 1) * 4), #these vectors show the bullet shooting pattern
(0, pygame.Vector2(0, 1) * 4),
(0, pygame.Vector2(0.5, 1) * 4)),
10, 8, 340
)
The vector I tried was (player.image.get_rect(topleft=(player.rect.x, player.rect.y)).x, 1)
I'm not looking for the whole code to be revised, I've had too many revisions and the code I have works, I just need help figuring out the vector.
Here's the code (just for reference):
import pygame
class Player(pygame.sprite.Sprite):
sprite = pygame.image.load("Sprites/player.png")
def __init__(self, *groups):
super().__init__(*groups)
self.image = Player.sprite
self.rect = self.image.get_rect(topleft=(445, 550))
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
key = pygame.key.get_pressed()
dist = 3
if key[pygame.K_DOWN]:
self.rect.y += dist
elif key[pygame.K_UP]:
self.rect.y -= dist
if key[pygame.K_RIGHT]:
self.rect.x += dist
elif key[pygame.K_LEFT]:
self.rect.x -= dist
class Spell:
def __init__(self, bullet, pattern, speed, loop, tick_delay):
self.bullet = bullet
self.pattern = pattern
self.speed = speed
self.loop = loop
self.tick_delay = tick_delay
class Opponent(pygame.sprite.Sprite):
def __init__(self, sprite, sequence, *groups):
super().__init__(*groups)
self.image = sprite
self.rect = self.image.get_rect(topleft=(425, 30))
self.start_time = pygame.time.get_ticks()
self.sequence = sequence
self.spellno = 0
self.currentspell = sequence[self.spellno]
def update(self):
time_gone = pygame.time.get_ticks() - self.start_time
if self.currentspell is not None and time_gone > self.currentspell.tick_delay:
self.start_time = pygame.time.get_ticks()
for bullet in self.currentspell.pattern:
if bullet[0] <= time_gone:
Bullet(self.rect.center, bullet[1], self.currentspell.bullet, sprites, bullets)
self.currentspell.loop -= 1
if self.currentspell.loop <= 0:
self.spellno += 1
if self.spellno >= len(self.sequence):
self.currentspell = None
else:
self.currentspell = self.sequence[self.spellno]
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, direction, image, *groups):
super().__init__(*groups)
self.image = image
self.rect = self.image.get_rect(topleft=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
self.pos += self.direction
self.rect.topleft = (self.pos.x, self.pos.y)
if not screen.get_rect().colliderect(self.rect):
self.kill()
sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
opponentgroup = pygame.sprite.Group()
img3 = pygame.image.load("Sprites/minty.png")
player = Player(sprites)
#I tried:
mi3 = Spell(
pygame.image.load("Sprites/purple-glowey.png"),
((0, pygame.Vector2(player.image.get_rect(topleft=(player.rect.x, player.rect.y)).x, 1) * 4),
), 4, 8, 340)
minty_spells = [mi1, mi3]
Minty = Opponent(img3, minty_spells, opponentgroup)
sprites.add(Minty)
pygame.init()
SCREENWIDTH = 1000
SCREENHEIGHT = 650
screen = pygame.display.set_mode([SCREENWIDTH, SCREENHEIGHT])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
background = screen.copy()
clock = pygame.time.Clock()
#main loop goes here
Any help would be appreciated
Thanks :)
At the moment a bullet is instantiated its direction needs to be:
(Player.pos - Opponent.pos).normalise()
This will give you a unit vector (a vector of length 1) pointing from Opponent to Player at the moment the bullet is fired. You'll need to add this to the bullet's position at each update.
I recommend changing the code structure a bit and turn the spells into subclasses of Spell (take a look at the state pattern). Move more of the shooting related code into the Spell class and give it an update method where the timer is updated and a shoot method in which the bullets are created.
The shoot method of the spell that should target the player can be overridden, so that it aims at the position of the player instead of using one of the predefined directions.
In the update method of the Opponent, you just need to check if the current spell is done (when loop is 0), then switch the spell by creating a new instance and call its update method each frame.
import pygame
class Spell:
def __init__(self, bullet_img, speed, loop, delay):
self.bullet_img = bullet_img
self.pattern = None
self.speed = speed
self.loop = loop
self.delay = delay
self.start_time = pygame.time.get_ticks()
def shoot(self, pos, target_pos):
for pattern in self.pattern:
Bullet(pos, pattern, self.bullet_img, sprites, bullets)
def update(self, pos, target_pos):
time_gone = pygame.time.get_ticks() - self.start_time
if time_gone > self.delay:
self.start_time = pygame.time.get_ticks()
self.shoot(pos, target_pos)
self.loop -= 1
class Spell1(Spell):
def __init__(self):
super().__init__(img, 10, 3, 340)
self.pattern = (pygame.Vector2(-2, 4), pygame.Vector2(0, 4),
pygame.Vector2(2, 4))
class Spell2(Spell):
def __init__(self):
super().__init__(img2, 4, 2, 340)
self.pattern = (pygame.Vector2(4, 4), pygame.Vector2(-4, 4))
class Spell3(Spell):
def __init__(self):
super().__init__(img3, 4, 6, 340)
# Override the shoot method to aim at the player position.
def shoot(self, pos, target_pos):
direction = (pygame.Vector2(target_pos) - pos).normalize() * 4
Bullet(pos, direction, self.bullet_img, sprites, bullets)
class Opponent(pygame.sprite.Sprite):
def __init__(self, pos, sprite, spells, *groups):
super().__init__(*groups)
self.image = sprite
self.rect = self.image.get_rect(topleft=pos)
self.start_time = pygame.time.get_ticks()
self.spells = spells
self.spellno = 0
self.currentspell = spells[self.spellno]() # Create the instance here.
def update(self):
if self.spellno < len(self.spells):
# You can pass the player position instead of the mouse pos here.
self.currentspell.update(self.rect.center, pygame.mouse.get_pos())
# Decrement the loop attribute of the current spell and
# switch to the next spell when it's <= 0. When all spells
# are done, set self.currentspell to None to stop shooting.
if self.currentspell.loop <= 0:
self.spellno += 1
if self.spellno < len(self.spells):
self.currentspell = self.spells[self.spellno]()
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, direction, image, *groups):
super().__init__(*groups)
self.image = image
self.rect = self.image.get_rect(center=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.center)
def update(self):
self.pos += self.direction
self.rect.center = self.pos
if not screen.get_rect().colliderect(self.rect):
self.kill()
sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
opponentgroup = pygame.sprite.Group()
img = pygame.Surface((30, 40))
img.fill((0, 100, 200))
img2 = pygame.Surface((30, 30))
img2.fill((110, 0, 220))
img3 = pygame.Surface((30, 50))
img3.fill((255, 170, 0))
minty_spells = [Spell1, Spell2, Spell3]
minty = Opponent((425, 30), img3, minty_spells, opponentgroup)
minty2 = Opponent((225, 30), img3, [Spell2, Spell3, Spell1], opponentgroup)
sprites.add(minty, minty2)
pygame.init()
screen = pygame.display.set_mode([1000, 650])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
background = screen.copy()
clock = pygame.time.Clock()
def main():
while True:
for events in pygame.event.get():
if events.type == pygame.QUIT:
pygame.quit()
return
sprites.update()
screen.blit(background, (0, 0))
sprites.draw(screen)
pygame.display.update()
clock.tick(100)
if __name__ == '__main__':
main()
Related
I am writing a simple invaders game. To add damage to the bases I figured I could blit a small, black surface on the base at bullet impact, and use a mask to check if the bullet was on the damage or the base, but it isn't working and I feel I am misunderstanding the mask. The first collision is detected but after that it also detects a collision but doesn't put any more damage on the base. I thought because the surface was black the base mask wouldn't include it, but it isn't working. Here is a short test to demo this. Press space (or any key) to fire a bullet at the base. I thought maybe I should generate a new mask for the base but that doesn't work. The mask collide is from the pygame sprite code on github.
import sys, pygame, random
from pygame.locals import *
screenwidth = 600
screenheight = 400
pygame.init()
screen = pygame.display.set_mode((screenwidth, screenheight))
pygame.display.set_caption("shoot 'em up")
screenrect = screen.get_rect()
black = (0, 0, 0)
blue = (10, 10, 255)
yellow = (238, 238, 0)
base_width = 80
base_height = 40
bullet_width = 3
bullet_height = 10
class Bullet(pygame.Surface):
def __init__(self, point):
super().__init__((bullet_width, bullet_height), pygame.SRCALPHA)
self.rect = self.get_rect()
self.rect.midbottom = point
self.fill(yellow)
self.velocity = -5
self.alive = True
self.mask = pygame.mask.from_surface(self)
def update(self):
self.rect.top += self.velocity
def draw(self, surf):
surf.blit(self, self.rect)
class Base(pygame.Surface):
def __init__(self, x, y, colour):
super().__init__((base_width, base_height), pygame.SRCALPHA)
self.rect = self.get_rect()
self.rect.x = x
self.rect.y = y
self.fill(colour)
self.alive = True
def add_damage(self, bullet):
width = random.randint(3, 6)
height = random.randint(8, 12)
damage = pygame.Surface((width, height), pygame.SRCALPHA)
damage.fill(black)
rect = damage.get_rect()
rect.x = bullet.rect.x - self.rect.x
rect.y = bullet.rect.top - self.rect.top
self.blit(damage, rect)
#self.mask = pygame.mask.from_surface(self)
def draw(self, surf):
surf.blit(self, self.rect)
class Test(pygame.Surface):
def __init__(self):
super().__init__((600, 400))
self. base = Base(50, 300, blue)
self.bullets = []
def run(self):
while 1:
self.get_events()
self.update()
self.draw()
def get_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
bullet = Bullet((60, 380))
self.bullets.append(bullet)
def update(self):
if self.bullets:
for bullet in self.bullets:
bullet.update()
self.collision_check(bullet)
for bullet in self.bullets:
if not bullet.alive:
self.bullets.remove(bullet)
def collision_check(self, bullet):
if bullet.rect.colliderect(self.base):
if self.collide_mask(bullet, self.base):
print("collide")
self.base.add_damage(bullet)
bullet.alive = False
def collide_mask(self, left, right):
xoffset = right.rect[0] - left.rect[0]
yoffset = right.rect[1] - left.rect[1]
try:
leftmask = left.mask
except AttributeError:
leftmask = pygame.mask.from_surface(left)
try:
rightmask = right.mask
except AttributeError:
rightmask = pygame.mask.from_surface(right)
return leftmask.overlap(rightmask, (xoffset, yoffset))
def draw(self):
self.fill(black)
self.base.draw(self)
for bullet in self.bullets:
bullet.draw(self)
screen.blit(self, (0,0))
pygame.display.flip()
if __name__=="__main__":
t = Test()
t.run()
As you can see this is not using pygame sprites.
if the pygame.Surface object is changed you need to recreate the mask with pygame.mask.from_surface. However, the mask is generated form the Surface's alpha channel. Therefore, you need to make the damaged area transparent. Create a completely transparent rectangle (RGBA = 0, 0, 0, 0) and blit the rectangle using the special flag BLEND_RGBA_MULT (or BLEND_RGBA_MIN). Finally recreate the mask:
damage = pygame.Surface((width, height), pygame.SRCALPHA)
self.blit(damage, rect, special_flags=pygame.BLEND_RGBA_MULT)
self.mask = pygame.mask.from_surface(self)
add_damage Mehtod:
class Base(pygame.Surface):
# [...]
def add_damage(self, bullet):
width = random.randint(3, 6)
height = random.randint(8, 12)
damage = pygame.Surface((width, height), pygame.SRCALPHA)
rect = damage.get_rect()
rect.x = bullet.rect.x - self.rect.x
rect.y = bullet.rect.top - self.rect.top
self.blit(damage, rect, special_flags=pygame.BLEND_RGBA_MULT)
self.mask = pygame.mask.from_surface(self)
For some reason, all my enemies are slowing down after they collide with the bullet. Why is this occurring? I have attached a GIF below. This game is incomplete and I am fairly new to pygame. Give any advice as needed. I made a previous post with a different, please reply to that as well if possible.
Attached GIF
import pygame, sys, random
from pygame.locals import *
pygame.init()
screen_height = 870
screen_width = 1530
screen = pygame.display.set_mode((screen_width, screen_height))
fpsfunc = pygame.time.Clock()
enemy_speed = 6
enemy_motion = 1
SPAWNENEMY = USEREVENT + 1
SPAWNEASY = USEREVENT + 2
SPAWNMED = USEREVENT + 3
SPAWNHARD = USEREVENT + 4
pygame.time.set_timer(SPAWNENEMY, 1000)
cenx = screen_width / 2
ceny = screen_height / 2
gen1 = [cenx-200,ceny-200,cenx-100,ceny-100,cenx,ceny,cenx+100,ceny+100,cenx+200,ceny+200]
gen2 = []
gen3 = []
choices = [gen1]
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.surf = pygame.Surface((50, 50))
self.surf.fill((255, 0, 0))
self.rect = self.surf.get_rect(center=(screen_width / 2,
screen_height - 100))
def update(self, pressed_keys):
if pressed_keys[K_d]:
self.rect.move_ip(3, 0)
if pressed_keys[K_a]:
self.rect.move_ip(-3, 0)
if pressed_keys[K_w]:
self.rect.move_ip(0, -5)
if pressed_keys[K_s]:
self.rect.move_ip(0, 2)
if self.rect.left <= 0:
self.rect.left = 0
if self.rect.top <= 0:
self.rect.top = 0
if pressed_keys[K_SPACE]:
bullet_sprites.add(player.create_bullet())
def create_bullet(self):
return PlayerBullet(self.rect.x + 25, self.rect.y)
class PlayerBullet(pygame.sprite.Sprite):
def __init__(self, pos_x, pos_y):
super().__init__()
self.image = pygame.Surface((10, 30))
self.image.fill((0, 255, 255))
self.rect = self.image.get_rect(center=(pos_x, pos_y))
def update(self):
self.rect.y -= 5
if self.rect.y + 30 < 0:
self.kill()
class Enemy(pygame.sprite.Sprite):
def __init__(self, cenx, ceny):
super(Enemy, self).__init__()
self.image = pygame.Surface((35,35))
self.image.fill((0,0,255))
self.rect = self.image.get_rect(center = (cenx, ceny))
def update(self):
global enemy_motion, enemy_speed, cycle
if pygame.sprite.spritecollide(self, bullet_sprites, True):
self.kill()
for enemy in enemy_sprites:
if enemy not in enemy_dir_data:
enemy_dir_data[enemy] = 1
if enemy.rect.right >= screen_width:
enemy_dir_data[enemy] = -1
if enemy.rect.left <= 0:
enemy_dir_data[enemy] = 1
enemy.rect.x += enemy_speed * enemy_dir_data[enemy]
def createnem(pos1, pos2):
return Enemy(pos1, pos2)
player = Player()
bullet_sprites = pygame.sprite.Group()
enemy_sprites = pygame.sprite.Group()
enemy_dir_data = {}
start_ticks=pygame.time.get_ticks()
while True:
seconds=(pygame.time.get_ticks()-start_ticks)/1000
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
bullet_sprites.add(player.create_bullet())
#print(seconds)
if seconds >= 10:
enemy_sprites.empty()
if len(enemy_sprites) == 0:
selected = random.choice(choices)
for create in range(0,len(selected),2):
x = create
y = create + 1
print(selected[x],selected[y])
enemy_sprites.add(createnem(gen1[x], gen1[y]))
enemy_dir_data = {}
start_ticks=pygame.time.get_ticks()
pressed_keys = pygame.key.get_pressed()
screen.fill((0, 0, 0))
screen.blit(player.surf, player.rect)
bullet_sprites.draw(screen)
enemy_sprites.draw(screen)
player.update(pressed_keys)
enemy_sprites.update()
bullet_sprites.update()
pygame.display.update()
fpsfunc.tick(60)
The speed of your enemies depends on the number of enemies because of the for loop in Enemy.update. update is called once for each enemy in the Group. Because of the for loop each enemy is moved multiple times per frame.
Read about Method Objects and move just the "self" instance object in update:
class Enemy(pygame.sprite.Sprite):
# [...]
def update(self):
global enemy_motion, enemy_speed, cycle
if pygame.sprite.spritecollide(self, bullet_sprites, True):
self.kill()
if self not in enemy_dir_data:
enemy_dir_data[self] = 1
if self.rect.right >= screen_width:
enemy_dir_data[self] = -1
if self.rect.left <= 0:
enemy_dir_data[self] = 1
self.rect.x += enemy_speed * enemy_dir_data[self]
pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.
The former delegates the to the update mehtod of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():
Calls the update() method on all Sprites in the Group [...]
The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]
You don't need the dictionary enemy_dir_data at all. Instead add an attribute enemy_dir to the Enemy class:
class Enemy(pygame.sprite.Sprite):
def __init__(self, cenx, ceny):
super(Enemy, self).__init__()
self.image = pygame.Surface((35,35))
self.image.fill((0,0,255))
self.rect = self.image.get_rect(center = (cenx, ceny))
self.enemy_dir = 1
def update(self):
global enemy_motion, enemy_speed, cycle
if pygame.sprite.spritecollide(self, bullet_sprites, True):
self.kill()
if self.rect.right >= screen_width:
self.enemy_dir = -1
if self.rect.left <= 0:
self.enemy_dir = 1
self.rect.x += enemy_speed * self.enemy_dir
Try:
pygame.sprite.Sprite.kill(self)
Instead of self.kill
What am trying to do is to create an Arkanoid game, where the bricks have 3 points of strength each and then they die. The issue is that instead of just the particular brick that gets hit, to lose the points, the whole brick_sprite group is loosing the points. And when one suppose to die, all the previous within the list up to that one dies. I think the issue is that it loops considering the update on line #240. Please check line 65 at def collision(self): under Brick Class. The issue is somewhere there.
"""This is a simple version of arkanoid game"""
import sys
import pygame
import random
# Set colors R G B
white = (255, 255, 255)
black = (0, 0, 0)
orange = (255, 100, 10)
light_blue = (0, 144, 255)
shadow = (192, 192, 192)
purple = (152, 0, 152)
# Display
display_height = 999
display_width = 444
pygame.display.set_caption = ("Arkanoid 1.0")
FPS = 60
# Movement speed
speed = display_width // 60
# Movements
left = (-speed, 0)
right = (speed, 0)
up = (0, speed)
diagonal_left = [-speed, -speed]
diagonal_right = [speed, -speed]
# Game objects dimentions
base_dimentions = (display_width // 5, display_height // 100)
[brick_width, brick_height] = [display_width // 20 * 2, display_height // 100]
brick_dimentions = [brick_width, brick_height]
ball_dimentions = (display_height // 100, display_height // 100)
# Initializing text font
pygame.font.init()
txt_font = pygame.font.SysFont("Score: ", display_height//44)
# Initializing sprite lists
all_sprites = pygame.sprite.Group()
brick_sprites = pygame.sprite.Group()
class Brick(pygame.sprite.Sprite):
def __init__(self, point_value, center):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(brick_dimentions)
self.image.fill(purple)
self.rect = self.image.get_rect()
self.rect.center = center
self.point_value = point_value
def update(self):
self.collision()
def collision1(self): #This works no issue.
# If brick is hit, loses a point
collision = pygame.sprite.spritecollide(ball, brick_sprites, True)
return collision
def collision(self): #Here is the issue.
# If brick is hit, loses a point
collision = pygame.sprite.spritecollide(ball, brick_sprites, False)
if collision:
self.point_value -= 1
if self.point_value == 0:
self.kill() ## BUGGISH ##"""
class Ball(pygame.sprite.Sprite):
"""Initiates a moving ball and its' attributes"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(ball_dimentions)
self.image.fill(light_blue)
self.rect = self.image.get_rect()
self.rect.center = self.init_position()
self.direction = random.choice([diagonal_left, diagonal_right])
self.score = 0
def update(self):
self.movement()
def init_position(self):
# Initialize position of the ball
init_position = (board.rect.center[0],
(board.rect.center[1] - (base_dimentions[1] / 2)
- (ball_dimentions[1] / 2)))
return init_position
def collision(self):
# If hit bricks
collision = pygame.sprite.spritecollideany(ball, brick_sprites)
if collision:
self.direction[1] *= -1
self.score += 1
enter code here
def movement(self):
self.containment()
self.rect[1] += self.direction[1]
self.rect[0] += self.direction[0]
self.deflect()
self.ball_loss()
self.collision()
def containment(self):
if self.rect.right >= display_width or self.rect.left <= 0:
self.direction[0] *= -1
if self.rect.top <= 0:
self.direction[1] *= -1
def ball_loss(self):
if self.rect.bottom >= display_height:
self.reset()
bricks_reset()
def reset(self):
self.rect.center = self.init_position()
self.direction[1] *= -1
self.score = 0
def deflect(self):
# If hit base_board, deflect
if (self.rect.bottom == board.rect.top and
(board.rect.left <= self.rect.left <= board.rect.right or
board.rect.left <= self.rect.right <= board.rect.right)):
self.direction[1] *= -1
self.board_ball_interaction()
def board_ball_interaction(self):
# When board is moving, effects balls direction/speed
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT] and board.rect.left > 0:
self.direction[0] -= speed // 2
elif keystate[pygame.K_RIGHT] and board.rect.right < display_width:
self.direction[0] += speed // 2
class Base_board(pygame.sprite.Sprite):
"""Initiates base_board class and it's attributes"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(base_dimentions)
self.image.fill(orange)
self.rect = self.image.get_rect()
self.rect.center = (display_width // 2,
display_height - 2 * base_dimentions[1])
self.x_direction = 0
def update(self):
# Up-dates classes' position according to user's imput
self.x_direction = 0
self.movement()
self.rect.x += self.x_direction
def movement(self):
# Creates movement and constrains object within screen dimentions
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT]:
if not self.rect.left <= 0:
self.x_direction = -speed
elif keystate[pygame.K_RIGHT]:
if not self.rect.right >= display_width:
self.x_direction = speed
def shoot(self):
pass
def enlogate(self):
pass
def control():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# and adding all sprites on lists
board = Base_board()
ball = Ball()
all_sprites.add(board)
all_sprites.add(ball)
def bricks_list_creator():
# Creates and adds bricks into a list
i = 9
point_value = 2 ####
coordinates = [display_width // 20 + brick_width / 6, display_height // 20]
while i > 0:
brick = Brick(point_value, (coordinates)) ####
coordinates[0] += brick_width * 1.1
brick_sprites.add(brick)
i -= 1
return brick_sprites
def bricks_reset():
# Reset brick list
brick_sprites.empty()
bricks_list_creator()
return brick_sprites
def render_text(screen):
text = txt_font.render("Score: {0}".format(ball.score), 1, (0, 0, 0))
return screen.blit(text, (5, 10))
def render_main(screen):
all_sprites.draw(screen)
brick_sprites.draw(screen)
render_text(screen)
# Game main
def main():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((display_width, display_height))
bricks_list_creator()
while True:
# Events
clock.tick(FPS)
control()
# Update
brick_sprites.update()
all_sprites.update()
# Render
screen.fill(shadow)
render_main(screen)
pygame.display.flip()
pygame.display.update()
main()
I think the issue is in the update() of your Brick class calling the collision.
The sprite update function is typically used for changing the position or look of your sprite, and is called every frame. So it's not a good place to check for collisions.
A Brick only needs to know its point_value, it doesn't move (AFAIK).
class Brick(pygame.sprite.Sprite):
def __init__(self, point_value, center):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(brick_dimentions)
self.image.fill(purple)
self.rect = self.image.get_rect()
self.rect.center = center
self.point_value = point_value
def takeHit( self, ball_sprite ):
# the ball has collided with *this* brick
self.point_value -= 1
if self.point_value == 0:
self.kill()
Then in Ball.collision() use the pygame.sprite.spritecollide() to get the list of Bricks the Ball has collided with, and reduce their hit points:
class Ball:
# [...]
def collision(self):
# calculate the list of bricks hit
hit_list = pygame.sprite.spritecollide( self, brick_sprites, False )
for brick in hit_list:
brick.takeHit() # may (or may not) kill the brick
Most of the time the hit_list is going to be a single Brick, but depending on the size of the ball, perhaps occasionally it's two bricks.
I'm trying to make a bullet-hell style game and I've almost got it to work except the opponent won't change from shooting one bullet pattern to another.
It's supposed to shoot 3 blue bullets 8 times, then switch to shooting 2 purple bullets 8 times. There's a sequence of patterns but I've only got two.
So it should iterate through each pattern every time the current pattern shoots a certain amount of times. When all the patterns are done it should stop shooting completely.
I've seen people try to make these but it's always java and I'm on python.
The code is very long but I can't cut it down any more. The original is in multiple files but I've put it into one script. It's virtually impossible to simplify.
import sys
import time
import itertools
import pygame
from pygame.locals import *
class Player(pygame.sprite.Sprite):
#this sprite variable is a placeholder
sprite = pygame.image.load("Sprites/player.png")
def __init__(self, *groups):
super().__init__(*groups)
self.image = Player.sprite
self.rect = self.image.get_rect(topleft=(445, 550))
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
key = pygame.key.get_pressed()
dist = 3
if key[pygame.K_DOWN]:
self.rect.y += dist
elif key[pygame.K_UP]:
self.rect.y -= dist
if key[pygame.K_RIGHT]:
self.rect.x += dist
elif key[pygame.K_LEFT]:
self.rect.x -= dist
class Spell:
def __init__(self, bullet, pattern, speed, loop, tick_delay):
self.bullet = bullet
self.pattern = pattern
self.speed = speed
self.loop = loop
self.tick_delay = tick_delay
class Opponent(pygame.sprite.Sprite):
def __init__(self, sprite, sequence, *groups):
super().__init__(*groups)
self.image = sprite
self.rect = self.image.get_rect(topleft=(425, 30))
self.pos = pygame.Vector2(self.rect.topleft)
self.path = itertools.cycle((self.rect.topleft, ))
self.next_point = pygame.Vector2(next(self.path))
self.speed = 1
self.ticks = 1000
self.queue = []
self.sequence = sequence
self.spellno = 0
self.currentspell = sequence[self.spellno]
def update(self):
#this function basically does most of the stuff in this class
move = self.next_point - self.pos
move_length = move.length()
if move_length != 0:
move.normalize_ip()
move = move * self.speed
self.pos += move
#later on down the line i want to make the opponent sprite move
if move.length() == 0 or move_length < self.speed:
self.next_point = pygame.Vector2(next(self.path))
self.rect.topleft = self.pos
for i in range(0, self.currentspell.loop):
if pygame.time.get_ticks() - self.ticks > self.currentspell.tick_delay:
self.ticks = pygame.time.get_ticks()
self.shoot()
time_gone = pygame.time.get_ticks() - self.ticks
for bullet in self.queue:
if bullet[0] <= time_gone:
Bullet(self.rect.center, bullet[1], self.currentspell.bullet, sprites, bullets)
self.queue = [bullet for bullet in self.queue if bullet[0] > time_gone]
return
def shoot(self):
pattern = self.currentspell.pattern
self.queue = pattern
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, direction, image, *groups):
super().__init__(*groups)
self.image = image
self.rect = self.image.get_rect(topleft=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
self.pos += self.direction
self.rect.topleft = (self.pos.x, self.pos.y)
if not screen.get_rect().colliderect(self.rect):
self.kill()
sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
opponentgroup = pygame.sprite.Group()
mi1 = Spell(pygame.image.load("Sprites/lightblue-glowey.png"),(
(0, pygame.Vector2(-0.5, 1) * 4),
(0, pygame.Vector2(0, 1) * 4),
(0, pygame.Vector2(0.5, 1) * 4)),
10, 8, 340
)
mi2 = Spell(pygame.image.load("Sprites/purple-glowey.png"),(
(0, pygame.Vector2(1, 1) * 4),
(0, pygame.Vector2(-1, 1) * 4)),
4, 8, 340
)
minty_spells = [mi1, mi2]
player = Player(sprites)
Minty = Opponent(pygame.image.load("Sprites/minty.png"), minty_spells, opponentgroup)
opponents = [Minty]
pygame.init()
SCREENWIDTH = 1000
SCREENHEIGHT = 650
screen = pygame.display.set_mode([SCREENWIDTH, SCREENHEIGHT])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
background = screen.copy()
clock = pygame.time.Clock()
currentopponent = 0
def closegame():
pygame.quit()
return
def stage(opponent, background, bgm):
currentopponent = opponent
for spell in opponents[opponent].sequence:
op = opponents[opponent]
op.update()
op.spellno += 1
def main():
running = True
while running:
for events in pygame.event.get():
if events.type == pygame.QUIT:
pygame.quit()
return
# update all sprites
sprites.update()
sprites.add(opponents[currentopponent])
# draw everything
screen.blit(background, (0, 0))
stage(0, "", "") # "" means placeholder. i'm working on them
sprites.draw(screen)
pygame.display.update()
clock.tick(100)
if __name__ == '__main__':
main()
Original Code and assets on my GitHub: https://github.com/E-Lee-Za/Eleeza-Crafter-The-Game
Here's a working (and simplified) version of your code. The loop attribute of the current spell gets decremented every time when the bullets are created. When loop is 0,
the self.spellno is incremented and the spell gets changed, otherwise if the spellno is >= len(self.sequence), self.currentspell gets set to None so that it stops shooting (just add if self.currentspell is not None to the conditional statement).
import pygame
class Spell:
def __init__(self, bullet, pattern, speed, loop, tick_delay):
self.bullet = bullet
self.pattern = pattern
self.speed = speed
self.loop = loop
self.tick_delay = tick_delay
class Opponent(pygame.sprite.Sprite):
def __init__(self, sprite, sequence, *groups):
super().__init__(*groups)
self.image = sprite
self.rect = self.image.get_rect(topleft=(425, 30))
self.start_time = pygame.time.get_ticks()
self.sequence = sequence
self.spellno = 0
self.currentspell = sequence[self.spellno]
def update(self):
time_gone = pygame.time.get_ticks() - self.start_time
# Only create bullets if self.currentspell is not None.
if self.currentspell is not None and time_gone > self.currentspell.tick_delay:
self.start_time = pygame.time.get_ticks()
for bullet in self.currentspell.pattern:
if bullet[0] <= time_gone:
Bullet(self.rect.center, bullet[1], self.currentspell.bullet, sprites, bullets)
# Decrement the loop attribute of the current spell and
# switch to the next spell when it's <= 0. When all spells
# are done, set self.currentspell to None to stop shooting.
self.currentspell.loop -= 1
if self.currentspell.loop <= 0:
self.spellno += 1
if self.spellno >= len(self.sequence):
self.currentspell = None
else:
self.currentspell = self.sequence[self.spellno]
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, direction, image, *groups):
super().__init__(*groups)
self.image = image
self.rect = self.image.get_rect(topleft=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
self.pos += self.direction
self.rect.topleft = (self.pos.x, self.pos.y)
if not screen.get_rect().colliderect(self.rect):
self.kill()
sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
opponentgroup = pygame.sprite.Group()
img = pygame.Surface((30, 40))
img.fill((0, 100, 200))
mi1 = Spell(
img,
((0, pygame.Vector2(-0.5, 1) * 4), (0, pygame.Vector2(0, 1) * 4),
(0, pygame.Vector2(0.5, 1) * 4)),
10, 8, 340
)
img2 = pygame.Surface((30, 30))
img2.fill((110, 0, 220))
mi2 = Spell(
img2,
((0, pygame.Vector2(1, 1) * 4), (0, pygame.Vector2(-1, 1) * 4)),
4, 8, 340
)
minty_spells = [mi1, mi2]
img3 = pygame.Surface((30, 50))
img3.fill((220, 0, 200))
Minty = Opponent(img3, minty_spells, opponentgroup)
sprites.add(Minty)
pygame.init()
SCREENWIDTH = 1000
SCREENHEIGHT = 650
screen = pygame.display.set_mode([SCREENWIDTH, SCREENHEIGHT])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
background = screen.copy()
clock = pygame.time.Clock()
def main():
while True:
for events in pygame.event.get():
if events.type == pygame.QUIT:
pygame.quit()
return
sprites.update()
screen.blit(background, (0, 0))
sprites.draw(screen)
pygame.display.update()
clock.tick(100)
if __name__ == '__main__':
main()
I am new to pygame and I am trying to make a game where the player has to bypass some enemy's to get to a point where you can go to the next level. I need the enemy's to walk back and forward on a predetermined path but I can't figure out how to do it. So I was wondering if there is an easy way to do this?
This is my code.
import pygame
import random
import os
import time
from random import choices
from random import randint
pygame.init()
a = 0
b = 0
width = 1280
height = 720
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Game")
done = False
n = 0
x = 0
y = 0
x_wall = 0
y_wall = 0
clock = pygame.time.Clock()
WHITE = (255,255,255)
RED = (255,0,0)
change_x = 0
change_y = 0
HW = width / 2
HH = height / 2
background = pygame.image.load('mountains.png')
#player class
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("character.png")
self.rect = self.image.get_rect()
self.rect.x = width / 2
self.rect.y = height / 2
#enemy class
class Enemy(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("enemy.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = width / 3
self.rect.y = height / 3
#wall class
class Wall(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("wall.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#wall movement
def update(self):
self.vx = 0
self.vy = 0
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
self.vx = 5
self.vy = 0
elif key[pygame.K_RIGHT]:
self.vx = -5
self.vy = 0
if key[pygame.K_UP]:
self.vy = 5
self.vx = 0
elif key[pygame.K_DOWN]:
self.vy = -5
self.vx = 0
self.rect.x = self.rect.x + self.vx
self.rect.y = self.rect.y + self.vy
#player sprite group
sprites = pygame.sprite.Group()
player = Player()
sprites.add(player)
#enemy sprite group
enemys = pygame.sprite.Group()
enemy = Enemy()
enemy2 = Enemy()
enemys.add(enemy, enemy2)
#all the wall sprites
wall_list = pygame.sprite.Group()
wall = Wall(x_wall, y_wall)
wall2 = Wall((x_wall + 50), y_wall)
wall3 = Wall((x_wall + 100), y_wall)
wall4 = Wall((x_wall + 150), y_wall)
wall5 = Wall((x_wall + 200), y_wall)
wall6 = Wall((x_wall + 250), y_wall)
#add all the walls to the list to draw them later
wall_list.add(wall, wall2, wall3, wall4, wall5, wall6)
#add all the walls here to fix the collision
all_walls = (wall, wall2, wall3, wall4, wall5, wall6)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
sprites.update()
wall_list.update()
enemys.update()
#collision between player and and walls
if player.rect.collidelist(all_walls) >= 0:
print("Collision !!")
player.rect.x = player.rect.x - player.vx
player.rect.y = player.rect.y - player.vx
#fill the screen
screen.fill((0, 0, 0))
#screen.blit(background,(x,y))
#draw the sprites
sprites.draw(screen)
wall_list.draw(screen)
enemys.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Here is the download link with the images if you want to run it:
https://geordyd.stackstorage.com/s/hZZ1RWcjal6ecZM
I'd give the sprite a list of points (self.waypoints) and assign the first one to a self.target attribute.
In the update method I subtract the self.pos from the self.target position to get a vector (heading) that points to the target and has a length equal to the distance. Scale this vector to the desired speed and use it as the velocity (which gets added to the self.pos vector each frame) and the entity will move towards the target.
When the target is reached, I just increment the waypoint index and assign the next waypoint in the list to self.target. It's a good idea to slow down when you're getting near the target, otherwise the sprite could get stuck and moves back and forth if it can't reach the target point exactly. Therefore I also check if the sprite is closer than the self.target_radius and decrease the velocity to a fraction of the maximum speed.
import pygame as pg
from pygame.math import Vector2
class Entity(pg.sprite.Sprite):
def __init__(self, pos, waypoints):
super().__init__()
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0, 0)
self.max_speed = 3
self.pos = Vector2(pos)
self.waypoints = waypoints
self.waypoint_index = 0
self.target = self.waypoints[self.waypoint_index]
self.target_radius = 50
def update(self):
# A vector pointing from self to the target.
heading = self.target - self.pos
distance = heading.length() # Distance to the target.
heading.normalize_ip()
if distance <= 2: # We're closer than 2 pixels.
# Increment the waypoint index to swtich the target.
# The modulo sets the index back to 0 if it's equal to the length.
self.waypoint_index = (self.waypoint_index + 1) % len(self.waypoints)
self.target = self.waypoints[self.waypoint_index]
if distance <= self.target_radius:
# If we're approaching the target, we slow down.
self.vel = heading * (distance / self.target_radius * self.max_speed)
else: # Otherwise move with max_speed.
self.vel = heading * self.max_speed
self.pos += self.vel
self.rect.center = self.pos
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
waypoints = [(200, 100), (500, 400), (100, 300)]
all_sprites = pg.sprite.Group(Entity((100, 300), waypoints))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
for point in waypoints:
pg.draw.rect(screen, (90, 200, 40), (point, (4, 4)))
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Instead of the waypoints list and index I'd actually prefer to use itertools.cycle and just call next to switch to the next point:
# In the `__init__` method.
self.waypoints = itertools.cycle(waypoints)
self.target = next(self.waypoints)
# In the `update` method.
if distance <= 2:
self.target = next(self.waypoints)
Use a list to have him walk back and forth.
class Enemy(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("enemy.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = width / 3
self.rect.y = height / 3
#x or y coordinates
self.list=[1,2,3,4,5]
self.index=0
def update(self):
# patrol up and down or left and right depending on x or y
if self.index==4:
#reverse order of list
self.list.reverse()
self.index=0
#set the x position of the enemy according to the list
self.rect.x=self.list[self.index]
self.index+=1