import bge # Blender Game Engine (UPBGE) import aud # Sounds import threading # Multithreading import trace import sys import time import math import mathutils import random ############################################################################### # ct_lib.py # @title: User library # @project: CodeTower # @lang: fr,en # @authors: Philippe Roy # @copyright: Copyright (C) 2022 Philippe Roy # @license: GNU GPL # # This game is a tower defense coding game. The towers are driven by Python code. # Ce simulateur est un jeu du type tower defense où les tours sont à piloter par la programmation Python. # # Commands trigged by button : cmd_* # Commands trigged by 3D scene objects : scn_* # Commands trigged by user (student or map designer) : ct_* # 3D scene manipulation : manip_* # ############################################################################### scene = bge.logic.getCurrentScene() # Colors tower_purple = (0.202, 0.114, 0.521,1) tower_turquoise = (0.051, 0.270, 0.279,1) tower_magenta = (0.799, 0.005, 0.314,1) tower_orange = (0.799, 0.130, 0.063,1) tower_yellow = (0.799, 0.617, 0.021, 1) tower_green = (0.246, 0.687, 0.078, 1) tower_red = (0.799, 0.031, 0.038, 1) tower_blue = (0.127, 0.456, 1.000, 1) tower_black = (0, 0, 0, 1) color_text = (0, 0, 0, 1) # Noir color_text_red = (0.799, 0.031, 0.038, 1) color_text_orange = (0.799, 0.176, 0.054, 1) color_text_yellow = (0.799, 0.617, 0.021, 1) ray_yellow = (0.799, 0.617, 0.021, 1) # [0.8, 0.619, 0.021]) ray_blue = (0.127, 0.456, 1.000, 1) ray_black = (0, 0, 0, 1) color_kaykit_black = (0.019, 0.032, 0.037, 1) # Sounds audiodev = aud.Device() snd_build = aud.Sound('asset/sounds/build.wav') sndbuff_build = aud.Sound.cache(snd_build) snd_archer = aud.Sound('asset/sounds/archer.wav') sndbuff_archer = aud.Sound.cache(snd_archer) snd_mage = aud.Sound('asset/sounds/mage.wav') sndbuff_mage = aud.Sound.cache(snd_mage) snd_life = aud.Sound('asset/sounds/life.ogg') sndbuff_life = aud.Sound.cache(snd_life) threads_waves=[] threads_cmd=[] # UPBGE constants JUST_ACTIVATED = bge.logic.KX_INPUT_JUST_ACTIVATED JUST_RELEASED = bge.logic.KX_INPUT_JUST_RELEASED ACTIVATE = bge.logic.KX_INPUT_ACTIVE # JUST_DEACTIVATED = bge.logic.KX_SENSOR_JUST_DEACTIVATED ############################################################################### # Données générales ############################################################################### def ct_level_current(): return scene.objects['Points']['level'] def ct_level(): return scene.objects['Points']['level_max'] ############################################################################### # Méthode kill pour les tâches (threads) ############################################################################### class thread_with_trace(threading.Thread): def __init__(self, *args, **keywords): threading.Thread.__init__(self, *args, **keywords) self.killed = False def start(self): self.__run_backup = self.run self.run = self.__run threading.Thread.start(self) def __run(self): sys.settrace(self.globaltrace) self.__run_backup() self.run = self.__run_backup def globaltrace(self, frame, event, arg): if event == 'call': return self.localtrace else: return None def localtrace(self, frame, event, arg): if self.killed: if event == 'line': raise SystemExit() return self.localtrace def kill(self): self.killed = True ############################################################################### # Start et stop des tâches (threads) ############################################################################### def thread_start(threads, type_txt, fct): threads.append(thread_with_trace(target = fct)) threads[len(threads)-1].start() print ("Thread",type_txt, "#", len(threads)-1, "open.") def thread_stop(threads, type_txt): i=0 zombie_flag=False for t in threads: if not t.is_alive(): print ("Thread",type_txt, "#",i,"closed.") else: print ("Thread",type_txt, "#",i,"still open ...") t.kill() t.join() if not t.is_alive(): print ("Thread",type_txt, "#",i,"killed.") else: print ("Thread",type_txt, "#",i,"zombie...") zombie_flag=True i +=1 if zombie_flag==False: print ("All threads",type_txt, "are closed.") return True else: print ("There are zombies threads",type_txt, ".") return False def thread_waves_start(fct): thread_start(threads_waves, "waves", fct) def thread_waves_stop(): thread_stop(threads_waves, "waves") def thread_cmd_start(fct): thread_start(threads_cmd, "commands", fct) def thread_cmd_stop(): thread_stop(threads_cmd, "commands") ############################################################################### # Sounds ############################################################################### # FIXME : Sound crash in Windows (very strange : blender, UPBGE, python ?), no music for Bill def sound_play (sound): if scene.objects['Commands']['sound'] and sys.platform!="win32": audiodev.play(sound) ############################################################################### # Waves (minions) ############################################################################### # Minion caracteristics : category (class), level, hp, speed, armor, bounty, lifes_damage # Minion 3d object : body (male,female,old, ...), variante (A,B,C,D, ...), level # Création d'un minion def ct_minion(x,y,cat,level): category=cat+"-lv"+str(level) minion_3d= scene.objects['Terrain']['minion_3d'] body = random.choice(minion_3d[category][0])+"_"+random.choice(minion_3d[category][1])+"_"+random.choice(minion_3d[category][2]) ct_minion_details(x,y,cat,level,body) # Création d'un minion détaillée def ct_minion_details(x,y,cat,level,body="Knight_m_A_common"): category=cat+"-lv"+str(level) # Pause while scene.objects['Terrain']['run'] == False: time.sleep(0.01) # Synchronisation des threads : attente de la création d'une tour while scene.objects['Terrain']['thread_cmd_lock'] == True: # print ("ct_minion : thread_cmd_lock =True") time.sleep(0.01) # Blocage des autres threads pendant l'apparition du minion scene.objects['Terrain']['thread_cmd_lock'] = True # Object 3D minion= scene.addObject(body, scene.objects['Terrain']) minion.worldScale=[0.25,0.25,0.25] minion.worldPosition=[x,y,0.1] scene.objects['Terrain']['idm']=scene.objects['Terrain']['idm']+1 minion['id']=scene.objects['Terrain']['idm'] minion.name="wm("+str(minion['id'])+")" # Wave minion (wm), identifier minion (idm) scene.objects['Points']['minions'] +=1 scene.objects['Points']['minions_run'] +=1 # Gestion de la distance et des minions zombis minion['dist']=0.0 minion['dist_old']=0.0 minion['dist_last_x']=minion.worldPosition.x minion['dist_last_y']=minion.worldPosition.y minion['dist_new']=True # Caracteristics minion_carac= scene.objects['Terrain']['minion_carac'] minion['cat']=minion_carac[category][0] minion['level']=minion_carac[category][1] minion['hp']=minion_carac[category][2] minion['speed']=minion_carac[category][3] minion['speed_base']=minion_carac[category][3] minion['armor']=minion_carac[category][4] minion['bounty']=minion_carac[category][5] minion['lifes_damage']=minion_carac[category][6] minion['buff']=[] minion['resist']=[] # Actuator Steering minion.actuators['Steering'].navmesh=scene.objects[scene.objects['Terrain']['navmesh']] minion.actuators['Steering'].target=scene.objects[scene.objects['Terrain']['endtile']] minion.actuators['Steering'].distance=2 # minion.actuators['Steering'].distance=0.5 minion.actuators['Steering'].velocity=minion['speed_base']*scene.objects['Terrain']['speed'] # Déblocage des autres threads après l'apparition du minion scene.objects['Terrain']['thread_cmd_lock'] = False # Destruction d'un minion def scn_minion_dead(cont): obj = cont.owner scene.objects['Points']['minions'] -=1 scene.objects['Points']['minions_run'] -=1 scene.objects['Points']['kills'] +=1 scene.objects['Points']['coins']= scene.objects['Points']['coins']+obj['bounty'] obj.endObject() ############################################################################### # Spells / Casts ############################################################################### # Buff/debuff Minion def scn_minion_affect(cont): if scene.objects['Terrain']['run'] == False: # Pause return obj = cont.owner # print (obj.name, obj['buff']) slow_state=False # Distance parcourue obj['dist']=obj['dist']+ math.sqrt((obj.worldPosition.x-obj['dist_last_x'])**2+(obj.worldPosition.y-obj['dist_last_y'])**2) obj['dist_last_x']=obj.worldPosition.x obj['dist_last_y']=obj.worldPosition.y # Lod # print(obj.currentLodLevel) # Etats actif for debuff_i in obj['buff']: if debuff_i[1] <= 0: obj['buff'].remove(debuff_i) continue if debuff_i[0] == "slow": slow_state=True debuff_i[1] -= scene.objects['Terrain']['speed'] # Effets if slow_state: obj.actuators['Steering'].velocity =(obj['speed_base']*scene.objects['Terrain']['speed'])/3 # obj.actuators['Steering'].velocity =(obj['speed_base']*scene.objects['Terrain']['speed'])/2 else: obj.actuators['Steering'].velocity = obj['speed_base']*scene.objects['Terrain']['speed'] ############################################################################### # Towers ############################################################################### # Tower caracteristics : category (class), damage, speed, range # Création d'une tour def ct_build(x,y, cat='Archer tower', tower_name="Tower", color=tower_purple, tower_3d="square-A"): tower_minion_3d= scene.objects['Terrain']['tower_minion_3d'] if cat=='Archer tower': # Archer category="Archer-lv1" if cat=='Mage tower': # Mage category="Mage-lv1" body = random.choice(tower_minion_3d[category][0])+"_"+random.choice(tower_minion_3d[category][1])+"_"+random.choice(tower_minion_3d[category][2]) ct_build_details(x,y, cat, tower_name, color, tower_3d, body) # Création d'une tour détaillée def ct_build_details(x,y, cat='Archer tower', tower_name="Tower", color=tower_purple, tower_3d="square-A", body="Archer_m_A_common"): # Vérification de la place if [x,y] in scene.objects['Terrain']['scene_tile_noncontruct'] or [x,y] in scene.objects['Terrain']['scene_tile_tower']: return False # Vérification du niveau scene.objects['Points']['level']= scene.objects['Points']['level'] + 1 if scene.objects['Points']['level'] > scene.objects['Points']['level_max'] : tour= scene.addObject("Tower_error", scene.objects['Terrain']) tour.worldPosition=[x,y,0.2] tour.worldScale=[1,1,1] scene.objects['Terrain']['scene_tile_tower'].append([x,y]) return False # Blocage des autres threads pendant la construction scene.objects['Terrain']['thread_cmd_lock'] = True # Objets 3D time.sleep(0.02) tour= scene.addObject('Tower-'+tower_3d, scene.objects['Terrain']) time.sleep(0.02) tour.color = color tour.worldPosition=[x,y,0.2] tour.worldScale=[1,1,1] scene.objects['Terrain']['scene_tile_tower'].append([x,y]) tower_minion= scene.addObject(body, scene.objects['Terrain']) tower_minion["type_towerminion"]=False tower_minion.name="tm("+str(x)+','+str(y)+")" # Tower minion (tm) tower_minion.worldPosition=[x,y,1] tower_minion.worldScale=[0.25,0.25,0.25] # Sounds sound_play(sndbuff_build) # Caracteristics tower_carac= scene.objects['Terrain']['tower_carac'] tour['cat']=tower_carac[cat][0] tour['tower_name']=tower_name tour['xp']=0 tour['lvl_current']=1 tour['lvl']=1 tour['damage']=tower_carac[cat][1] tour['speed']=tower_carac[cat][2] tour['range']=tower_carac[cat][3] tour['techno']=[] tour['cast']="slow" # tour['cast_duration']=2 tour['cast_duration']=3 tour['target']=[] tour['target_past']=[] # Capteur Near tour.sensors['Near'].distance=tour['range'] tour.sensors['Near'].skippedTicks =round(1/(tour['speed']*scene.objects['Terrain']['speed'])) # Déblocage des autres threads après la construction scene.objects['Terrain']['thread_cmd_lock'] = False return True # Suppression d'une tour def ct_remove(x,y): for obj_i in scene.objects: if "type_tower" in obj_i.getPropertyNames(): if x == obj_i.worldPosition.x and y == obj_i.worldPosition.y: scene.objects["tm("+str(round(obj_i.worldPosition.x))+','+str(round(obj_i.worldPosition.y))+")"].endObject() obj_i.endObject() scene.objects['Points']['level']= scene.objects['Points']['level'] - 1 # Réaction d'une tour def scn_tower_near(cont): obj = cont.owner sensor = obj.sensors['Near'] # Tir if sensor.positive and len(sensor.hitObjectList)>0 and scene.objects['Terrain']['run']==True : # Tir sur le plus avancé basé sur les distances parcourues target=sensor.hitObjectList[0] target_dist = target['dist'] for obj_i in sensor.hitObjectList: if obj_i['dist']> target_dist: target=obj_i target_dist = target['dist'] # Tir sur le plus avancé basé sur l'ordre de passage # target=sensor.hitObjectList[0] # target_id = target['navPosition'] # for obj_i in sensor.hitObjectList: # if obj_i['navPosition']< target_id: # target=obj_i # target_id = target['navPosition'] # Tir sur le plus avancé basé sur les distances par rapport à la tour -> ne marche pas # target=sensor.hitObjectList[0] # if len(sensor.hitObjectList)>1: # target_eloignement = False # target_distance_eloignement = 0 # target_distance_approche = 100 # print ("detection:",sensor.hitObjectList) # for obj_i in sensor.hitObjectList: # for obj_j in obj['target_past']: # if obj_j[0]==obj_i.name: # print ("name :", obj_j[0], "distance :", obj.getDistanceTo(obj_i), "distance old :", obj_j[1], "ecart :", obj.getDistanceTo(obj_i) - obj_j[1]) # # Éloignement # if obj.getDistanceTo(obj_i) - obj_j[1] > 0: # Ecart de distance # target_eloignement = True # if obj.getDistanceTo(obj_i) > target_distance_eloignement: # target=obj_i # target_distance_eloignement = obj.getDistanceTo(obj_i) # # Approche # else: # if target_eloignement == False: # if obj.getDistanceTo(obj_i) < target_distance_approche: # target=obj_i # target_distance_approche = obj.getDistanceTo(obj_i) # if target_eloignement == True: # print ("Eloignement : target:", target.name, "distance :", obj.getDistanceTo(target)) # print ("") # else: # print ("Approche : target:", target.name, "distance :", obj.getDistanceTo(target)) # print ("") # obj['target_past']=[] # for obj_i in sensor.hitObjectList: # obj['target_past'].append([obj_i.name, obj.getDistanceTo(obj_i)]) # Orientation du tower minion towerminion="tm("+str(round(obj.worldPosition.x))+','+str(round(obj.worldPosition.y))+")" angle =math.atan((target.worldPosition.y-obj.worldPosition.y)/(target.worldPosition.x-obj.worldPosition.x)) if target.worldPosition.x>obj.worldPosition.x: angle2=math.pi/2+angle-scene.objects[towerminion].worldOrientation.to_euler().z angle3=angle else: angle2=math.pi+math.pi/2+angle-scene.objects[towerminion].worldOrientation.to_euler().z angle3=math.pi+angle scene.objects[towerminion].applyRotation((0, 0, angle2), False) # Bullet (3d object) (vitesse lente) # -> tendance au plantage # if scene.objects['Terrain']['speed']<1: # bullet= scene.addObject("Bullet", scene.objects['Terrain']) # bullet.mass=0.001 # bullet.applyForce=((0,0,9.81),True) # bullet.worldPosition=[obj.worldPosition.x,obj.worldPosition.y,1.5] # bullet.worldScale=[0.75,0.75,0.75] # # bullet.worldScale=[0.5,0.5,0.5] # bullet.worldLinearVelocity.x = (target.worldPosition.x-bullet.worldPosition.x)*bullet['velocity'] # bullet.worldLinearVelocity.y= (target.worldPosition.y-bullet.worldPosition.y)*bullet['velocity'] # bullet.worldLinearVelocity.z = (target.worldPosition.z+0.1-bullet.worldPosition.z)*bullet['velocity'] # Sounds if obj['cat']=="Archer tower": sound_play(sndbuff_archer) if obj['cat']=="Mage tower": sound_play(sndbuff_mage) # Ligne (drawLine) (vitesse rapide) # if scene.objects['Terrain']['speed']>=1: if scene.objects['Terrain']['speed']<10: # Pas d'animation à 10 -> plantage # Archer (tir de flêche) if obj['cat']=="Archer tower": if target.name in scene.objects: scene.objects['Terrain']['draw_process']=True scene.objects['Terrain']['draw_list'].append([5, "arrow", [obj.worldPosition.x, obj.worldPosition.y, obj.worldPosition.z+0.8],target.name, angle3, ray_black, 5]) # Cast zone if obj['cat']=="Mage tower": # Mage (cast) scene.objects['Terrain']['draw_process']=True scene.objects['Terrain']['draw_list'].append([30, "cast", [obj.worldPosition.x, obj.worldPosition.y, obj.worldPosition.z+0.8],ray_blue,30]) # Rayon if obj['cat']=="Test": if target.name in scene.objects: scene.objects['Terrain']['draw_process']=True scene.objects['Terrain']['draw_list'].append([5, "ray", [obj.worldPosition.x, obj.worldPosition.y, obj.worldPosition.z+0.8], target.name, angle3, ray_yellow,5]) # Suivi du minion # Dégats target['hp'] = target['hp'] - obj['damage'] if target['hp']<=0: target['dead']=True # Cast (buff and debuff) if obj['cat']=="Mage tower": for target_i in sensor.hitObjectList: target_i['buff'].append([obj['cast'], obj['cast_duration']]) ############################################################################### # Carte ############################################################################### # Texte de carte def ct_map_text_wave(wave): scene.objects['Map_text']['Text']=("Wave " + str(wave)) scene.objects['Map_text'].setVisible(True,False) scene.objects['Map_text'].color = color_text_yellow scene.objects['Map_text']['timer']=0 scene.objects['Map_text']['anim']=True # Texte de carte def ct_map_text(text): scene.objects['Map_text']['Text']=text scene.objects['Map_text'].setVisible(True,False) # Fin def ct_map_end(x,y): scene.objects['Map_end'].worldPosition=[x,y,0.2] scene.objects['Map_end'].worldScale=[0.25,0.25,0.25] # Minion arrivé à la fin def scn_map_end_near(cont): obj = cont.owner sensor = obj.sensors['Near'] if sensor.positive : for obj_i in sensor.hitObjectList : sound_play(sndbuff_life) if scene.objects['Points']['lifes']>0: scene.objects['Points']['lifes']= scene.objects['Points']['lifes']-obj_i['lifes_damage'] scene.objects['Points']['minions_run'] -=1 for obj_i in sensor.hitObjectList : obj_i.endObject() # def scn_map_end_near(cont): # obj = cont.owner # print(obj) # sensor = obj.sensors['Near'] # if sensor.positive and len(sensor.hitObjectList)>0 : # # print ("end, len(sensor.hitObjectList) : ", len(sensor.hitObjectList)) # for obj_i in sensor.hitObjectList : # # print ("obj['idm_last'], obj_i ['id'] : ", obj['idm_last'], obj_i ['id']) # if obj['idm_last'] != obj_i ['id']: # obj['idm_last'] = obj_i ['id'] # sound_play(sndbuff_life) # if scene.objects['Points']['lifes']>0: # scene.objects['Points']['lifes']= scene.objects['Points']['lifes']-obj_i['lifes_damage'] # scene.objects['Points']['minions_run'] -=1 # obj['idm_last'] = obj_i ['id'] # for obj_i in sensor.hitObjectList : # obj_i.endObject() # Drapeau de fin def ct_map_endflag(x,y): endflag= scene.addObject("Map_endflag", scene.objects['Terrain']) endflag.worldPosition=[x,y,0.3] endflag.worldScale=[0.3,0.3,0.3] if round(x) == x : if round(y) == y : scene.objects['Terrain']['scene_tile_noncontruct'].append([x,y]) else: scene.objects['Terrain']['scene_tile_noncontruct'].append([x,math.floor(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([x,math.ceil(y)]) else: if round(y) == y : scene.objects['Terrain']['scene_tile_noncontruct'].append([math.floor(x),y]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.ceil(x),y]) else: scene.objects['Terrain']['scene_tile_noncontruct'].append([math.floor(x),math.floor(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.floor(x),math.ceil(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.ceil(x),math.floor(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.ceil(x),math.ceil(y)]) ############################################################################### # Temporisation ############################################################################### def ct_sleep (duration): time.sleep(duration*(1/scene.objects['Terrain']['speed'])) # def ct_tempo (duration): # scene.objects['Terrain']['delay_cmd']=0 # while scene.objects['Terrain']['delay_cmd']x0: angle2=angle_xy else: angle2=math.pi+angle_xy x2=x0+(((6-draw_cmd[0])*step)*(math.cos(angle2))) y2=y0+(((6-draw_cmd[0])*step)*(math.sin(angle2))) z2=z0-(((6-draw_cmd[0])*step_z)*(math.sin(angle_z))) x3=x0+(((6-draw_cmd[0])*step+step)*(math.cos(angle2))) y3=y0+(((6-draw_cmd[0])*step+step)*(math.sin(angle2))) z3=z0-(((6-draw_cmd[0])*step_z+step_z)*(math.sin(angle_z))) bge.render.drawLine([x2,y2, z2], [x3,y3,z3], draw_cmd[5]) draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # if scene.objects['Terrain']['speed']<1: # draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # else: # draw_cmd[0] = draw_cmd[0]-1 # bge.render.drawLine([draw_cmd[2][0]+((6-draw_cmd[0])*0.25)*(math.cos(draw_cmd[4])), draw_cmd[2][1]+((6-draw_cmd[0])*0.25)*(math.sin(draw_cmd[4])),draw_cmd[2][2]], # [draw_cmd[2][0]+((6-draw_cmd[0])*0.25+0.25)*(math.cos(draw_cmd[4])), draw_cmd[2][1]+((6-draw_cmd[0])*0.25+0.25)*(math.sin(draw_cmd[4])),draw_cmd[2][2]], # draw_cmd[5]) # Mage (cast) # FIXME : Problème if draw_cmd[1]=="cast": # Mage (cast) circle(draw_cmd[2], 3.1-draw_cmd[0]*0.1, draw_cmd[3]) circle(draw_cmd[2], 3-draw_cmd[0]*0.1, draw_cmd[3]) circle(draw_cmd[2], 2.9-draw_cmd[0]*0.1, draw_cmd[3]) draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # if scene.objects['Terrain']['speed']<=2: # draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # if scene.objects['Terrain']['speed']==4: # draw_cmd[0] = draw_cmd[0]-draw_cmd[4]/2 # circle(draw_cmd[2], 3, draw_cmd[3]) # simple # radius=[3,3,2.5,2.5,2,2,1.5,1.5,1,1,1] # basé sur un tableau # circle(draw_cmd[2], radius[draw_cmd[0]], draw_cmd[3]) # Rayon if draw_cmd[1]=="ray": if draw_cmd[3] in scene.objects: x0 = draw_cmd[2][0]+0.25*(math.cos(draw_cmd[4])) y0 = draw_cmd[2][1]+0.25*(math.sin(draw_cmd[4])) x1 = scene.objects[draw_cmd[3]].worldPosition.x y1 = scene.objects[draw_cmd[3]].worldPosition.y z1 = scene.objects[draw_cmd[3]].worldPosition.z bge.render.drawLine([x0,y0, draw_cmd[2][2]], [x1,y1,z1], draw_cmd[5]) # suivi minion # bge.render.drawLine([draw_cmd[2][0]+0.25*(math.cos(draw_cmd[4])), draw_cmd[2][1]+0.25*(math.sin(draw_cmd[4])), draw_cmd[2][2]], draw_cmd[3], draw_cmd[5]) # décalage minion # bge.render.drawLine(draw_cmd[2], draw_cmd[3], draw_cmd[5]) # simple draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # Suppression des draws finis i=0 for draw_cmd in scene.objects['Terrain']['draw_list']: if draw_cmd[0]<=0: scene.objects['Terrain']['draw_list'].pop(i) else: i=i+1 if len(scene.objects['Terrain']['draw_list'])==0: scene.objects['Terrain']['draw_process']=False