Пример #1
0
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
Пример #2
0
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
Пример #3
0
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
Пример #4
0
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()
Пример #5
0
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