Removing enemies

I am making a space shooter. Enemies spawn at top of the screen, and when(if) they get to the bottom I want to unload them. I wrote a method similar to one from asteroids example:
-loads enemy as local variable and passes it to a list

After that in update task I move enemies and check which enemies have passed the remove border. Enemies who have done so are added to another list. Enemy list = Enemy list - Remove list. At the next frame I set remove list to empty.
So every reference to enemy who have crossed the line is removed, and it is also removed. But they don’t get removed, they are still rendered, just not updated.

Here is the code. Sry for not commenting everything, but didn’t start commenting it from beginning, and it is a bit long and unreadable.

#Import libraries
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import VBase4, VBase3, TransparencyAttrib
from panda3d.core import AmbientLight, DirectionalLight, Vec4, Vec3
import sys
import random

class Astro(ShowBase):


	def __init__(self):
		#First line must be all 0s, or first line of enemies will spawn early. Not sure why it happens, but not gonna bang my head a lot over it.
		self.level1 = [[0,0,0,0,0,0,0,0,0],
		               [1,1,1,1,1,1,1,1,1],
					   [0,1,0,1,1,0,1,0,0],
					   [0,0,1,0,0,1,0,0,0]] #Level layout: 0-don't spawn anything, 1-spawn Spaceship, 2-HVF
		ShowBase.__init__(self)
		
		#Load Textures for faster loading when enemy requests it(not sure if ti works)
		self.tex = []
		for i in range(1,22):
			Tex = self.loader.loadTexture("Models/Enemies/Trox/Tex"+str(i)+".jpg")
			self.tex.append(Tex)
		
		#Load backgrounds
		self.background1 = self.loader.loadModel("Models/Background/Background.egg")
		self.background2 = self.loader.loadModel("Models/Background/Background.egg")
		self.background1.setPos(0,0,0)
		self.background2.setPos(0,32,0)
		self.background1.reparentTo(self.render)
		self.background2.reparentTo(self.render)
		base.oobe()
		
		#Load player model
		self.player = self.loader.loadModel("Models/Player/Player.egg")
		self.player.setPos(0,-10,1)
		self.player.reparentTo(self.render)
		
		#Load player laser
		self.laser = self.loader.loadModel("Models/Player/Laser/Laser.egg")
		self.laser.setSy(0)
		self.laser.setPos(self.player,0,0.95,-0.2)
		self.laser.reparentTo(self.render)
		
		#Camera stuff
		base.disableMouse()
		self.camera.setPos(0,0,45)
		self.camera.setHpr(0,-90,0)
		
		#Update Task
		self.taskMgr.add(self.updateTask, "update")
		
		#Keyboard setup
		self.keyboardSetup()
		
		#Lights
		self.createEnvironment()
		#base.oobe()
		
		
		self.firetime = 1 #Time since last fire
		self.firelength = 0#Length of fire
		self.elapsedfire = 0#Used this to disable constant space pressing hack
		self.line = 0#Map line used to spawn enemies
		self.spawn = 0#If it is 1 spawn enemies
		self.oldt=0#Used to check if enough time has passed since last spawn
		self.HVFlist = []#List of High Velocity Fighter enemies
		self.shiplist = []
		
	def keyboardSetup(self):
		self.keyMap = {"left":0, "right":0, "forward":0, "backward":0, "fire":0}
		
		self.accept("escape", sys.exit)
		self.accept("arrow_left", self.setKey, ["left",1])
		self.accept("arrow_left-up", self.setKey, ["left",0])
		self.accept("arrow_right", self.setKey, ["right",1])
		self.accept("arrow_right-up", self.setKey, ["right",0])
		self.accept("arrow_up", self.setKey, ["forward",1])
		self.accept("arrow_up-up", self.setKey, ["forward",0])
		self.accept("arrow_down", self.setKey, ["backward",1])
		self.accept("arrow_down-up", self.setKey, ["backward",0])
		self.accept("space", self.setKey, ["fire",1])
		self.accept("space-up", self.setKey, ["fire",0])
	#If spawn is enabled and map didn't come to the end, spawn enemy that is in the list
	def loadEnemies(self):
		if (self.spawn==1 and self.line<=len(self.level1)):
			for n in range(9):
				if self.level1[self.line-1][n-1] == 2:
					HVF = self.loader.loadModel("Models/Enemies/HVF/fighter.egg")
					self.HVFlist.append(HVF)
					HVF.setPos(3.5*n-14,15,2)
					HVF.setScale(0.1)
					HVF.reparentTo(self.render)
				if self.level1[self.line-1][n-1] == 1:
					ship = self.loader.loadModel("Models/Enemies/Trox/ship.egg")
					tex = self.loader.loadTexture("Models/Enemies/Trox/Tex"+str(random.randrange(1,23))+".jpg")
					ship.setTexture(tex,1)
					ship.setScale(1.25)
					self.shiplist.append(ship)
					ship.setPos(3.5*n-14,15,1)
					ship.reparentTo(self.render)
					
	def updateEnemies(self):
		self.shipremove = []
		for i in range(len(self.HVFlist)):
			self.HVFlist[i-1].setY(self.HVFlist[i-1].getY()-3*self.time)
		for i in range(len(self.shiplist)):
			self.shiplist[i-1].setY(self.shiplist[i-1].getY()-10*self.time)
			if self.shiplist[i-1].getY()<-13:
				self.shipremove.append(self.shiplist[i-1])
		self.shiplist = list(set(self.shiplist)-set(self.shipremove))
				

	#Lights
	def createEnvironment(self):
		#Creates ambient light for brighter shadows
		ambientLight = AmbientLight("ambientLight")
		ambientLight.setColor(Vec4(.6, .6, .6, 1))
		self.render.setLight(self.render.attachNewNode(ambientLight))
		#Creates directional light to light up the player
		directionalLight = DirectionalLight("directionalLight")
		directionalLight.setColor(VBase4(0.8, 0.8, 0.5, 1))
		dlnp = self.render.attachNewNode(directionalLight)
		dlnp.setPos(0,0,260)
		dlnp.lookAt(self.player)
		self.render.setLight(dlnp)
		
	def setKey(self, key, value):
		self.keyMap[key] = value
		
	#Update task
	def updateTask(self, task):
		self.time = (globalClock.getDt() * 1)#Frame independent time
		self.oldt+=self.time#Spawn timer
		if self.oldt>=2:#Spawn timer check
			self.oldt=0
			self.line+=1
			self.spawn=1
		else:
			self.spawn=0
		self.updateBack()
		self.updatePlayer()
		self.updateLaser()
		self.loadEnemies()
		self.updateEnemies()
		return Task.cont
	
		

	#Making the backgrounds tile
	def updateBack(self):
		speed = 1
		
		self.background1.setY(self.background1.getY() - self.time * speed)
		self.background2.setY(self.background2.getY() - self.time * speed)
		
		if (self.background1.getY() < -30):
			self.background1.setY(self.background2.getY() + 32)
		if (self.background2.getY() < -30):
			self.background2.setY(self.background1.getY() + 32)
			
	#Making the player respond to controls
	def updatePlayer(self):
		speed = 10
		bankspeed = 45
		
		#If the player presses left arrow make him go left and rotate the ship
		if (self.keyMap["left"]!=0):
			if self.player.getX()>-14.5:
				self.player.setX(self.player.getX()-speed*self.time)
				if self.player.getX()<-14.5:
					self.player.setX(-14.5)
			self.player.setR(self.player.getR()-bankspeed*self.time)
			if (self.player.getR() < -30):
				self.player.setR(-30)
		#Ditto for the right arrow		
		elif (self.keyMap["right"]!=0):
			if self.player.getX()<14.5:
				self.player.setX(self.player.getX()+speed*self.time)
				if self.player.getX()>14.5:
					self.player.setX(14.5)
			self.player.setR(self.player.getR()+bankspeed*self.time)
			if (self.player.getR() > 30):
				self.player.setR(30)
		#If neither right nor left arrow are pressed auto return		
		elif (self.player.getR() > 0):
				self.player.setR(self.player.getR()-(bankspeed+1)*self.time)
				if (self.player.getR() < 0):
					self.player.setR(0)
		elif (self.player.getR() < 0):
				self.player.setR(self.player.getR()+(bankspeed+1)*self.time)
				if (self.player.getR() > 0):
					self.player.setR(0)
		#Go forward
		if self.keyMap["forward"] != 0:
			if self.player.getY()<11:
				self.player.setY(self.player.getY() + self.time * speed)
				if self.player.getY()>11:
					self.player.setY(11)
		#Go backward
		elif self.keyMap["backward"] != 0:
			if self.player.getY()> -11:
				self.player.setY(self.player.getY() - self.time * speed)
				if self.player.getY()< -11:
					self.player.setY(-11)
	#Firing laser
	def updateLaser(self):
		self.firetime += self.time
		self.laser.setPos(self.player,0,0.95,-0.2)
		if (self.keyMap["fire"] != 0 and self.firetime >= 0.5):
			self.laser.setSy(25)
			self.firelength += self.time
			if self.firelength > 1:
				self.firetime = 0
				self.firelength = 0
		else:
			self.laser.setSy(0)
			self.elapsedfire += self.time
	
app = Astro()
app.run()

im guessing this is where your updating and removing your ships?

   def updateEnemies(self):
      self.shipremove = []
      for i in range(len(self.HVFlist)):
         self.HVFlist[i-1].setY(self.HVFlist[i-1].getY()-3*self.time)
      for i in range(len(self.shiplist)):
         self.shiplist[i-1].setY(self.shiplist[i-1].getY()-10*self.time)
         if self.shiplist[i-1].getY()<-13:
            self.shipremove.append(self.shiplist[i-1])
      self.shiplist = list(set(self.shiplist)-set(self.shipremove)) 

personally, if I’m removing something from a list id use list.pop(indexPosition) instead of creating a new list to tell it to remove from, but thats your decision.

From what I can tell, your removing the ship from the list of the nodes but not from the renderer, which is done from ship.removeNode()

   def updateEnemies(self):
      for i in range(len(self.HVFlist)):
         self.HVFlist[i-1].setY(self.HVFlist[i-1].getY()-3*self.time)
      for i in range(len(self.shiplist)):
         self.shiplist[i-1].setY(self.shiplist[i-1].getY()-10*self.time)
         if self.shiplist[i-1].getY()<-13:
            removedShip = self.shiplist.pop(i-1)
            removedShip.removeNode()

Yep, I am removing and updating them there. Gonna give it an estetic rewrite after I finish it. I am using lists to remove for one reason: for loop, I can’t remove items from list I am iterating over, or I will risk an error because of index change. So I write items to another list, and outside of the loop I remove them from main list, and now thx to you from the render, too. Here is the working piece of code. Thank you.

for i in range(len(self.shiplist)):
			self.shiplist[i-1].setY(self.shiplist[i-1].getY()-10*self.time)
			if self.shiplist[i-1].getY()<-13:
				self.shipremove.append(self.shiplist[i-1])
		self.shiplist = list(set(self.shiplist)-set(self.shipremove))
		for i in range(len(self.shipremove)):
			self.shipremove[i-1].removeNode()
		self.shipremove = []

I can clean that up some more. A few Python tips:

  • ranges start at 0, so all of those [i-1] are wrong. The code still works because a negative index is counted from the end backwards.
  • There is no need to use ranges at all in this case. You can iterate over elements of a list directly.
  • The special notation list[:] makes a copy of the list, so that we can modify the original within the loop.
  • There is no need to store this temporary list in a variable or erase it. It will simply go away when the function exits.
def updateEnemies(self):
	for ship in self.shiplist[:]:
		ship.setY(ship.getY()-10*self.time)
		if ship.getY()<-13:
			self.shiplist.remove(ship)
			ship.removeNode()
			

:open_mouth: So simple. :open_mouth:
Code looks much cleaner now. This is gonna save a lot of my time when I add more enemies and collision detection. Now I can store all of the enemies in one list.

I thought that ranges go [1,x], but I now see that they go [0,x). Thanks for clearing it up.