Moving Sprites across a screen

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:

enter image description here

but here's how it is:

enter image description here

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

Answers (2)

sloth
sloth

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:

  • event handling
  • update game state
  • draw everything

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()

enter image description here

Upvotes: 3

Lev M.
Lev M.

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

Related Questions