Reputation: 377
So I'm back at making a game and I was coding in the gameplay mechanics (it's a dodge-the-bullet, I was making the bullets) The bullets should keep generating from the top sprite (Minty) and keep spreading out(trust me, I've spent a lot of time reading up and googling stuff.) This is how it was supposed to look: (the top sprite is the opponent, the little white square is player, the purple circles are the bullets by the way)
how it should be:
but here's how it is:
And I just don't understand why it's happening?
Here's my code:
import sys
import time
import pygame
from pygame.locals import *
pygame.init()
#INITIALISE THE WINDOW.
#CONSTANTS ARE CAPITAL, VARIABLES ARE LOWERCASE
SCREENWIDTH = 1000
SCREENHEIGHT = 650
SCREENSIZE = [SCREENWIDTH, SCREENHEIGHT]
screen = pygame.display.set_mode(SCREENSIZE)
BG_COL = [255, 123, 67]
S1_COL = (0, 255, 188)
clock = pygame.time.Clock()
screen.fill(BG_COL)
pygame.draw.rect(screen, S1_COL,(50, 50, 900, 575), 0)
pygame.display.update()
clock.tick(60)
class Player(pygame.sprite.Sprite):
def __init__(self, sprite):
self.sprite = sprite
self.x = 445
self.y = 550
class Opponent(pygame.sprite.Sprite):
def __init__(self, sprite):
self.sprite = sprite
self.x = 445
self.y = 30
class Bullet:
def __init__(self, sprite, length, width):
self.sprite = sprite
self.x = 460
self.y = 50
self.length = length
self.width = width
self.area = self.sprite.get_rect(x=self.x, y=self.y)
self.area.clamp_ip((50, 50, 900, 575))
class BulletGroup(pygame.sprite.Group):
def __init__(self, typeof, numof):
self.typeof = typeof
self.numof = numof
self.listof = []
for i in range(0, self.numof):
self.listof.append(typeof)
player = Player(pygame.image.load("Sprites/player.png"))
Minty = Opponent(pygame.image.load("Sprites/minty.png"))
purple_glow = Bullet(pygame.image.load("Sprites/purple-glowey.png"), 70, 65)
test_bullets = BulletGroup(purple_glow, 5)
#make functions
def background(colour): #to make it easier to draw the background each time
screen.fill(BG_COL)
pygame.draw.rect(screen, colour,(50, 50, 900, 575), 0)
def handle_keys():
""" Handles Keys """
key = pygame.key.get_pressed()
dist = 2
if key[pygame.K_DOWN]: # down key
player.y += dist # move down
elif key[pygame.K_UP]: # up key
player.y -= dist # move up
if key[pygame.K_RIGHT]: # right key
player.x += dist # move right
elif key[pygame.K_LEFT]: # left key
player.x -= dist # move left
#MAIN GAME LOOP
running = True
while running:
for events in pygame.event.get():
if events.type == QUIT:
pygame.quit()
exit()
running = False
if events.type == KEYDOWN:
background(S1_COL)
#BULLETS:
def move_bullets(bullets, xchange, ychange):
#MOVE THE CLONED BULLETS IN ONE CONSTANT DIRECTION
for b in bullets.listof:
b.x += xchange
b.y += ychange
screen.blit(b.sprite, (b.x, b.y))
pygame.draw.rect(screen, S1_COL, b.area, 0)
xchange += 10
pygame.time.delay(50)
#STAGE1 MAKE
screen.blit(pygame.transform.scale(Minty.sprite, (130, 140)), [Minty.x, Minty.y])
for events in pygame.event.get():
screen.blit(pygame.transform.scale(Minty.sprite, (130, 140)), [Minty.x, Minty.y]) #i'm going to change image size on phone later on
print(test_bullets.listof[0].x)
print(test_bullets.listof[0].y)
#IF KEY PRESSED
#MOVE PLAYER SPRITE
screen.blit(player.sprite, (player.x, player.y))
handle_keys()
move_bullets(test_bullets, -10, 10)
pygame.display.update()
Any feedback, explanation, edit suggests or something like that would be greatly appreciated.
Upvotes: 1
Views: 1753
Reputation: 101052
You should use the Sprite
class as intended: define at least an image
and rect
property, and put the logic of each game object into the update
method. No need to handle drawing yourself.
Then your main loop will become very clear and a classical three part loop, where you do, in order, the following:
Here's how it could look like (note the comments where I explain some stuff):
import pygame
import itertools
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)
# let's create a background surface that we can reuse instead of
# drawing manually to the screen
background = screen.copy()
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
# the player sprite is constant, so let's use a class variable
sprite = pygame.image.load("Sprites/player.png")
def __init__(self, *groups):
# we want to use sprites, so we have to call __init__ of the Sprite class
# groups is a list of groups we want this sprite to add to
super().__init__(*groups)
# the image of the sprite needs to be in an attribute called 'image'
self.image = Player.sprite
# for pygame to know where to draw the sprite, we need a 'rect' attribute
self.rect = self.image.get_rect(topleft=(445, 550))
def update(self):
# moving by keyboard is unique to the player class, so let's to this here
key = pygame.key.get_pressed()
dist = 3
# we just update our rect's position to move the sprite
# we should use vectors here, too, but for now this is good enough
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 Opponent(pygame.sprite.Sprite):
def __init__(self, sprite, *groups):
super().__init__(*groups)
self.image = sprite
self.rect = self.image.get_rect(topleft=(445, 30))
# we keep an additional attribute 'pos' to store the postion
# it's a vector so we can use some vector math
self.pos = pygame.Vector2(self.rect.topleft)
# we want to move the Opponent in a specific pattern
# so let's keep a list of points we want to move to
# 'cycle' will generate and "endless loop" of this points
self.path = itertools.cycle(((445, 30), (345, 235), (90, 115), (490, 80), (850, 250), (745, 110)))
# we can use 'next' to get the next position
self.next_point = pygame.Vector2(next(self.path))
# maybe we want to change the speed of the opponent
self.speed = 1
# we use ticks to store the milliseconds since the game started later
# this allows us to do thing over time
self.ticks = 1000
# a list of bullets we want to shoot later
self.queue = []
def update(self):
# so we want to move to a specific point
# we use some vector math to get the direction we have to move to
move = self.next_point - self.pos
move_length = move.length()
if move_length != 0:
# since 'move' is the vector between 'pos' and 'next_point'
# we have to normalize it so it just points into the right
# direction at a certain length instead of all the way from
# 'pos' and 'next_point'
move.normalize_ip()
move = move * self.speed
self.pos += move
# if we are already at the target position (or overshoot it)
# we take the next position from 'path'
if move.length() == 0 or move_length < self.speed:
self.next_point = pygame.Vector2(next(self.path))
# we update the 'rect' position so pygame draws the sprite at the right position on the screen
self.rect.topleft = self.pos
# so let's count some time. Every 3000ms passed, we want to shoot some bullets
if pygame.time.get_ticks() - self.ticks > 3000:
self.ticks = pygame.time.get_ticks()
self.shoot()
# see how much time passed since the last shooting
time_gone = pygame.time.get_ticks() - self.ticks
for bullet in self.queue:
# the first value of the tuples in the 'queue' describes when to shoot the bullet
if bullet[0] <= time_gone:
# create the bullet and add them to the 'sprites' and 'bullets' groups
# 'bullets' isn't used yet, but you can use it later for collision detection
Bullet(self.rect.center, bullet[1], sprites, bullets)
# now remove all bullets that have been fired
self.queue = [bullet for bullet in self.queue if bullet[0] > time_gone]
def shoot(self):
bullet_speed = 4
# this list describes the pattern of the attack
# the first value is the time when to shoot the bullet (afer X ms)
# the second value is the movement vector of the bullet
pattern = ((0, pygame.Vector2(-0.5, 1) * bullet_speed),
(0, pygame.Vector2( 0, 1) * bullet_speed),
(0, pygame.Vector2(0.5, 1) * bullet_speed),
(150, pygame.Vector2(-0.5, 1) * bullet_speed),
(150, pygame.Vector2( 0, 1) * bullet_speed),
(150, pygame.Vector2(0.5, 1) * bullet_speed),
(300, pygame.Vector2(-0.5, 1) * bullet_speed),
(300, pygame.Vector2( 0, 1) * bullet_speed),
(300, pygame.Vector2(0.5, 1) * bullet_speed))
self.queue = pattern
class Bullet(pygame.sprite.Sprite):
sprite = pygame.image.load("Sprites/purple-glowey.png")
def __init__(self, pos, direction, *groups):
super().__init__(*groups)
self.image = Bullet.sprite
self.rect = self.image.get_rect(topleft=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
# just move along the direction
self.pos += self.direction
self.rect.topleft = (self.pos.x, self.pos.y)
# if no longer on screen, remove from all groups
if not screen.get_rect().colliderect(self.rect):
self.kill()
sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
player = Player(sprites)
Minty = Opponent(pygame.image.load("Sprites/minty.png"), sprites)
def main():
running = True
while running:
for events in pygame.event.get():
if events.type == pygame.QUIT:
return
# update all sprites, a.k.a. game logic
sprites.update()
# draw everything
screen.blit(background, (0, 0))
sprites.draw(screen)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
Upvotes: 3
Reputation: 6269
Your problem is in the BulletGroup
class: you keep appending the same object to the list, so instead of having a list with 5 bullets like you expect, you actually have a list of 5 references to the same bullet.
When you run over this list and change the coordinates, you are changing the same bullet and drawing the same bullet.
You should use copy
to duplicate your first bullet.
Upvotes: 1