Python Forum
[PyGame] multiple copies of the same object issue
Thread Rating:
  • 1 Vote(s) - 1 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] multiple copies of the same object issue
#1
I am writing a simple side scrolling game in the style of the old Scramble arcade game, except with lousy graphics! I have a background composed of ground objects, which are blue rectangles of varying height. Atop them are either nothing, a turret (which will fire eventually!!) or a surface missile. I have used the range_to_fire code from the shipgame demo on here to check whether to launch a missile. There is a player ship (which is a simple triangle) and it can fire a laser or a missile. The player missile uses the formula for motion of a projectile (from here, as I couldn't remember them lol: https://formulas.tutorvista.com/physics/...rmula.html) As you can see I plan to use the state engine code eventually but just wanted it working first. Implementing the state engine code was pretty easy last time, and I am more in the habit of writing it with the update/collision_check/draw loop style as well now.

Currently it just draws a random background and uses randint to decide whether a pillar has nothing, a missile, or a turret.

Keys are cursor keys = move, space = laser, v = missile

So my current issues are:

1) The surface missiles launch, but leave a copy atop the pillar

2) the player missile has multiple copies as it drops down.

There are 2 files: constants.py and game_test.py:

import pygame

size = width, height = 600, 400

TOP = 5
LEFT = 5
BASE = 380
RIGHT = 570

# colors

blue = (0,0,255)
black = (0,0,0)
white = (255,255,255)
yellow = (238, 238, 0)

red = (255,0,0)
orange	 = (255, 128, 0)
yellow1 = (255, 255, 0)
green	 = (0, 255, 0)
blue2 = (0, 0, 238)
purple	 = (128, 0, 128)
magenta3 = (205, 0, 205)	
peachpuff1 = (255, 218, 185)

# unused at the minute
LEFT_KEYS = [pygame.K_LEFT, pygame.K_z]
RIGHT_KEYS = [pygame.K_RIGHT, pygame.K_x]
UP_KEYS = [pygame.K_UP, pygame.K_p]
DOWN_KEYS = [pygame.K_DOWN, pygame.K_l]
import pygame
from pygame.locals import *
from random import randint
import sys, os, math

from constants import *
from state_test import States

pygame.init()
pygame.display.set_caption("Shooter")
screen = pygame.display.set_mode(size)

#pygame.key.set_repeat(10, 50)
clock = pygame.time.Clock() 

class Game: #(States):

	def __init__(self):
		#States.__init__(self)
		self.next = 'title'
		self.screen = screen
		self.level = Background()
		self.ship = Ship(20,30)
		
		self.scroll_timer = 3
			
	def run(self):
	
		done = False
		while not done:
			
			dt = clock.tick(60)/1000.0
			
			self.ship.update(dt)
	
			if self.ship.laser.firing:
				self.ship.laser.update()
			
			#for missile in self.ship.missiles:
			if self.ship.missile:
				if self.ship.missile.launched:
					self.ship.missile.update()
			
			for missile in self.level.missiles:
				if self.player_in_range(missile, self.ship):
					missile.launched = True
				if missile.launched:
					missile.update()				
		
			self.scroll_timer -= 1
			if self.scroll_timer == 0:
				self.scroll_timer = 3
				self.level.display_rect.x += 2
				
				for bg in self.level.ground:
					bg.rect.x -= 2
				
				for turret in self.level.turrets:
					turret.rect.x -= 2
				
				for missile in self.level.missiles:
					missile.rect.x -= 2
		
			self.collision_check()
						
			self.draw(screen)

			for event in pygame.event.get():
				if event.type == pygame.QUIT:
					done = True
					
				if event.type == pygame.KEYDOWN:
					if event.key == pygame.K_q:
						sys.exit()
	
	def collision_check(self):
		for bg in self.level.ground:
			if self.ship.rect.colliderect(bg.rect):
				print "ship hit background"
			if self.ship.laser.firing:
				if self.ship.laser.rect.colliderect(bg.rect):
					print "laser hit background"
					self.ship.laser.reset()
		
		if self.ship.laser.firing:
			for turret in self.level.turrets:
				if self.ship.laser.rect.colliderect(turret.rect):
					print "laser hit turret"
					self.ship.laser.reset()
					turret.alive = False
					
	def draw(self, surf):
		surf.fill((0,0,0))
		
		surf.blit(self.level, (0,0), self.level.display_rect)
		self.ship.draw(surf)
		
		if self.ship.laser.firing:
			self.ship.laser.draw(surf)
		
		#for missile in self.ship.missiles:
		if self.ship.missile:
			if self.ship.missile.launched:
				self.ship.missile.draw(surf)
			
		for missile in self.level.missiles:
			if missile.launched:
				missile.draw(surf)
				
		pygame.display.flip()
		
	def player_in_range(self, missile, player):
		try:
			offset_x =  missile.rect.x - player.rect.x
			offset_y =  missile.rect.y - player.rect.y
			d = int(math.degrees(math.atan(offset_x / offset_y)))
		except ZeroDivisionError: #player is above enemy
			return False
		
		#if player is within 15 degrees of enemy
		if math.fabs(d) <= 15: 
			print "above"
			return True
		else:
			return False

class Ship:

	def __init__(self, x = 0, y = 0):
		self.dx = 300
		self.dy = 300
		self.rect = pygame.Rect(x,y,30,20)
		
		self.laser = Laser(x, y)
		self.missiles = []
		self.missile = None
		
	def update(self, dt):

		keys = pygame.key.get_pressed()
			
		if keys[K_SPACE]:	
			self.laser.firing = True
			self.laser.rect.x = self.rect.x+30
			self.laser.rect.y = self.rect.y+15
	
		if keys[K_v]:
			#m = PlayerMissile(self.rect.x, self.rect.y)
			#m.launched = True
			#self.missiles.append(m)
			self.missile = PlayerMissile(self.rect.x, self.rect.y)
			self.missile.launched = True
			#self.missiles.append(m)
			
		if keys[pygame.K_LEFT]:
			if self.rect.x > TOP:
				self.laser.firing = False
				self.rect.x -= self.dx * dt
				self.laser.rect.x = self.rect.x #+30
				self.laser.ex = self.rect.x + 5
		if keys[pygame.K_RIGHT]:
			if self.rect.x < RIGHT:
				self.laser.firing = False
				self.rect.x += self.dx * dt
				self.laser.rect.x = self.rect.x #+30
				self.laser.ex = self.rect.x + 5
		if keys[pygame.K_UP]:
			if self.rect.y > LEFT:
				self.laser.firing = False
				self.rect.y -= self.dy * dt
				self.laser.rect.y = self.rect.y #+15
		if keys[pygame.K_DOWN]:
			if self.rect.y < BASE:
				self.laser.firing = False
				self.rect.y += self.dy * dt
				self.laser.rect.y = self.rect.y #+15
			
	def draw(self, surf):
		pygame.draw.polygon(surf, white, [[self.rect.x,self.rect.y], [self.rect.x+30, self.rect.y+15], [self.rect.x, self.rect.y+20]], 5)
  
class Laser:
 	
	def __init__(self, x = 0, y = 0):
		self.ex = x + 5
		self.speed = 20
		self.rect = pygame.Rect(x, y, 5, 2)
		self.max_len = 600 - x
		
		self.firing = False

	def update(self):
		if self.firing:
			if self.ex < self.max_len - self.speed:
				self.ex += self.speed
				self.rect.width = self.ex-self.rect.x
			else:
				self.reset()
				
	def reset(self):
		self.firing = False
		self.ex = self.rect.x+5
		self.rect.width = self.ex-self.rect.x

	def draw(self, surf):
		pygame.draw.line(surf, (255,0,255), (self.rect.x, self.rect.y), (self.ex, self.rect.y))
		
class Turret:

	def __init__(self, x=0, y=0):
		self.cx = x+15
		self.cy = y+10
		self.direction = LEFT
		self.rect = pygame.Rect(x, y, 30, 30)
		self.alive = True
		
	def draw(self, surf):
		pygame.draw.line(surf, yellow, (self.rect.x, self.rect.y), (self.cx, self.cy), 4)
		
		if self.alive:
			pygame.draw.arc(surf, red, self.rect, 0, 3.14, 10)
		else:
			pygame.draw.arc(surf, yellow, self.rect, 0, 3.14, 10)
	
class SurfaceMissile:

	def __init__(self, x=0, y=0):
		self.rect = pygame.Rect(x,y,10,30)	
		self.alive = True
		self.launched = False
		
	def update(self):
		if self.alive:
			self.rect.y-= 2
		
	def draw(self, surf):
		pygame.draw.rect(surf, orange, self.rect)

class PlayerMissile:

	def __init__(self, x=0, y=0):
		self.rect = pygame.Rect(x,y,20,20)	
		self.alive = True
		self.launched = False
		self.sx = 6
		self.sy = 0
		self.time = 0
		self.g = 2.0
		
	def update(self):
		if self.alive and self.launched:
			self.time += 1
			self.rect.x += (self.time * self.sx)
			self.rect.y += (self.g/2 * self.time**2)
		
	def draw(self, surf):
		pygame.draw.rect(surf, purple, self.rect)
				
class Ground:

	def __init__(self, x=0, y=0, height=40, color=blue):
		self.width = 40
		self.height = height
		self.rect = pygame.Rect(x, y, self.width, self.height)
		self.turret = None
		self.missile = None
		
		if randint(0,10) > 7:
			self.turret = Turret(x+5, y-15)
		
		if not self.turret:
			if randint(0,10) > 5:
				self.missile = SurfaceMissile(x+5, y-30)
				
	def update(self, amt):
		self.rect.x -= amt
		
	def draw(self, surf):
		if self.turret:
			self.turret.draw(surf)
		if self.missile and not self.missile.launched:
			self.missile.draw(surf)
			
		pygame.draw.rect(surf, blue, self.rect)
			
class Background(pygame.Surface):
	
	def __init__(self):
		super(Background, self).__init__((6000, 400))
		
		self.display_rect = pygame.Rect(0,0,600,400)
		self.ground = []
		self.turrets = []
		self.missiles = []
		
		for c in range(5):
			g = Ground(c*40, 400-40, 40)
			self.ground.append(g)
			g.draw(self)
			
		for c in range(5, 130):
			height = randint(20, 200)
			g = Ground(c*40, 400-height, height) #c*40, 400-height, height)
			self.ground.append(g)
			g.draw(self)
			
			if g.turret:
				self.turrets.append(g.turret)
			
			if g.missile:
				self.missiles.append(g.missile)
				
if __name__== "__main__":
	g = Game()
	g.run()
	pygame.quit()
	sys.exit()
Reply
#2
Your code appears to be all over the place. Currently you have the same Surface missle being drawn on the ground in Ground class, while when its in the air its being drawn by the Game class? Its drawing them at the same time creating an appearance of a copy.

Here is the missle being drawn in the air
Quote:
        for missile in self.level.missiles:
            if missile.launched:
                pass#missile.draw(surf)
and here is the missle being drawn on the ground
Quote:
        if self.missile and not self.missile.launched:
            pass#self.missile.draw(surf)
and they are in 2 different classes (Ground and Game).
Quote:
        self.level = Background()
and those level missles revert back to yet another class where the missles list is actually resides apparently.
Quote:
        self.missiles = []

Each single object should have only one draw command regardless of where it is on the screen. All its logic and data should reside in one class. This code is all over the place its really hard to help without devoting a lot of time in retracing your entire code. And i dont have that much time. im just scanning the entire thing and seeing what jumps out at me.

You could of also meshed those two files together and made it a lot easier for people to copy and paste the code to run to try to help like this
import pygame
from pygame.locals import *
from random import randint
import sys, os, math

#from state_test import States

size = width, height = 600, 400
 
TOP = 5
LEFT = 5
BASE = 380
RIGHT = 570
 
# colors
 
blue = (0,0,255)
black = (0,0,0)
white = (255,255,255)
yellow = (238, 238, 0)
 
red = (255,0,0)
orange   = (255, 128, 0)
yellow1 = (255, 255, 0)
green    = (0, 255, 0)
blue2 = (0, 0, 238)
purple   = (128, 0, 128)
magenta3 = (205, 0, 205)    
peachpuff1 = (255, 218, 185)
 
# unused at the minute
LEFT_KEYS = [pygame.K_LEFT, pygame.K_z]
RIGHT_KEYS = [pygame.K_RIGHT, pygame.K_x]
UP_KEYS = [pygame.K_UP, pygame.K_p]
DOWN_KEYS = [pygame.K_DOWN, pygame.K_l]
 
pygame.init()
pygame.display.set_caption("Shooter")
screen = pygame.display.set_mode(size)
 
#pygame.key.set_repeat(10, 50)
clock = pygame.time.Clock() 
 
class Game: #(States):
 
    def __init__(self):
        #States.__init__(self)
        self.next = 'title'
        self.screen = screen
        self.level = Background()
        self.ship = Ship(20,30)
         
        self.scroll_timer = 3
             
    def run(self):
     
        done = False
        while not done:
             
            dt = clock.tick(60)/1000.0
             
            self.ship.update(dt)
     
            if self.ship.laser.firing:
                self.ship.laser.update()
             
            #for missile in self.ship.missiles:
            if self.ship.missile:
                if self.ship.missile.launched:
                    self.ship.missile.update()
             
            for missile in self.level.missiles:
                if self.player_in_range(missile, self.ship):
                    missile.launched = True
                if missile.launched:
                    missile.update()                
         
            self.scroll_timer -= 1
            if self.scroll_timer == 0:
                self.scroll_timer = 3
                self.level.display_rect.x += 2
                 
                for bg in self.level.ground:
                    bg.rect.x -= 2
                 
                for turret in self.level.turrets:
                    turret.rect.x -= 2
                 
                for missile in self.level.missiles:
                    missile.rect.x -= 2
         
            self.collision_check()
                         
            self.draw(screen)
 
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    done = True
                     
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:
                        sys.exit()
     
    def collision_check(self):
        for bg in self.level.ground:
            if self.ship.rect.colliderect(bg.rect):
                print "ship hit background"
            if self.ship.laser.firing:
                if self.ship.laser.rect.colliderect(bg.rect):
                    print "laser hit background"
                    self.ship.laser.reset()
         
        if self.ship.laser.firing:
            for turret in self.level.turrets:
                if self.ship.laser.rect.colliderect(turret.rect):
                    print "laser hit turret"
                    self.ship.laser.reset()
                    turret.alive = False
                     
    def draw(self, surf):
        surf.fill((0,0,0))
         
        surf.blit(self.level, (0,0), self.level.display_rect)
        self.ship.draw(surf)
         
        if self.ship.laser.firing:
            self.ship.laser.draw(surf)
         
        #for missile in self.ship.missiles:
        if self.ship.missile:
            if self.ship.missile.launched:
                self.ship.missile.draw(surf)
             
        for missile in self.level.missiles:
            if missile.launched:
                pass#missile.draw(surf)
                 
        pygame.display.flip()
         
    def player_in_range(self, missile, player):
        try:
            offset_x =  missile.rect.x - player.rect.x
            offset_y =  missile.rect.y - player.rect.y
            d = int(math.degrees(math.atan(offset_x / offset_y)))
        except ZeroDivisionError: #player is above enemy
            return False
         
        #if player is within 15 degrees of enemy
        if math.fabs(d) <= 15: 
            print "above"
            return True
        else:
            return False
 
class Ship:
 
    def __init__(self, x = 0, y = 0):
        self.dx = 300
        self.dy = 300
        self.rect = pygame.Rect(x,y,30,20)
         
        self.laser = Laser(x, y)
        self.missiles = []
        self.missile = None
         
    def update(self, dt):
 
        keys = pygame.key.get_pressed()
             
        if keys[K_SPACE]:   
            self.laser.firing = True
            self.laser.rect.x = self.rect.x+30
            self.laser.rect.y = self.rect.y+15
     
        if keys[K_v]:
            #m = PlayerMissile(self.rect.x, self.rect.y)
            #m.launched = True
            #self.missiles.append(m)
            self.missile = PlayerMissile(self.rect.x, self.rect.y)
            self.missile.launched = True
            #self.missiles.append(m)
             
        if keys[pygame.K_LEFT]:
            if self.rect.x > TOP:
                self.laser.firing = False
                self.rect.x -= self.dx * dt
                self.laser.rect.x = self.rect.x #+30
                self.laser.ex = self.rect.x + 5
        if keys[pygame.K_RIGHT]:
            if self.rect.x < RIGHT:
                self.laser.firing = False
                self.rect.x += self.dx * dt
                self.laser.rect.x = self.rect.x #+30
                self.laser.ex = self.rect.x + 5
        if keys[pygame.K_UP]:
            if self.rect.y > LEFT:
                self.laser.firing = False
                self.rect.y -= self.dy * dt
                self.laser.rect.y = self.rect.y #+15
        if keys[pygame.K_DOWN]:
            if self.rect.y < BASE:
                self.laser.firing = False
                self.rect.y += self.dy * dt
                self.laser.rect.y = self.rect.y #+15
             
    def draw(self, surf):
        pygame.draw.polygon(surf, white, [[self.rect.x,self.rect.y], [self.rect.x+30, self.rect.y+15], [self.rect.x, self.rect.y+20]], 5)
   
class Laser:
     
    def __init__(self, x = 0, y = 0):
        self.ex = x + 5
        self.speed = 20
        self.rect = pygame.Rect(x, y, 5, 2)
        self.max_len = 600 - x
         
        self.firing = False
 
    def update(self):
        if self.firing:
            if self.ex < self.max_len - self.speed:
                self.ex += self.speed
                self.rect.width = self.ex-self.rect.x
            else:
                self.reset()
                 
    def reset(self):
        self.firing = False
        self.ex = self.rect.x+5
        self.rect.width = self.ex-self.rect.x
 
    def draw(self, surf):
        pygame.draw.line(surf, (255,0,255), (self.rect.x, self.rect.y), (self.ex, self.rect.y))
         
class Turret:
 
    def __init__(self, x=0, y=0):
        self.cx = x+15
        self.cy = y+10
        self.direction = LEFT
        self.rect = pygame.Rect(x, y, 30, 30)
        self.alive = True
         
    def draw(self, surf):
        pygame.draw.line(surf, yellow, (self.rect.x, self.rect.y), (self.cx, self.cy), 4)
         
        if self.alive:
            pygame.draw.arc(surf, red, self.rect, 0, 3.14, 10)
        else:
            pygame.draw.arc(surf, yellow, self.rect, 0, 3.14, 10)
     
class SurfaceMissile:
 
    def __init__(self, x=0, y=0):
        self.rect = pygame.Rect(x,y,10,30)  
        self.alive = True
        self.launched = False
         
    def update(self):
        if self.alive:
            self.rect.y-= 2
         
    def draw(self, surf):
        pygame.draw.rect(surf, orange, self.rect)
 
class PlayerMissile:
 
    def __init__(self, x=0, y=0):
        self.rect = pygame.Rect(x,y,20,20)  
        self.alive = True
        self.launched = False
        self.sx = 6
        self.sy = 0
        self.time = 0
        self.g = 2.0
         
    def update(self):
        if self.alive and self.launched:
            self.time += 1
            self.rect.x += (self.time * self.sx)
            self.rect.y += (self.g/2 * self.time**2)
         
    def draw(self, surf):
        pygame.draw.rect(surf, purple, self.rect)
                 
class Ground:
 
    def __init__(self, x=0, y=0, height=40, color=blue):
        self.width = 40
        self.height = height
        self.rect = pygame.Rect(x, y, self.width, self.height)
        self.turret = None
        self.missile = None
         
        if randint(0,10) > 7:
            self.turret = Turret(x+5, y-15)
         
        if not self.turret:
            if randint(0,10) > 5:
                self.missile = SurfaceMissile(x+5, y-30)
                 
    def update(self, amt):
        self.rect.x -= amt
         
    def draw(self, surf):
        if self.turret:
            self.turret.draw(surf)
        if self.missile and not self.missile.launched:
            pass#self.missile.draw(surf)
             
        pygame.draw.rect(surf, blue, self.rect)
             
class Background(pygame.Surface):
     
    def __init__(self):
        super(Background, self).__init__((6000, 400))
         
        self.display_rect = pygame.Rect(0,0,600,400)
        self.ground = []
        self.turrets = []
        self.missiles = []
         
        for c in range(5):
            g = Ground(c*40, 400-40, 40)
            self.ground.append(g)
            g.draw(self)
             
        for c in range(5, 130):
            height = randint(20, 200)
            g = Ground(c*40, 400-height, height) #c*40, 400-height, height)
            self.ground.append(g)
            g.draw(self)
             
            if g.turret:
                self.turrets.append(g.turret)
             
            if g.missile:
                self.missiles.append(g.missile)
                 
if __name__== "__main__":
    g = Game()
    g.run()
    pygame.quit()
    sys.exit()
Recommended Tutorials:
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020