Exemple #1
0
class World(DirectObject):
    def __init__(self):

        #Our standard title and instructions text
        self.title = OnscreenText(text="Panda3D: Tutorial - Musicbox(sounds)",
                                  style=1,
                                  fg=(1, 1, 1, 1),
                                  pos=(0.7, -0.95),
                                  scale=.07)
        self.escapeEventText = OnscreenText(text="ESC: Quit",
                                            style=1,
                                            fg=(1, 1, 1, 1),
                                            pos=(-1.3, 0.95),
                                            align=TextNode.ALeft,
                                            scale=.05)

        #Set up the key input
        self.accept('escape', sys.exit)

        #Fix the camera position
        base.disableMouse()

        #Loading sounds is done in a similar way to loading other things
        #Loading the main music box song
        self.musicBoxSound = base.loadMusic('music/musicbox.ogg')
        self.musicBoxSound.setVolume(.5)  #Volume is a percentage from 0 to 1
        self.musicBoxSound.setLoopCount(
            0)  #0 means loop forever, 1 (default) means
        #play once. 2 or higher means play that
        #many times

        #Sound objects do not have a pause function, just play and stop. So we will
        #Use this variable to keep track of where the sound is at when it was stoped
        #to impliment pausing
        self.musicTime = 0

        #Loading the open/close effect
        #loadSFX and loadMusic are identical. They are often used for organization
        #(loadMusic is used for background music, loadSfx is used for other effects)
        self.lidSfx = base.loadSfx('music/openclose.ogg')
        #The open/close file has both effects in it. Fortunatly we can use intervals
        #to easily define parts of a sound file to play
        self.lidOpenSfx = SoundInterval(self.lidSfx, duration=2, startTime=0)
        self.lidCloseSfx = SoundInterval(self.lidSfx, startTime=5)

        #For this tutorial, it seemed appropriate to have on screen controls. The
        #following code creates them
        #This is a label for a slider
        self.sliderText = OnscreenText("Volume",
                                       style=1,
                                       fg=(1, 1, 1, 1),
                                       pos=(0, 0.8),
                                       scale=.07)
        #The slider itself. It calls self.setMusicBoxVolume when changed
        self.slider = DirectSlider(pos=Vec3(0, 0, .7),
                                   value=.50,
                                   command=self.setMusicBoxVolume)
        #A button that calls self.toggleMusicBox when pressed
        self.button = DirectButton(pos=Vec3(.7, 0, .7),
                                   text="Open Box",
                                   scale=.1,
                                   pad=(.5, .5),
                                   rolloverSound=None,
                                   clickSound=None,
                                   command=self.toggleMusicBox)

        #A variable to represent the state of the simulation. It starts closed
        self.boxOpen = False

        #Here we load and set up the music box. It was modeled in a complex way, so
        #setting it up will be complicated
        self.musicBox = loader.loadModel('models/MusicBox')
        self.musicBox.setPos(0, 60, -10)
        self.musicBox.reparentTo(render)
        #Just like the scene graph contains hierarchies of nodes, so can
        #models. You can get the NodePath for the node using the find
        #function, and then you can animate the model by moving its parts
        #To see the hierarchy of a model, use, the ls function
        #self.musicBox.ls() prints out the entire hierarchy of the model

        #Finding pieces of the model
        self.Lid = self.musicBox.find('**/lid')
        self.Panda = self.musicBox.find('**/turningthing')

        #This model was made with the hinge in the wrong place
        #this is here so we have something to turn
        self.HingeNode = self.musicBox.find('**/box').attachNewNode(
            'nHingeNode')
        self.HingeNode.setPos(.8659, 6.5, 5.4)
        #WRT - ie with respect to. Reparents the object without changing
        #its position, size, or orientation
        self.Lid.wrtReparentTo(self.HingeNode)
        self.HingeNode.setHpr(0, 90, 0)

        #This sets up an interval to play the close sound and actually close the box
        #at the same time.
        self.lidClose = Parallel(
            self.lidCloseSfx,
            LerpFunc(self.HingeNode.setP,
                     duration=2,
                     fromData=0,
                     toData=90,
                     blendType='easeInOut'))

        #Same thing for opening the box
        self.lidOpen = Parallel(
            self.lidOpenSfx,
            LerpFunc(self.HingeNode.setP,
                     duration=2,
                     fromData=90,
                     toData=0,
                     blendType='easeInOut'))

        #The interval for turning the panda
        self.PandaTurn = self.Panda.hprInterval(7, Vec3(360, 0, 0))
        #Do a quick loop and pause to set it as a looping interval so it can be
        #started with resume and loop properly
        self.PandaTurn.loop()
        self.PandaTurn.pause()

    def setMusicBoxVolume(self):
        #Simply reads the current value from the slider and sets it in the sound
        newVol = self.slider.guiItem.getValue()
        self.musicBoxSound.setVolume(newVol)

    def toggleMusicBox(self):
        if self.boxOpen:
            #close the box
            self.lidClose.start()  #Start the close box interval
            self.PandaTurn.pause()  #Pause the figurine turning
            #Save the current time of the music box song
            self.musicTime = self.musicBoxSound.getTime()
            self.musicBoxSound.stop()  #Stop the music box song
            self.button['text'] = "Open Box"  #Prepare to change button label
        else:
            #open the box
            self.lidOpen.start()  #Start the open box interval
            self.PandaTurn.resume()  #Resume the figuring turning
            #Reset the time of the music box song so it starts where it left off
            self.musicBoxSound.setTime(self.musicTime)
            self.musicBoxSound.play()  #Play the music box song
            self.button['text'] = "Close Box"  #Prepare to change button label

        self.button.setText()  #Actually change the button label
        self.boxOpen = not self.boxOpen  #Set our state to opposite what it was
Exemple #2
0
class Connect4:
    def __init__(self, p_base):

        self.base = p_base
        self.render = p_base.render

        # Keyboard inputs map
        self.keyMap = {"left": False, "right": False, "down": False, "drop": False}

        # Global parameters
        self.player = 1
        self.speed = 15
        self.movement_V = False
        self.movement_H = False
        self.axes_H = [-3.6, -2.4, -1.2, 0, 1.2, 2.4, 3.6]
        self.axes_V = [0.25, -1.0, -2.25, -3.5, -4.75, -6]
        self.column = 3
        self.line = 5
        self.quit_game_bool = False
        self.round = 0

        # Initialization of audio coin
        self.audio_coin = self.base.loader.loadMusic("connect4/audio/coin.ogg")

        # Initialization of the table
        self.table = self.base.loader.loadModel("connect4/models/table")
        self.table.reparentTo(self.render)
        self.table.setScale(2, 2, 2)
        self.table.setHpr(90, 0, 0)
        self.table_anim_start = self.table.posInterval(3, Point3(0, 30, -8), startPos=Point3(0, 0, -8))
        self.table_anim_end = self.table.posInterval(3, Point3(0, 0, -8), startPos=Point3(0, 30, -8))

        # Initialization of the grid
        self.grid = self.base.loader.loadModel("connect4/models/grid")
        self.grid.reparentTo(self.render)
        self.grid.setColor(0.1, 0.2, 0.8, 1.0)
        self.grid.setHpr(90, 0, 0)
        self.grid.setScale(0.6, 0.6, 0.625)
        self.grid_anim_start = self.grid.posInterval(3, Point3(3.6, 30, -6), startPos=Point3(3.6, 30, 0))
        self.grid_anim_end = self.grid.posInterval(3, Point3(3.6, 30, 0), startPos=Point3(3.6, 30, -6))
        self.gridContent = np.zeros(6 * 7)

        # Initialization of the discs
        self.discs = []
        self.nb_discs = 44
        for i in range(0, self.nb_discs):
            disc = self.base.loader.loadModel("connect4/models/disc")
            disc.reparentTo(self.render)
            if i % 2 == 0:
                color_disc = Disc(i, disc, 1.0, 0.0, 0.0)
            else:
                color_disc = Disc(i, disc, 1.0, 1.0, 0.0)
            self.discs.append(color_disc)
        self.first_disc_anim = self.discs[self.round].disc.posInterval(3, Point3(0, 30, 1.5), startPos=Point3(0, 0, 8))

        # Initialization of start sequences
        self.init_sequence = Parallel(self.table_anim_start, self.grid_anim_start, self.first_disc_anim, name="p_start")
        self.init_sequence.start()

        # Initialization of keys
        self.base.accept("arrow_left", self.updateKeyMap, ["left", True])
        self.base.accept("arrow_left-up", self.updateKeyMap, ["left", False])
        self.base.accept("arrow_right", self.updateKeyMap, ["right", True])
        self.base.accept("arrow_right-up", self.updateKeyMap, ["right", False])
        self.base.accept("arrow_down", self.updateKeyMap, ["down", True])
        self.base.accept("arrow_down-up", self.updateKeyMap, ["down", False])
        self.base.accept("space", self.updateKeyMap, ["drop", True])
        self.base.accept("space-up", self.updateKeyMap, ["drop", False])

        # Initialization of winning cases
        self.results = []
        with open("connect4/csv/cases.csv") as csvfile:
            reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC)
            for row in reader:
                self.results.append(row)

        # Initialization of fonts
        self.font = self.base.loader.loadFont("connect4/font/Roboto-Medium.ttf")
        self.font.setPixelsPerUnit(60)

        # Initialization of the victory text
        self.text_victory = OnscreenText(text='', pos=(1.4, -0.8), scale=0.1)
        self.text_victory.setFg((0, 0, 0, 1))
        self.text_victory.setBg((1, 1, 1, 0))
        self.text_victory.setShadow((0.5, 0.5, 0.5, 1))

        # Initialization of buttons
        self.load_game_button = DirectButton(text="Load", pos=(-1.5, 0, 0.75), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                             text_scale=0.9, command=self.load_game)

        self.new_game_button = DirectButton(text="New game", pos=(-1.5, 0, 0.9), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                            text_scale=0.9, command=self.new_game)
        self.button_changed = False

        self.save_game_button = DirectButton(text="Save", pos=(-1.5, 0, 0.6), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                             text_scale=0.9, command=self.save_game)

        self.quit_game_button = DirectButton(text="Quit", pos=(-1.5, 0, -0.95), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                             text_scale=0.9, command=self.quit_game)

        self.hand_control_button = DirectButton(text="Activer le contrôle \n visuel", pos=(1.5, 0, -0.9), frameSize=(-3, 3, -1, 0.8), scale=.1,
                                             text_scale=0.5, command=self.activate_hand_control)
        # Mode
        # (mode 0 : default mode)
        # (mode 1 : hand control mode)
        self.mode = 0

        self.disc_caught = -1
        self.disc_dropped = -1

        # Initialization of the right hand
        self.right_hand = self.base.loader.loadModel("connect4/models/hand")
        self.right_hand.reparentTo(self.render)
        self.right_hand.setPos(3.6, -20, 0)
        self.right_hand.setColor(0.88, 0.67, 0.41, 1.0)
        self.right_hand.setHpr(90, -90, 0)
        self.right_hand.setScale(0.2, 0.2, 0.2)

        # self.left_hand = self.base.loader.loadModel("connect4/models/hand")
        # self.left_hand.reparentTo(self.render)
        # self.left_hand.setPos(-3.6, -20, 0)
        # self.left_hand.setColor(0.88, 0.67, 0.41, 1.0)
        # self.left_hand.setHpr(90, -90, 180)
        # self.left_hand.setScale(0.2, 0.2, 0.2)

    def activate_hand_control(self):
        if self.mode == 0:
            self.mode = 1
            self.hand_control_button.setText("Désactiver le contrôle \n visuel")
            self.right_hand.setPos(3.6, 30, 0)
            self.new_game()
            # self.left_hand.setPos(-3.6, 20, 0)
        else:
            self.mode = 0
            self.hand_control_button.setText("Activer le contrôle \n visuel")
            self.right_hand.setPos(3.6, -20, 0)
            self.new_game()
            # self.left_hand.setPos(-3.6, -20, 0)

    def updateKeyMap(self, key, state):
        """ Function that updates the input map """
        self.keyMap[key] = state

    def load_game(self):
        """ Load game functions used for load game button """
        print("Connect 4 > Load a game")
        f1 = open("connect4/safeguard/safeguard.txt", "r")
        last_line = f1.readlines()[-1]
        f1.close()
        last_line_list = last_line.split(',')
        k = 0
        p = 1
        round = 0
        for i in range(0, 42):
            col = i % 7
            line = i // 7
            if last_line_list[i] == '1':
                self.discs[k].disc.setPos(self.axes_H[col], 30, self.axes_V[line])
                k += 2
                round += 1
            elif last_line_list[i] == '2':
                self.discs[p].disc.setPos(self.axes_H[col], 30, self.axes_V[line])
                p += 2
                round += 1
        self.round = round
        self.discs[self.round].disc.setPos(0, 30, 1.5)
        self.gridContent = [int(j) for j in last_line_list]

    def new_game(self):
        """ New game functions used for new game button """
        print("Connect 4 > New game")

        self.gridContent = np.zeros(6 * 7)
        self.round = 0
        self.text_victory.setText('')

        if self.mode == 0:
            for i in range(0, 42):
                self.discs[i].disc.setPos(100, 100, 100)
            self.discs[self.round].disc.setPos(0, 30, 1.5)

        elif self.mode == 1:
            pos_xr = [-8.4, -7.2, -6.0,
                      -12, -10.8, -9.6, -8.4, -7.2, -6.0,
                      -12, -10.8, -9.6, -8.4, -7.2, -6.0,
                      -12, -10.8, -9.6, -8.4, -7.2, -6.0]
            pos_xy = [8.4, 7.2, 6.0,
                      12, 10.8, 9.6, 8.4, 7.2, 6.0,
                      12, 10.8, 9.6, 8.4, 7.2, 6.0,
                      12, 10.8, 9.6, 8.4, 7.2, 6.0]
            pos_z = [-6.4, -6.4, -6.4,
                     -5.2, -5.2, -5.2, -5.2, -5.2, -5.2,
                     -4.0, -4.0, -4.0, -4.0, -4.0, -4.0,
                     -2.8, -2.8, -2.8, -2.8, -2.8, -2.8]
            n = 0
            p = 0
            for i in range(0, 42):
                if i % 2 == 0:
                    self.discs[i].disc.setPos(pos_xr[n], 30, pos_z[n])
                    n += 1
                else:
                    self.discs[i].disc.setPos(pos_xy[p], 30, pos_z[p])
                    p += 1

    def save_game(self):
        """ Save game functions used for save game button """
        print("Connect 4 > Save the game")
        grid = [int(j) for j in self.gridContent]
        grid_content_str = ','.join([str(elem) for elem in grid])
        f = open("connect4/safeguard/safeguard.txt", "a")
        f.write(grid_content_str + "\n")
        f.close()

    def quit_game(self):
        """ Quit game functions used for quit game button """
        print("Connect 4 > Quit the game")
        for i in range(0, self.nb_discs):
            self.discs[i].disc.removeNode()
        self.grid.removeNode()
        self.table.removeNode()
        self.new_game_button.destroy()
        self.save_game_button.destroy()
        self.load_game_button.destroy()
        self.quit_game_button.destroy()
        self.quit_game_bool = True

    def check_victory(self):
        """
        Function that check if there is a victory case
        @return 1 if red wins and 2 if yellow wins
        """

        if self.mode == 0:
            num_disc = self.round
        elif self.mode == 1:
            num_disc = self.disc_dropped

        if num_disc % 2 == 0:
            disc_type = 1
        else:
            disc_type = 2
        self.gridContent[7 * self.line + self.column] = disc_type

        for i in range(69):
            for j in range(4):
                if self.results[i][j] == 7 * self.line + self.column:
                    if (self.gridContent[int(self.results[i][0])] == disc_type) and (
                            self.gridContent[int(self.results[i][1])] == disc_type) and (
                            self.gridContent[int(self.results[i][2])] == disc_type) and (
                            self.gridContent[int(self.results[i][3])] == disc_type):
                        return disc_type
        return 0

    def mainloop(self):
        """ Main loop of the connect 4 game """

        # If quit_button is clicked
        if self.quit_game_bool:
            return 0

        # Get the clock
        dt = globalClock.getDt()

        # Change the button "New game" to "Restart" for the first round
        if self.round == 1 and self.button_changed == False:
            self.new_game_button["text"] = "Restart"
            self.button_changed = True
            print("Connect 4 > Main loop")

        # Default mode
        if self.mode == 0:

            # Get the position of the current disc
            pos = self.discs[self.round].disc.getPos()

            # Left click
            if self.keyMap["left"] and self.column != 0 and not self.movement_V:
                self.keyMap["left"] = False
                self.column -= 1
                self.movement_H = True

            # Right click
            if self.keyMap["right"] and self.column != 6 and not self.movement_V:
                self.keyMap["right"] = False
                self.column += 1
                self.movement_H = True

            # down clic
            if self.keyMap["down"] and self.gridContent[self.column] == 0 and not self.movement_V:
                # To have only one click
                self.keyMap["down"] = False

                # Find the final disc line
                line_fixed = 0
                self.line = 5
                while line_fixed == 0 and self.line >= 0:
                    if self.gridContent[7 * self.line + self.column] != 0:
                        self.line -= 1
                    else:
                        line_fixed = 1
                self.movement_V = True

                # check if there is a victory or not
                victory = self.check_victory()
                if victory == 1:
                    self.text_victory.setText('Red wins')
                if victory == 2:
                    self.text_victory.setText('Yellow wins')

            # Progressive vertical movement
            if self.movement_V and pos.z >= self.axes_V[self.line]:
                pos.z -= self.speed * dt
                self.discs[self.round].disc.setPos(pos)

            # Set the disc position / Prepare next disc
            if self.movement_V and pos.z <= self.axes_V[self.line]:
                pos.z = self.axes_V[self.line]
                self.discs[self.round].disc.setPos(pos)
                self.audio_coin.play()
                self.movement_V = False
                self.line = 0
                self.column = 3
                self.round += 1
                if self.round < 42 and self.mode == 0:
                    self.discs[self.round].disc.setPos(0, 30, 1.5)

            # Horizontal movement
            if self.mode == 0 and self.movement_H:
                pos.x = self.axes_H[self.column]
                self.discs[self.round].disc.setPos(pos)
                self.movement_H = False

        # Handplay mode
        if self.mode == 1:

            # Detect hand position
            if cap.isOpened():
                success, image = cap.read()

                image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)

                image.flags.writeable = False
                results = hands.process(image)

                # Draw the hand annotations on the image.
                image.flags.writeable = True
                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

                # If a hand is detected
                if results.multi_hand_landmarks:
                    for hand_landmarks in results.multi_hand_landmarks:
                        for idx, landmark in enumerate(hand_landmarks.landmark):
                            if idx == 9:
                                x = 24 * landmark.x - 12
                                z = - 14 * landmark.y + 7
                                self.right_hand.setPos(x, 30, z)

                # If a is caught
                if self.disc_caught != -1:
                    self.discs[self.disc_caught].disc.setPos(self.right_hand.getPos().x - 0.5, 30, self.right_hand.getPos().z + 0.5)
                else:
                    for i in range(0, 42):
                        if (abs(self.right_hand.getPos().x - 0.5 - self.discs[i].disc.getPos().x) < 0.5) \
                                and abs(self.right_hand.getPos().z - self.discs[i].disc.getPos().z) < 0.5:
                            self.disc_caught = self.discs[i].id
                            print("Connect 4 > Disc n°", self.disc_caught, " is caught")
                            self.discs[self.disc_caught].disc.setPos(x - 0.5, 30, z + 0.5)

                # If space touch is pressed
                if self.keyMap["drop"]:
                    print("Connect 4 > Disc n°", self.disc_caught, " is dropped")
                    self.keyMap["drop"] = False
                    pos_x = self.discs[self.disc_caught].disc.getPos().x
                    min = 10
                    for i in range(len(self.axes_H)):
                        if abs(self.axes_H[i] - pos_x) < min:
                            self.column = i
                            min = abs(self.axes_H[i] - pos_x)

                    # Find the final disc line
                    line_fixed = 0
                    self.line = 5
                    while line_fixed == 0 and self.line >= 0:
                        if self.gridContent[7 * self.line + self.column] != 0:
                            self.line -= 1
                        else:
                            line_fixed = 1
                    self.movement_V = True

                    self.discs[self.disc_caught].disc.setPos(self.axes_H[self.column], 30, self.axes_V[0])
                    self.disc_dropped = self.disc_caught
                    self.disc_caught = -1

                    # check if there is a victory or not
                    victory = self.check_victory()
                    if victory == 1:
                        self.text_victory.setText('Red wins')
                    if victory == 2:
                        self.text_victory.setText('Yellow wins')

                # Progressive vertical movement
                pos = self.discs[self.disc_dropped].disc.getPos()
                if self.movement_V and pos.z >= self.axes_V[self.line]:
                    pos.z -= self.speed * dt
                    self.discs[self.disc_dropped].disc.setPos(pos)

                # Set the disc position
                if self.movement_V and pos.z <= self.axes_V[self.line]:
                    pos.z = self.axes_V[self.line]
                    self.discs[self.disc_dropped].disc.setPos(pos)
                    self.audio_coin.play()
                    self.movement_V = False
                    self.line = 0
                    self.column = 3
                    self.round += 1







        return 1
Exemple #3
0
class MusicBox(DirectObject):
    def __init__(self):
        # Our standard title and instructions text
        self.title = OnscreenText(text="Panda3D: Tutorial - Music Box",
                                  parent=base.a2dBottomCenter,
                                  pos=(0, 0.08), scale=0.08,
                                  fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5))
        self.escapeText = OnscreenText(text="ESC: Quit", parent=base.a2dTopLeft,
                                       fg=(1, 1, 1, 1), pos=(0.06, -0.1),
                                       align=TextNode.ALeft, scale=.05)

        # Set up the key input
        self.accept('escape', sys.exit)

        # Fix the camera position
        base.disableMouse()

        # Loading sounds is done in a similar way to loading other things
        # Loading the main music box song
        self.musicBoxSound = loader.loadMusic('music/musicbox.ogg')
        self.musicBoxSound.setVolume(.5)  # Volume is a percentage from 0 to 1
        # 0 means loop forever, 1 (default) means
        # play once. 2 or higher means play that many times
        self.musicBoxSound.setLoopCount(0)

        # Set up a simple light.
        self.plight = PointLight("light")
        self.plight.setColor((0.7, 0.7, 0.5, 1))
        light_path = base.render.attachNewNode(self.plight)
        light_path.setPos(0, 0, 20)
        base.render.setLight(light_path)

        alight = AmbientLight("ambient")
        alight.setColor((0.3, 0.3, 0.4, 1))
        base.render.setLight(base.render.attachNewNode(alight))

        # Enable per-pixel lighting
        base.render.setShaderAuto()

        # Sound objects do not have a pause function, just play and stop. So we will
        # Use this variable to keep track of where the sound is at when it was stoped
        # to impliment pausing
        self.musicTime = 0

        # Loading the open/close effect
        # loadSFX and loadMusic are identical. They are often used for organization
        #(loadMusic is used for background music, loadSfx is used for other effects)
        self.lidSfx = loader.loadSfx('music/openclose.ogg')
        # The open/close file has both effects in it. Fortunatly we can use intervals
        # to easily define parts of a sound file to play
        self.lidOpenSfx = SoundInterval(self.lidSfx, duration=2, startTime=0)
        self.lidCloseSfx = SoundInterval(self.lidSfx, startTime=5)

        # For this tutorial, it seemed appropriate to have on screen controls.
        # The following code creates them.
        # This is a label for a slider
        self.sliderText = OnscreenText("Volume", pos=(-0.1, 0.87), scale=.07,
                                       fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
        # The slider itself. It calls self.setMusicBoxVolume when changed
        self.slider = DirectSlider(pos=(-0.1, 0, .75), scale=0.8, value=.50,
                                   command=self.setMusicBoxVolume)
        # A button that calls self.toggleMusicBox when pressed
        self.button = DirectButton(pos=(.9, 0, .75), text="Open",
                                   scale=.1, pad=(.2, .2),
                                   rolloverSound=None, clickSound=None,
                                   command=self.toggleMusicBox)

        # A variable to represent the state of the simulation. It starts closed
        self.boxOpen = False

        # Here we load and set up the music box. It was modeled in a complex way, so
        # setting it up will be complicated
        self.musicBox = loader.loadModel('models/MusicBox')
        self.musicBox.setPos(0, 60, -9)
        self.musicBox.reparentTo(render)
        # Just like the scene graph contains hierarchies of nodes, so can
        # models. You can get the NodePath for the node using the find
        # function, and then you can animate the model by moving its parts
        # To see the hierarchy of a model, use, the ls function
        # self.musicBox.ls() prints out the entire hierarchy of the model

        # Finding pieces of the model
        self.Lid = self.musicBox.find('**/lid')
        self.Panda = self.musicBox.find('**/turningthing')

        # This model was made with the hinge in the wrong place
        # this is here so we have something to turn
        self.HingeNode = self.musicBox.find(
            '**/box').attachNewNode('nHingeNode')
        self.HingeNode.setPos(.8659, 6.5, 5.4)
        # WRT - ie with respect to. Reparents the object without changing
        # its position, size, or orientation
        self.Lid.wrtReparentTo(self.HingeNode)
        self.HingeNode.setHpr(0, 90, 0)

        # This sets up an interval to play the close sound and actually close the box
        # at the same time.
        self.lidClose = Parallel(
            self.lidCloseSfx,
            LerpHprInterval(self.HingeNode, 2.0, (0, 90, 0), blendType='easeInOut'))

        # Same thing for opening the box
        self.lidOpen = Parallel(
            self.lidOpenSfx,
            LerpHprInterval(self.HingeNode, 2.0, (0, 0, 0), blendType='easeInOut'))

        # The interval for turning the panda
        self.PandaTurn = self.Panda.hprInterval(7, (360, 0, 0))
        # Do a quick loop and pause to set it as a looping interval so it can be
        # started with resume and loop properly
        self.PandaTurn.loop()
        self.PandaTurn.pause()

    def setMusicBoxVolume(self):
        # Simply reads the current value from the slider and sets it in the
        # sound
        newVol = self.slider.guiItem.getValue()
        self.musicBoxSound.setVolume(newVol)

    def toggleMusicBox(self):
        #if self.lidOpen.isPlaying() or self.lidClose.isPlaying():
        #    # It's currently already opening or closing
        #    return

        if self.boxOpen:
            self.lidOpen.pause()

            self.lidClose.start()  # Start the close box interval
            self.PandaTurn.pause()  # Pause the figurine turning
            # Save the current time of the music
            self.musicTime = self.musicBoxSound.getTime()
            self.musicBoxSound.stop()  # Stop the music
            self.button['text'] = "Open"  # Prepare to change button label
        else:
            self.lidClose.pause()

            self.lidOpen.start()  # Start the open box interval
            self.PandaTurn.resume()  # Resume the figuring turning
            # Reset the time of the music so it starts where it left off
            self.musicBoxSound.setTime(self.musicTime)
            self.musicBoxSound.play()  # Play the music
            self.button['text'] = "Close"  # Prepare to change button label

        self.button.setText()  # Actually change the button label
        # Set our state to opposite what it was
        self.boxOpen = not self.boxOpen