class Panda3dBulletPhysics(World): # NOTE: the model ids of objects that correspond to opened doors. They will be ignored for collisions. openedDoorModelIds = [ '122', '133', '214', '246', '247', '361', '73', '756', '757', '758', '759', '760', '761', '762', '763', '764', '765', '768', '769', '770', '771', '778', '779', '780', 's__1762', 's__1763', 's__1764', 's__1765', 's__1766', 's__1767', 's__1768', 's__1769', 's__1770', 's__1771', 's__1772', 's__1773', ] # FIXME: is not a complete list of movable objects movableObjectCategories = [ 'table', 'dressing_table', 'sofa', 'trash_can', 'chair', 'ottoman', 'bed' ] # For more material, see table: http://www.ambrsoft.com/CalcPhysics/Density/Table_2.htm defaultDensity = 1000.0 # kg/m^3 # For more coefficients, see table: https://www.thoughtspike.com/friction-coefficients-for-bullet-physics/ defaultMaterialFriction = 0.7 defaultMaterialRestitution = 0.5 def __init__(self, scene, suncgDatasetRoot=None, debug=False, objectMode='box', agentRadius=0.1, agentHeight=1.6, agentMass=60.0, agentMode='capsule'): super(Panda3dBulletPhysics, self).__init__() self.__dict__.update(scene=scene, suncgDatasetRoot=suncgDatasetRoot, debug=debug, objectMode=objectMode, agentRadius=agentRadius, agentHeight=agentHeight, agentMass=agentMass, agentMode=agentMode) if suncgDatasetRoot is not None: self.modelCatMapping = ModelCategoryMapping( os.path.join(suncgDatasetRoot, "metadata", "ModelCategoryMapping.csv")) else: self.modelCatMapping = None self.bulletWorld = BulletWorld() self.bulletWorld.setGravity(Vec3(0, 0, -9.81)) if debug: debugNode = BulletDebugNode('physic-debug') debugNode.showWireframe(True) debugNode.showConstraints(False) debugNode.showBoundingBoxes(True) debugNode.showNormals(False) self.debugNodePath = self.scene.scene.attachNewNode(debugNode) self.debugNodePath.show() self.bulletWorld.setDebugNode(debugNode) else: self.debugNodePath = None self._initLayoutModels() self._initAgents() self._initObjects() self.scene.worlds['physics'] = self def destroy(self): # Nothing to do pass def _initLayoutModels(self): # Load layout objects as meshes for model in self.scene.scene.findAllMatches( '**/layouts/object*/model*'): shape, transform = getCollisionShapeFromModel( model, mode='mesh', defaultCentered=False) node = BulletRigidBodyNode('physics') node.setMass(0.0) node.setFriction(self.defaultMaterialFriction) node.setRestitution(self.defaultMaterialRestitution) node.setStatic(True) node.addShape(shape) node.setDeactivationEnabled(True) node.setIntoCollideMask(BitMask32.allOn()) self.bulletWorld.attach(node) # Attach the physic-related node to the scene graph physicsNp = model.getParent().attachNewNode(node) physicsNp.setTransform(transform) if node.isStatic(): model.getParent().setTag('physics-mode', 'static') else: model.getParent().setTag('physics-mode', 'dynamic') # Reparent render and acoustics nodes (if any) below the new physic node #XXX: should be less error prone to just reparent all children (except the hidden model) renderNp = model.getParent().find('**/render') if not renderNp.isEmpty(): renderNp.reparentTo(physicsNp) acousticsNp = model.getParent().find('**/acoustics') if not acousticsNp.isEmpty(): acousticsNp.reparentTo(physicsNp) # NOTE: we need this to update the transform state of the internal bullet node physicsNp.node().setTransformDirty() # Validation assert np.allclose( mat4ToNumpyArray(physicsNp.getNetTransform().getMat()), mat4ToNumpyArray(model.getNetTransform().getMat()), atol=1e-6) def _initAgents(self): # Load agents for agent in self.scene.scene.findAllMatches('**/agents/agent*'): transform = TransformState.makeIdentity() if self.agentMode == 'capsule': shape = BulletCapsuleShape( self.agentRadius, self.agentHeight - 2 * self.agentRadius) elif self.agentMode == 'sphere': shape = BulletCapsuleShape(self.agentRadius, 2 * self.agentRadius) # XXX: use BulletCharacterControllerNode class, which already handles local transform? node = BulletRigidBodyNode('physics') node.setMass(self.agentMass) node.setStatic(False) node.setFriction(self.defaultMaterialFriction) node.setRestitution(self.defaultMaterialRestitution) node.addShape(shape) self.bulletWorld.attach(node) # Constrain the agent to have fixed position on the Z-axis node.setLinearFactor(Vec3(1.0, 1.0, 0.0)) # Constrain the agent not to be affected by rotations node.setAngularFactor(Vec3(0.0, 0.0, 0.0)) node.setIntoCollideMask(BitMask32.allOn()) node.setDeactivationEnabled(True) # Enable continuous collision detection (CCD) node.setCcdMotionThreshold(1e-7) node.setCcdSweptSphereRadius(0.50) if node.isStatic(): agent.setTag('physics-mode', 'static') else: agent.setTag('physics-mode', 'dynamic') # Attach the physic-related node to the scene graph physicsNp = NodePath(node) physicsNp.setTransform(transform) # Reparent all child nodes below the new physic node for child in agent.getChildren(): child.reparentTo(physicsNp) physicsNp.reparentTo(agent) # NOTE: we need this to update the transform state of the internal bullet node physicsNp.node().setTransformDirty() # Validation assert np.allclose( mat4ToNumpyArray(physicsNp.getNetTransform().getMat()), mat4ToNumpyArray(agent.getNetTransform().getMat()), atol=1e-6) def _initObjects(self): # Load objects for model in self.scene.scene.findAllMatches( '**/objects/object*/model*'): modelId = model.getParent().getTag('model-id') # XXX: we could create BulletGhostNode instance for non-collidable objects, but we would need to filter out the collisions later on if not modelId in self.openedDoorModelIds: shape, transform = getCollisionShapeFromModel( model, self.objectMode, defaultCentered=True) node = BulletRigidBodyNode('physics') node.addShape(shape) node.setFriction(self.defaultMaterialFriction) node.setRestitution(self.defaultMaterialRestitution) node.setIntoCollideMask(BitMask32.allOn()) node.setDeactivationEnabled(True) if self.suncgDatasetRoot is not None: # Check if it is a movable object category = self.modelCatMapping.getCoarseGrainedCategoryForModelId( modelId) if category in self.movableObjectCategories: # Estimate mass of object based on volumetric data and default material density objVoxFilename = os.path.join(self.suncgDatasetRoot, 'object_vox', 'object_vox_data', modelId, modelId + '.binvox') voxelData = ObjectVoxelData.fromFile(objVoxFilename) mass = Panda3dBulletPhysics.defaultDensity * voxelData.getFilledVolume( ) node.setMass(mass) else: node.setMass(0.0) node.setStatic(True) else: node.setMass(0.0) node.setStatic(True) if node.isStatic(): model.getParent().setTag('physics-mode', 'static') else: model.getParent().setTag('physics-mode', 'dynamic') # Attach the physic-related node to the scene graph physicsNp = model.getParent().attachNewNode(node) physicsNp.setTransform(transform) # Reparent render and acoustics nodes (if any) below the new physic node #XXX: should be less error prone to just reparent all children (except the hidden model) renderNp = model.getParent().find('**/render') if not renderNp.isEmpty(): renderNp.reparentTo(physicsNp) acousticsNp = model.getParent().find('**/acoustics') if not acousticsNp.isEmpty(): acousticsNp.reparentTo(physicsNp) # NOTE: we need this to update the transform state of the internal bullet node node.setTransformDirty() # NOTE: we need to add the node to the bullet engine only after setting all attributes self.bulletWorld.attach(node) # Validation assert np.allclose( mat4ToNumpyArray(physicsNp.getNetTransform().getMat()), mat4ToNumpyArray( model.getParent().getNetTransform().getMat()), atol=1e-6) else: logger.debug('Object %s ignored from physics' % (modelId)) def step(self, dt): self.bulletWorld.doPhysics(dt) def isCollision(self, root): isCollisionDetected = False if isinstance(root.node(), BulletRigidBodyNode): result = self.bulletWorld.contactTest(root.node()) if result.getNumContacts() > 0: isCollisionDetected = True else: for nodePath in root.findAllMatches('**/+BulletBodyNode'): isCollisionDetected |= self.isCollision(nodePath) return isCollisionDetected def getCollisionInfo(self, root, dt): result = self.bulletWorld.contactTest(root.node()) force = 0.0 relativePosition = LVecBase3f(0.0, 0.0, 0.0) isCollisionDetected = False for _ in result.getContacts(): # Iterate over all manifolds of the world # NOTE: it seems like the contact manifold doesn't hold the information # to calculate contact force. We need the persistent manifolds for that. for manifold in self.bulletWorld.getManifolds(): # Check if the root node is part of that manifold, by checking positions # TODO: there is surely a better way to compare the two nodes here #if (manifold.getNode0().getTransform().getPos() == root.getNetTransform().getPos()): if manifold.getNode0().getTag('model-id') == root.getTag( 'model-id'): # Calculate the to totalImpulse = 0.0 maxImpulse = 0.0 for pt in manifold.getManifoldPoints(): impulse = pt.getAppliedImpulse() totalImpulse += impulse if impulse > maxImpulse: maxImpulse = impulse relativePosition = pt.getLocalPointA() force = totalImpulse / dt isCollisionDetected = True return force, relativePosition, isCollisionDetected def calculate2dNavigationMap(self, agent, z=0.1, precision=0.1, yup=True): agentRbNp = agent.find('**/+BulletRigidBodyNode') # Calculate the bounding box of the scene bounds = [] for nodePath in self.scene.scene.findAllMatches( '**/object*/+BulletRigidBodyNode'): node = nodePath.node() #NOTE: the bounding sphere doesn't seem to take into account the transform, so apply it manually (translation only) bsphere = node.getShapeBounds() center = nodePath.getNetTransform().getPos() bounds.extend( [center + bsphere.getMin(), center + bsphere.getMax()]) minBounds, maxBounds = np.min(bounds, axis=0), np.max(bounds, axis=0) # Using the X and Y dimensions of the bounding box, discretize the 2D plan into a uniform grid with given precision X = np.arange(minBounds[0], maxBounds[0], step=precision) Y = np.arange(minBounds[1], maxBounds[1], step=precision) nbTotalCells = len(X) * len(Y) threshold10Perc = int(nbTotalCells / 10) # XXX: the simulation needs to be run a little before moving the agent, not sure why self.bulletWorld.doPhysics(0.1) # Sweep the position of the agent across the grid, checking if collision/contacts occurs with objects or walls in the scene. occupancyMap = np.zeros((len(X), len(Y))) occupancyMapCoord = np.zeros((len(X), len(Y), 2)) n = 0 for i, x in enumerate(X): for j, y in enumerate(Y): agentRbNp.setPos(LVecBase3f(x, y, z)) if self.isCollision(agentRbNp): occupancyMap[i, j] = 1.0 occupancyMapCoord[i, j, 0] = x occupancyMapCoord[i, j, 1] = y n += 1 if n % threshold10Perc == 0: logger.debug('Collision test no.%d (out of %d total)' % (n, nbTotalCells)) if yup: # Convert to image format (y,x) occupancyMap = np.flipud(occupancyMap.T) occupancyMapCoord = np.flipud( np.transpose(occupancyMapCoord, axes=(1, 0, 2))) return occupancyMap, occupancyMapCoord
class GameLogic(ShowBase): def __init__(self,pandaBase): self.pandaBase = pandaBase self.pandaBase.enableParticles() self.accept("DemarrerPartie",self.startGame) #Vérifier si la connexion à la base de donnée est bien établite try: # CREE LE DAO ET LE DTO self.leDao = DAO_B_Oracle("e1529743","AAAaaa111","delta/decinfo.edu") self.leDto = self.leDao.lire() self.leDto.verifier() self.leDao.deconnection() except: self.leDto = DTOdefault() ctypes.windll.user32.MessageBoxA(0, "Erreur lors de la connexion a la base de donnee. Les valeurs par defaut sont utilisees !", "Valeurs par defaut", 0) def setup(self,niveau): self.setupBulletPhysics() self.setupCamera() self.setupMap(niveau) self.setupLightAndShadow() #Création d'une carte de base #self.carte.creerCarteParDefaut() if (niveau != "quick"): print("hasard") self.map.construireMapDto() else: self.map.construireMapHasard() #A besoin des éléments de la map self.setupControle() self.setupInterface() #Fonction d'optimisation #DOIVENT ÊTRE APPELÉE APRÈS LA CRÉATION DE LA CARTE #Ça va prendre les modèles qui ne bougent pas et en faire un seul gros #en faisant un seul gros noeud avec self.map.figeObjetImmobile() #DEBUG: Décommenter pour affiche la hiérarchie #self.pandaBase.startDirect() messenger.send("ChargementTermine") def startGame(self,niveau): self.setup(niveau) print (niveau) #On démarrer l'effet du compte à rebour. #La fonction callBackDebutPartie sera appelée à la fin self.interfaceMessage.effectCountDownStart(self.leDto.mess_countdown_time[2],self.callBackDebutPartie) #self.row[2], row[3]interfaceMessage.effectMessageGeneral(self.leDto.mess_accueil_content[1],self.leDto.mess_accueil_time[2]) def setupBulletPhysics(self): debugNode = BulletDebugNode('Debug') debugNode.showWireframe(True) debugNode.showConstraints(True) debugNode.showBoundingBoxes(False) debugNode.showNormals(False) self.debugNP = render.attachNewNode(debugNode) self.mondePhysique = BulletWorld() self.mondePhysique.setGravity(Vec3(0, 0, -9.81)) self.mondePhysique.setDebugNode(self.debugNP.node()) taskMgr.add(self.updatePhysics, "updatePhysics") taskMgr.add(self.updateCarte, "updateCarte") def setupCamera(self): #On doit désactiver le contrôle par défaut de la caméra autrement on ne peut pas la positionner et l'orienter self.pandaBase.disableMouse() #Le flag pour savoir si la souris est activée ou non n'est pas accessible #Petit fail de Panda3D taskMgr.add(self.updateCamera, "updateCamera") self.setupTransformCamera() def setupTransformCamera(self): #Défini la position et l'orientation de la caméra self.positionBaseCamera = Vec3(0,-18,32) camera.setPos(self.positionBaseCamera) #On dit à la caméra de regarder l'origine (point 0,0,0) camera.lookAt(render) def setupMap(self,niveau): self.map = Map(self.mondePhysique, self.leDto, niveau) #On construire la carte comme une coquille, de l'extérieur à l'intérieur #Décor et ciel self.map.construireDecor(camera) #Plancher de la carte self.map.construirePlancher() #Murs et éléments de la map def setupLightAndShadow(self): #Lumière du skybox plight = PointLight('Lumiere ponctuelle') plight.setColor(VBase4(1,1,1,1)) plnp = render.attachNewNode(plight) plnp.setPos(0,0,0) camera.setLight(plnp) #Simule le soleil avec un angle dlight = DirectionalLight('Lumiere Directionnelle') dlight.setColor(VBase4(0.8, 0.8, 0.6, 1)) dlight.get_lens().set_fov(75) dlight.get_lens().set_near_far(0.1, 60) dlight.get_lens().set_film_size(30,30) dlnp = render.attachNewNode(dlight) dlnp.setPos(Vec3(-2,-2,7)) dlnp.lookAt(render) render.setLight(dlnp) #Lumière ambiante alight = AmbientLight('Lumiere ambiante') alight.setColor(VBase4(0.25, 0.25, 0.25, 1)) alnp = render.attachNewNode(alight) render.setLight(alnp) #Ne pas modifier la valeur 1024 sous peine d'avoir un jeu laid ou qui lag dlight.setShadowCaster(True, 1024,1024) #On doit activer l'ombre sur les modèles render.setShaderAuto() def setupControle(self,): #Créer le contrôle #A besoin de la liste de tank pour relayer correctement le contrôle self.inputManager = InputManager(self.map.listTank,self.debugNP,self.pandaBase) self.accept("initCam",self.setupTransformCamera) def setupInterface(self): self.interfaceTank = [] self.interfaceTank.append(InterfaceTank(0,self.map.listTank[0].couleur)) self.interfaceTank.append(InterfaceTank(1,self.map.listTank[1].couleur)) self.interfaceMessage = InterfaceMessage(self.leDto) def callBackDebutPartie(self): #Quand le message d'introduction est terminé, on permet aux tanks de bouger self.inputManager.debuterControle() #Mise à jour du moteur de physique def updateCamera(self,task): #On ne touche pas à la caméra si on est en mode debug if(self.inputManager.mouseEnabled): return task.cont vecTotal = Vec3(0,0,0) distanceRatio = 1.0 if (len(self.map.listTank) != 0): for tank in self.map.listTank: vecTotal += tank.noeudPhysique.getPos() vecTotal = vecTotal/len(self.map.listTank) vecTotal.setZ(0) camera.setPos(vecTotal + self.positionBaseCamera) return task.cont #Mise à jour du moteur de physique def updatePhysics(self,task): dt = globalClock.getDt() messenger.send("appliquerForce") self.mondePhysique.doPhysics(dt) #print(len(self.mondePhysique.getManifolds())) #Analyse de toutes les collisions for entrelacement in self.mondePhysique.getManifolds(): node0 = entrelacement.getNode0() node1 = entrelacement.getNode1() self.map.traiterCollision(node0, node1) return task.cont def updateCarte(self,task): #print task.time self.map.update(task.time) return task.cont
class GameLogic(ShowBase): def __init__(self, pandaBase): self.pandaBase = pandaBase self.pandaBase.enableParticles() self.accept("DemarrerPartie", self.startGame) self.accept("DemarrerMenuNiveau", self.startMenu) self.accept("DemarrerConnexion", self.startConnexion) self.accept("tankElimine", self.gameOver) self.accept("DemarrerPageFinPartie", self.pageFinPartie) def pageFinPartie(self): self.dao = DAO_Oracle() # on affiche les résultats de fin de partie seulement si la connexion oracle fonctionne if self.dao.getConnexionState(): self.pageFinPartie = InterfaceFinPartie() def gameOver(self, idPerdant): # si la connexion oracle fonctionone, on insère les données dans la bd dao = DAO_Oracle() if dao.getConnexionState(): #mettre l'état du joueur gagnant à Vrai if idPerdant == 0: indexGagnant = 1 # joueur 2 a gagné elif idPerdant == 1: indexGagnant = 0 # joueur 1 a gagné DTO.joueurs[indexGagnant].vainqueur = True # ajuster les niveaux des deux joueurs analyse = AnalyseDTOJoueur() # for i in range(2): tanks = self.map.listTank #ajouter les expériences pour joueur 1 levelUp1 = DTO.joueurs[0].updateExperience(tanks[0], tanks[1]) #ajouter les expériences pour joueur 2 ancienLevel = DTO.joueurs[1].calculerLevel() DTO.joueurs[1].updateExperience(tanks[1], tanks[0]) nouveauLevel = DTO.joueurs[1].calculerLevel() if (nouveauLevel > ancienLevel): levelUp = True #ajouter les statistiques d'armes au DTO for i in range(len(self.map.listTank)): DTO.joueurs[i].tank = self.map.listTank[i] # ajouter la date de fin de partie now = datetime.datetime.now() DTO.finPartie = datetime.datetime.now() self.dao = DAO_Oracle() self.dao.envoyerStatistiques() # après que tous les nouvelles statistiques sont calculées et/ou envoyées # on affiche les résultats de la partie messenger.send("DemarrerPageFinPartie") # sinon, on ne fait qu'afficher le nom du joueur gagnant else: pass def setup(self): self.setupBulletPhysics() self.setupCamera() self.setupMap() self.setupLightAndShadow() #Création d'une carte de base #self.carte.creerCarteParDefaut() if DTO.mapSelectionne == None: self.map.construireMapHasard() else: self.map.construireMapChoisi() # le temps de début de partie now = datetime.datetime.now() DTO.debutPartie = now #A besoin des éléments de la map self.setupControle() self.setupInterface() #Fonction d'optimisation #DOIVENT ÊTRE APPELÉE APRÈS LA CRÉATION DE LA CARTE #Ça va prendre les modèles qui ne bougent pas et en faire un seul gros #en faisant un seul gros noeud avec self.map.figeObjetImmobile() #DEBUG: Décommenter pour affiche la hiérarchie #self.pandaBase.startDirect() messenger.send("ChargementTermine") def startGame(self): # self.menu.run() self.setup() #On démarrer l'effet du compte à rebour. #La fonction callBackDebutPartie sera appelée à la fin self.interfaceMessage.effectCountDownStart(3, self.callBackDebutPartie) self.interfaceMessage.effectMessageGeneral( "Appuyer sur F1 pour l'aide", 3) def startMenu(self): self.menu = InterfaceMenuNiveaux() def startConnexion(self): self.menuConnexion = InterfaceConnexion() def setupBulletPhysics(self): debugNode = BulletDebugNode('Debug') debugNode.showWireframe(True) debugNode.showConstraints(True) debugNode.showBoundingBoxes(False) debugNode.showNormals(False) self.debugNP = render.attachNewNode(debugNode) self.mondePhysique = BulletWorld() self.mondePhysique.setGravity(Vec3(0, 0, -9.81)) self.mondePhysique.setDebugNode(self.debugNP.node()) taskMgr.add(self.updatePhysics, "updatePhysics") taskMgr.add(self.updateCarte, "updateCarte") def setupCamera(self): #On doit désactiver le contrôle par défaut de la caméra autrement on ne peut pas la positionner et l'orienter self.pandaBase.disableMouse() #Le flag pour savoir si la souris est activée ou non n'est pas accessible #Petit fail de Panda3D taskMgr.add(self.updateCamera, "updateCamera") self.setupTransformCamera() def setupTransformCamera(self): #Défini la position et l'orientation de la caméra self.positionBaseCamera = Vec3(0, -18, 32) camera.setPos(self.positionBaseCamera) #On dit à la caméra de regarder l'origine (point 0,0,0) camera.lookAt(render) def setupMap(self): self.map = Map(self.mondePhysique) #On construire la carte comme une coquille, de l'extérieur à l'intérieur #Décor et ciel self.map.construireDecor(camera) #Plancher de la carte self.map.construirePlancher() #Murs et éléments de la map def setupLightAndShadow(self): #Lumière du skybox plight = PointLight('Lumiere ponctuelle') plight.setColor(VBase4(1, 1, 1, 1)) plnp = render.attachNewNode(plight) plnp.setPos(0, 0, 0) camera.setLight(plnp) #Simule le soleil avec un angle dlight = DirectionalLight('Lumiere Directionnelle') dlight.setColor(VBase4(0.8, 0.8, 0.6, 1)) dlight.get_lens().set_fov(75) dlight.get_lens().set_near_far(0.1, 60) dlight.get_lens().set_film_size(30, 30) dlnp = render.attachNewNode(dlight) dlnp.setPos(Vec3(-2, -2, 7)) dlnp.lookAt(render) render.setLight(dlnp) #Lumière ambiante alight = AmbientLight('Lumiere ambiante') alight.setColor(VBase4(0.25, 0.25, 0.25, 1)) alnp = render.attachNewNode(alight) render.setLight(alnp) #Ne pas modifier la valeur 1024 sous peine d'avoir un jeu laid ou qui lag dlight.setShadowCaster(True, 1024, 1024) #On doit activer l'ombre sur les modèles render.setShaderAuto() def setupControle(self, ): #Créer le contrôle #A besoin de la liste de tank pour relayer correctement le contrôle self.inputManager = InputManager(self.map.listTank, self.debugNP, self.pandaBase) self.accept("initCam", self.setupTransformCamera) def setupInterface(self): self.interfaceTank = [] self.interfaceTank.append( InterfaceTank(0, self.map.listTank[0].couleur)) self.interfaceTank.append( InterfaceTank(1, self.map.listTank[1].couleur)) self.interfaceMessage = InterfaceMessage() def callBackDebutPartie(self): #Quand le message d'introduction est terminé, on permet aux tanks de bouger self.inputManager.debuterControle() #Mise à jour du moteur de physique def updateCamera(self, task): #On ne touche pas à la caméra si on est en mode debug if (self.inputManager.mouseEnabled): return task.cont vecTotal = Vec3(0, 0, 0) distanceRatio = 1.0 if (len(self.map.listTank) != 0): for tank in self.map.listTank: vecTotal += tank.noeudPhysique.getPos() vecTotal = vecTotal / len(self.map.listTank) vecTotal.setZ(0) camera.setPos(vecTotal + self.positionBaseCamera) return task.cont #Mise à jour du moteur de physique def updatePhysics(self, task): dt = globalClock.getDt() messenger.send("appliquerForce") self.mondePhysique.doPhysics(dt) #Analyse de toutes les collisions for entrelacement in self.mondePhysique.getManifolds(): node0 = entrelacement.getNode0() node1 = entrelacement.getNode1() self.map.traiterCollision(node0, node1) return task.cont def updateCarte(self, task): self.map.update(task.time) return task.cont
class GameLogic(ShowBase): def __init__(self, pandaBase): #######################AJOUTER ######################################################### self.pandaBase = pandaBase self.accept("DemarrerPartie", self.startGame) self.unDTO = DTOBalance() self.setupBalance() self.attribueurBonus = AttributionBonus() self.accept("MortJoueur", self.finDePartie) self.accept("AjoutFavori", self.ajoutFavori) self.accept("updateArmurie", self.updateArmurie) self.accept("escape", exit) def finDePartie(self, idPerdant): self.tempsFin = time.time() tempsPartie = self.tempsFin - self.tempsDebut for tank in self.map.listTank: if tank.identifiant != idPerdant: vieGagnant = tank.pointDeVie vieMax = tank.pointDeVieMax vieJoueur100 = 100 * vieGagnant / vieMax idGagnant = tank.identifiant #Ajout des bonus if idGagnant == 0: idJoueurGagnant = self.leDTOUsers.getIdUser(self.username1) idJoueurPerdant = self.leDTOUsers.getIdUser(self.username2) messenger.send( "AttribuerBonus", [idJoueurGagnant, self.username1, vieJoueur100, tempsPartie]) else: idJoueurGagnant = self.leDTOUsers.getIdUser(self.username2) idJoueurPerdant = self.leDTOUsers.getIdUser(self.username1) messenger.send( "AttribuerBonus", [idJoueurGagnant, self.username2, vieJoueur100, tempsPartie]) self.map.DTOStat.definitionGagnant(idJoueurGagnant) ####Ajout nb fois jouer if self.idNiveau != 000: self.unDAOUser = DAOOracleUsers() self.unDAOUser.updateNbFoisNivJouer(idJoueurGagnant, self.idNiveau) self.unDAOUser.updateNbFoisNivJouer(idJoueurPerdant, self.idNiveau) self.DAOStats.ecrireStatsFinPartie() def updateArmurie(self, listeInfo): print listeInfo self.unDAOUser = DAOOracleUsers() for info in listeInfo: self.unDAOUser.updateNbArmes(info[0], info[1]) print "un update!" def setup(self): self.setupBulletPhysics() self.setupCamera() self.setupMap() self.setupLightAndShadow() #Création d'une carte de base if self.idNiveau == 000: self.map.construireMapHasard() else: self.map.creerCarteParDefaut() #A besoin des éléments de la map self.setupControle() self.setupInterface() #Fonction d'optimisation #DOIVENT ÊTRE APPELÉE APRÈS LA CRÉATION DE LA CARTE #Ça va prendre les modèles qui ne bougent pas et en faire un seul gros #en faisant un seul gros noeud avec self.map.figeObjetImmobile() #DEBUG: Décommenter pour affiche la hiérarchie #self.pandaBase.startDirect() messenger.send("ChargementTermine") def startGame(self, idNiveau, leDTOMap, leDTOUsers, username1, username2, listeArmes): self.listeArmes = listeArmes self.username1 = username1 self.username2 = username2 self.idNiveau = idNiveau self.leDTOMap = leDTOMap self.leDTOUsers = leDTOUsers self.tempsDebut = time.time() self.setup() self.DAOStats = DAOStatistiques(self.map.DTOStat) self.DAOStats.ecrireInfosDebutPartie() #On démarrer l'effet du compte à rebour. #La fonction callBackDebutPartie sera appelée à la fin self.interfaceMessage.effectCountDownStart( self.unDTO.getValue("messageCompteRebour"), self.callBackDebutPartie) self.interfaceMessage.effectMessageGeneral( self.unDTO.getValue("messageAccueilContenu"), self.unDTO.getValue("messageAccueilDuree")) def setupBulletPhysics(self): debugNode = BulletDebugNode('Debug') debugNode.showWireframe(True) debugNode.showConstraints(True) debugNode.showBoundingBoxes(False) debugNode.showNormals(False) self.debugNP = render.attachNewNode(debugNode) self.mondePhysique = BulletWorld() self.mondePhysique.setGravity(Vec3(0, 0, -9.81)) self.mondePhysique.setDebugNode(self.debugNP.node()) taskMgr.add(self.updatePhysics, "updatePhysics") taskMgr.add(self.updateCarte, "updateCarte") def setupCamera(self): #On doit désactiver le contrôle par défaut de la caméra autrement on ne peut pas la positionner et l'orienter self.pandaBase.disableMouse() #Le flag pour savoir si la souris est activée ou non n'est pas accessible #Petit fail de Panda3D taskMgr.add(self.updateCamera, "updateCamera") self.setupTransformCamera() def setupTransformCamera(self): #Défini la position et l'orientation de la caméra self.positionBaseCamera = Vec3(0, -18, 32) camera.setPos(self.positionBaseCamera) #On dit à la caméra de regarder l'origine (point 0,0,0) camera.lookAt(render) def setupMap(self): self.map = Map(self, self.mondePhysique, self.idNiveau, self.leDTOMap, self.leDTOUsers, self.username1, self.username2, self.listeArmes) #On construire la carte comme une coquille, de l'extérieur à l'intérieur #Décor et ciel self.map.construireDecor(camera) #Plancher de la carte self.map.construirePlancher() #Murs et éléments de la map def setupLightAndShadow(self): #Lumière du skybox plight = PointLight('Lumiere ponctuelle') plight.setColor(VBase4(1, 1, 1, 1)) plnp = render.attachNewNode(plight) plnp.setPos(0, 0, 0) camera.setLight(plnp) #Simule le soleil avec un angle dlight = DirectionalLight('Lumiere Directionnelle') dlight.setColor(VBase4(0.8, 0.8, 0.6, 1)) dlight.get_lens().set_fov(75) dlight.get_lens().set_near_far(0.1, 60) dlight.get_lens().set_film_size(30, 30) dlnp = render.attachNewNode(dlight) dlnp.setPos(Vec3(-2, -2, 7)) dlnp.lookAt(render) render.setLight(dlnp) #Lumière ambiante alight = AmbientLight('Lumiere ambiante') alight.setColor(VBase4(0.25, 0.25, 0.25, 1)) alnp = render.attachNewNode(alight) render.setLight(alnp) #Ne pas modifier la valeur 1024 sous peine d'avoir un jeu laid ou qui lag dlight.setShadowCaster(True, 1024, 1024) #On doit activer l'ombre sur les modèles render.setShaderAuto() def setupControle(self, ): #Créer le contrôle #A besoin de la liste de tank pour relayer correctement le contrôle self.inputManager = InputManager(self.map.listTank, self.debugNP, self.pandaBase) self.accept("initCam", self.setupTransformCamera) def setupInterface(self): self.interfaceTank = [] self.interfaceTank.append( InterfaceTank(0, self.map.listTank[0].couleur)) self.interfaceTank.append( InterfaceTank(1, self.map.listTank[1].couleur)) self.interfaceMessage = InterfaceMessage(self) def callBackDebutPartie(self): #Quand le message d'introduction est terminé, on permet aux tanks de bouger self.inputManager.debuterControle() #Mise à jour du moteur de physique def updateCamera(self, task): #On ne touche pas à la caméra si on est en mode debug if (self.inputManager.mouseEnabled): return task.cont vecTotal = Vec3(0, 0, 0) distanceRatio = 1.0 if (len(self.map.listTank) != 0): for tank in self.map.listTank: vecTotal += tank.noeudPhysique.getPos() vecTotal = vecTotal / len(self.map.listTank) vecTotal.setZ(0) camera.setPos(vecTotal + self.positionBaseCamera) return task.cont #Mise à jour du moteur de physique def updatePhysics(self, task): dt = globalClock.getDt() messenger.send("appliquerForce") self.mondePhysique.doPhysics(dt) #print(len(self.mondePhysique.getManifolds())) #Analyse de toutes les collisions for entrelacement in self.mondePhysique.getManifolds(): node0 = entrelacement.getNode0() node1 = entrelacement.getNode1() self.map.traiterCollision(node0, node1) return task.cont def updateCarte(self, task): self.map.update(task.time) return task.cont def setupBalance(self): unDAO = DAOOracleBalance() if Connexion().getCurseur() != None: self.unDTO = unDAO.read() def ajoutFavori(self, identifiant): self.unDAOUser = DAOOracleUsers() if identifiant == 0: idJoueur = self.leDTOUsers.getIdUser(self.username1) else: idJoueur = self.leDTOUsers.getIdUser(self.username2) self.unDAOUser.ajoutAuFavori(self.idNiveau, idJoueur) def exit(self): Connexion.seDeconnecter() exit()
class World(object): def __init__(self, base, numberOfPlayers): self.base = base self.render = base.render self.taskMgr = base.taskMgr self.loader = base.loader self.windowEventSetup() # Create separate nodes so that shaders apply to # stuff in the worldRender but not the outsideWorldRender self.worldRender = render.attachNewNode("world") self.outsideWorldRender = render.attachNewNode("world") self.base.setFrameRateMeter(True) self.globalClock = ClockObject.getGlobalClock() self.globalClock.setMode(ClockObject.MLimited) self.globalClock.setFrameRate(60) self.base.disableMouse() if not 0 < numberOfPlayers < 5: raise ValueError("Number of players must be from 1 to 4") self.numberOfPlayers = numberOfPlayers self.createLights() self.initialisePhysics(debug=False) # Load the default arena self.level = Level("01.json", self) # disable default cam so that we can have multiplayer base.camNode.setActive(False) # moved player setup out to its own method self.setupPlayers(self.numberOfPlayers) # Set up shaders for shadows and cartoon outline self.setupShaders() # Create an audio manager. This is attached to the camera for # player 1, so sounds close to other players might not be very # loud self.audioManager = Audio3DManager.Audio3DManager(self.base.sfxManagerList[0], self.playerCameras[0]) # Distance should be in m, not feet self.audioManager.setDistanceFactor(3.28084) # Now initialise audio for all vehicles that were # previously set up. We can't just create the audio manager # first because we need the player 1 camera for vehicle in self.playerVehicles: vehicle.initialiseSound(self.audioManager) self.initialiseCollisionInfo() def run(self): """ Start running the game loop by adding it to the task manager """ self.taskMgr.add(self.gameLoop, "update") def setupPlayers(self, numberOfPlayers): """ Method to set up player, camera and skybox Sets up two player splitscreen TODO: Needs reorganising, removal of hardcoding """ self.playerCameras = [] self.playerVehicles = [] self.playerControllers = [] self.playerInputKeys = range(numberOfPlayers) # default camera position bound to each car cameraPosition = (0, -8, 3) vehiclePositions = self.level.spawnPositions if numberOfPlayers > len(vehiclePositions): raise ValueError( "Number of players (%d) requested is greater " "than the number of spawn positions (%d)" % (numberOfPlayers, len(vehiclePositions)) ) # single player display setup - default displayXStart = 0.0 displayXEnd = 1.0 displayYStart = 0.0 displayYEnd = 1.0 # I am not proud of myself for this... There has to be a better way using maths # But it's nearly 2am and I'll figure it out tomorrow for i in xrange(numberOfPlayers): vehiclePosition = vehiclePositions[i] if numberOfPlayers == 2: if i == 0: # player 1, indexes from 0 displayXStart = 0.0 # don't need this but it's safer to have it' displayXEnd = 1.0 displayYStart = 0.5 displayYEnd = 1.0 else: displayXStart = 0.0 # don't need this but it's safer to have it' displayXEnd = 1.0 displayYStart = 0.0 displayYEnd = 0.5 elif numberOfPlayers == 3: if i == 0: # top of screen displayXStart = 0.0 # don't need this but it's safer to have it' displayXEnd = 1.0 displayYStart = 0.5 displayYEnd = 1.0 elif i == 1: # p2, bottom left displayXStart = 0.0 displayXEnd = 0.5 displayYStart = 0.0 displayYEnd = 0.5 else: # bottom right displayXStart = 0.5 displayXEnd = 1.0 displayYStart = 0.0 displayYEnd = 0.5 elif numberOfPlayers == 4: if i == 0: # p1, top left displayXStart = 0.0 # don't need this but it's safer to have it' displayXEnd = 0.5 displayYStart = 0.5 displayYEnd = 1.0 elif i == 1: # p2, top right displayXStart = 0.5 displayXEnd = 1.0 displayYStart = 0.5 displayYEnd = 1.0 elif i == 2: # p3, bottom left displayXStart = 0.0 displayXEnd = 0.5 displayYStart = 0.0 displayYEnd = 0.5 else: # else p4, bottom right displayXStart = 0.5 displayXEnd = 1.0 displayYStart = 0.0 displayYEnd = 0.5 # setup display area for this player's camera displayBox = (displayXStart, displayXEnd, displayYStart, displayYEnd) # set up camera with display area above and default camera position camera = self.createCamera(displayBox, cameraPosition) self.playerCameras.append(camera) # set up player car vehicle = Vehicle(vehiclePosition, self.worldRender, self.world, self.base) self.playerVehicles.append(vehicle) # Create a 2D display region for showing the player's HUD playerRender2d = self.createPlayerDisplay(displayBox) # set up player controller with car, camera and keyset playerController = PlayerControl( self.playerVehicles[i], self.playerCameras[i], self.playerInputKeys[i], playerRender2d ) self.playerControllers.append(playerController) # set up skybox for this particular camera set and hide from other cameras self.createSkybox( self.playerCameras[i], self.playerInputKeys[i] ) # can use inputkey code for bitmaskCode as it will be unique def createPlayerDisplay(self, displayBox): """Create a 2D display region with camera to be used to show things like the speedo and points """ displayRegion = self.base.win.makeDisplayRegion(*displayBox) displayRegion.setSort(20) camera2d = NodePath(Camera("player2dcam")) lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setNearFar(-1000, 1000) camera2d.node().setLens(lens) render2d = NodePath("render2d") render2d.setDepthTest(False) render2d.setDepthWrite(False) camera2d.reparentTo(render2d) displayRegion.setCamera(camera2d) return render2d def windowEventSetup(self): """ Method to bind window events (resizing etc) to windowEventHandler method """ self.base.accept("window-event", self.windowEventHandler) def windowEventHandler(self, window=None): """ Called when the panda window is modified to update FOV and aspect ratio TODO fix hardcoding for camera names """ if window.isClosed(): sys.exit() wp = window.getProperties() windowWidth = wp.getXSize() windowHeight = wp.getYSize() for i in self.playerCameras: # added to allow for changing player number # since window size has changed we need to update the aspect ratio and FOV i.node().getLens().setAspectRatio(windowWidth / (windowHeight / 2)) i.node().getLens().setFov(60) def createCamera(self, dispRegion, pos): """ Method to create a camera. Takes displayRegion and a position tuple. Sets aspect ratio based on window properties and also sets FOV """ camera = base.makeCamera(base.win, displayRegion=dispRegion) windowWidth = base.win.getXSize() windowHeight = base.win.getYSize() camera.node().getLens().setAspectRatio(windowWidth / (windowHeight / 2)) camera.node().getLens().setFov(60) camera.setPos(pos) return camera def initialisePhysics(self, debug=False): """ Create Bullet world for physics objects """ self.world = BulletWorld() self.world.setGravity(Vec3(0, 0, -9.81)) if debug: self.debugNP = self.outsideWorldRender.attachNewNode(BulletDebugNode("Debug")) self.debugNP.show() self.debugNP.node().showWireframe(True) self.debugNP.node().showConstraints(True) self.debugNP.node().showBoundingBoxes(False) self.debugNP.node().showNormals(True) self.world.setDebugNode(self.debugNP.node()) def createSkybox(self, currentCamera, bitmaskCode): """ Create a skybox linked to the current camera setup. Skybox will only be shown to camera with this bitmaskCode TODO: remove bitmaskCode or make it more modular to reduce coupling """ sky = self.loader.loadModel("data/models/sky/cube.egg") diffuse = self.loader.loadTexture(self.level.skyTexture) sky.setTexture(diffuse) sky.setScale(270) # Get it to follow the camera so it feels as if it's infinitely # far away, but call setCompass so it rotates with the world sky.reparentTo(currentCamera) sky.setCompass() # hide skybox from other players sky.hide(BitMask32.allOn()) # show only to this player's camera currentCamera.node().setCameraMask(BitMask32.bit(bitmaskCode)) sky.show(BitMask32.bit(bitmaskCode)) def createLights(self): """Create an ambient light and a spotlight for shadows""" self.render.clearLight() alight = AmbientLight("ambientLight") alight.setColor(Vec4(0.7, 0.7, 0.7, 1)) alightNP = self.worldRender.attachNewNode(alight) self.worldRender.setLight(alightNP) # Create a directional light for shadows dlight = DirectionalLight("dLight") dlight.setColor(Vec4(0.6, 0.6, 0.6, 1)) dlight.setShadowCaster(True, 1024, 1024) dlight.getLens().setNearFar(1, 15) dlight.getLens().setFilmSize(128, 128) dlightNP = self.worldRender.attachNewNode(dlight) dlightNP.setPos(0, 0, 10) dlightNP.lookAt(0, 0, 0) self.worldRender.setLight(dlightNP) def setupShaders(self): """ Creates shaders for cartoon outline and enables shaders for shadows """ if self.base.win.getGsg().getSupportsBasicShaders() == 0: return thickness = 1.0 for camera in self.playerCameras: self.filters = CommonFilters(self.base.win, camera) filterEnabled = self.filters.setCartoonInk(separation=thickness) if filterEnabled == False: # Couldn't enable filter, video card probably # doesn't support filter return self.worldRender.setShaderAuto() def initialiseCollisionInfo(self): """ Sets up information required to later check for collisions """ self.previousContactForce = defaultdict(float) self.collisionDetected = defaultdict(bool) # List of node paths that we want to check for collisions on, # with information about the collision sounds to play self.collisionObjects = dict((v.rigidNode, v.collisionSound) for v in self.playerVehicles) self.collisionObjects.update(self.level.collisionObjects) # For assigning points etc, keep track of the player that # controls each vehicle self.vehicleControllers = dict( (vehicle.rigidNode, player) for vehicle, player in zip(self.playerVehicles, self.playerControllers) ) def checkCollisions(self, dt): """ Check to see what objects have collided and take actions Eg. play sounds, update player points """ # Use the change in the sum of contact force magnitudes to # check for collisions. # Go though contact points, calculating the total contact # force on all nodes of interest totalContactForce = defaultdict(float) for manifold in self.world.getManifolds(): nodes = (manifold.getNode0(), manifold.getNode1()) # Sum all points to get total impulse. Not sure if this is a # good idea or we should use individual points, and if we # need to use force direction rather than just magnitude points = manifold.getManifoldPoints() totalImpulse = sum((p.getAppliedImpulse() for p in points)) # Use force to get a more frame-rate independent measure of # collision magnitude force = totalImpulse / dt for node in nodes: if node in self.collisionObjects: totalContactForce[node] += force # If both objects are vehicles, then update points if all(n in self.vehicleControllers for n in nodes): self.calculateCollisionPoints( manifold, self.vehicleControllers[nodes[0]], self.vehicleControllers[nodes[1]] ) for node, force in totalContactForce.iteritems(): forceChange = force - self.previousContactForce[node] if self.collisionDetected[node]: # If a collision was recently detected, don't keep checking # This avoids sounds repeatedly starting to play continue soundInfo = self.collisionObjects[node] if forceChange > soundInfo.thresholdForce: self.playCollisionSound(soundInfo, forceChange) # Set collision detected, and then set a time out # so that it is later reset to False self.collisionDetected[node] = True self.taskMgr.doMethodLater( 0.2, self.collisionDetected.update, "clearColision", extraArgs=[{node: False}] ) # Want to reset anything not touching to zero, so replace # previous contact forces with current ones rather than updating self.previousContactForce = totalContactForce def playCollisionSound(self, soundInfo, magnitude): """Play a sound when an object is impacted Selects from a list of sounds depending on the collision magnitude, and scales the sound volume by the magnitude """ relativeMagnitude = (magnitude - soundInfo.thresholdForce) / (soundInfo.maxForce - soundInfo.thresholdForce) soundIndex = min(int(relativeMagnitude * len(soundInfo.sounds)), len(soundInfo.sounds) - 1) collisionSound = self.audioManager.loadSfx(soundInfo.sounds[soundIndex]) self.audioManager.attachSoundToObject(collisionSound, soundInfo.nodePath) collisionSound.setVolume(min(0.2 + magnitude / soundInfo.maxForce, 1.0)) collisionSound.play() def calculateCollisionPoints(self, manifold, player1, player2): """Calculate points to give players when vehicles collide """ # Todo: Make this actually assign points based on the vehicle # travelling towards the collision location manifoldPoints = manifold.getManifoldPoints() totalImpulse = sum((p.getAppliedImpulse() for p in manifoldPoints)) player1.add_points(int(totalImpulse) // 100) player2.add_points(int(totalImpulse) // 100) def gameLoop(self, task): dt = self.globalClock.getDt() for i in self.playerControllers: # added to allow for changing player number i.updatePlayer(dt) self.checkCollisions(dt) self.world.doPhysics(dt) return task.cont