Esempio n. 1
0
class GameWindow(pyglet.window.Window):
    """Main game class.  Contains main game parameters as well as the level geometry, camera, level batch (for the drawing of levels and objects), and fps display."""
    def __init__(self,
                 mario_graphics_dir,
                 fullscreen=False,
                 resolution=None,
                 y_inv=False,
                 vsync=False,
                 msaa=1,
                 resizable=True,
                 show_fps=False,
                 font=None):
        self.mario_graphics_dir = mario_graphics_dir
        self.screenshot_dir = mario_graphics_dir / 'screenshots'
        os.makedirs(self.screenshot_dir, exist_ok=True)

        self.y_inv = y_inv
        self.fov = 45
        self.font = font
        self.paused = False
        self.full_res = fullscreen
        self.resolution = resolution
        self.current_level = None
        self.mouse_sensitivity = 0.08
        self.min_mouse_sensitivity = 0.01
        self.max_mouse_sensitivity = 0.15143

        ## Graphics setings
        self.wireframe = False
        self.load_textures = True
        self.load_skyboxes = True

        ## MSAA.
        config = self.get_config(msaa)

        ## Set screen resolution and window border type.
        self.max_x_res = pyglet.canvas.get_display().get_default_screen().width
        self.max_y_res = pyglet.canvas.get_display().get_default_screen(
        ).height
        self.set_resolution()
        border_style = self.get_border_style(fullscreen)

        ## Init pyglet window.
        super().__init__(self.x_res,
                         self.y_res,
                         "Mario 64",
                         resizable=resizable,
                         vsync=vsync,
                         fullscreen=False,
                         config=config,
                         style=border_style)

        if fullscreen:
            self.set_location(0, 0)

        ## Set mouse.
        self.exclusive_mouse = False
        self.set_exclusive_mouse(self.exclusive_mouse)

        ## Set OpenGL state.
        self.set_opengl_state()

        ## Geometry
        self.level_geometry = Geometry(self.mario_graphics_dir)
        self.level_geometry.toggle_group_textures(self.load_textures)
        self.skybox_dict = {
            'wdw': 'wdw',
            'ttm': 'water',
            'thi': 'water',
            'ddd': 'water',
            'hmc': None,
            'bits': 'bits',
            'ccm': 'ccm',
            'pss': None,
            'jrb': 'clouds',
            'rr': 'cloud_floor',
            'bitfs': 'bitfs',
            'cotmc': None,
            'bowser_1': 'bidw',
            'wmotr': 'cloud_floor',
            'ttc': None,
            'lll': 'bitfs',
            'totwc': 'cloud_floor',
            'wf': 'cloud_floor',
            'ssl': 'ssl',
            'sa': 'cloud_floor',
            'vcutm': None,
            'bob': 'water',
            'castle_courtyard': 'water',
            'sl': 'ccm',
            'bitdw': 'bidw',
            'bbh': 'bbh',
            'castle_inside': None,
            'bowser_3': 'bits',
            'bowser_2': 'bitfs',
            'castle_grounds': 'water'
        }

        ## FPS Display
        self.show_fps = show_fps
        self.fps_display = pyglet.window.FPSDisplay(self)
        self.fps_display.update_period = 0.2

        ## Camera
        self.start_area = None
        self.start_yaw = None
        self.start_pos = None
        self.draw_distance = 100000
        self.camera = FirstPersonCamera(y_inv=self.y_inv)

        ## Create menus.
        self.register_menu_event_types()
        self.pause_menu = PauseMenu(self.x_res,
                                    self.y_res,
                                    self.width,
                                    self.height,
                                    self.wireframe,
                                    font=self.font)
        self.set_menu_handlers()

        ## Load intro.
        self.in_intro = True
        self.level_batch = self.load_intro()
        self.push_handlers(self.pause_menu)

        pyglet.clock.schedule(self.on_update)

    def set_resolution(self):
        if self.full_res:
            self.x_res = self.max_x_res
            self.y_res = self.max_y_res
        else:
            self.x_res, self.y_res = self.resolution
            if self.x_res > self.max_x_res:
                self.x_res = self.max_x_res
            if self.y_res > self.max_y_res:
                self.y_res = self.max_y_res

    def get_border_style(self, fullscreen):
        if fullscreen:
            border_style = pyglet.window.Window.WINDOW_STYLE_BORDERLESS
        else:
            border_style = pyglet.window.Window.WINDOW_STYLE_DEFAULT

        return border_style

    def get_config(self, msaa):
        if msaa > 1:
            screen = pyglet.canvas.get_display().get_default_screen()
            template = pyglet.gl.Config(sample_buffers=1, samples=msaa)
            try:
                config = screen.get_best_config(template)
            except:
                config = None

        else:
            config = None

        return config

    def set_opengl_state(self):
        ## Set global OpenGL state based on GameWindow attributes.
        if self.wireframe:
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
        else:
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
            glEnable(GL_DEPTH_TEST)
            glDepthMask(GL_TRUE)
            material_reflectance = (ctypes.c_float * 4)(*(1.0, 1.0, 1.0, 1.0))
            light_direction = (ctypes.c_float * 4)(*(1 / math.sqrt(3),
                                                     1 / math.sqrt(3),
                                                     1 / math.sqrt(3), 0.0))
            glLightfv(GL_LIGHT0, GL_POSITION, light_direction)
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                         material_reflectance)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

    def set_start_pos(self):
        self.start_area, self.start_yaw, *self.start_pos = self.level_geometry.level_scripts[
            self.current_level].mario_pos
        for i in range(3):
            self.start_pos[i] = -1 * self.start_pos[i]

        ## We'll load partially inside the floor if we don't add some height.  But because the camera has negative coordinates, we actually subtract 300 to add 300 height.
        self.start_pos[1] -= 300

    def load_intro(self):
        self.current_level = 'intro'
        level_batch = self.level_geometry.load_intro()
        self.camera.position = [0, 130, -2300]
        self.camera.yaw = 0.0
        self.camera.pitch = 0.0
        self.skybox_present = False

        return level_batch

    def load_new_level(self, level, areas=True):
        if self.in_intro:
            self.in_intro = False
            self.exclusive_mouse = True
            self.set_exclusive_mouse(self.exclusive_mouse)
            self.pop_handlers()
            self.push_handlers(self.camera.input_handler)
        self.current_level = level
        self.set_start_pos()
        self.level_batch = self.level_geometry.load_level(level)
        ## Reset camera position to the new start_pos.
        self.camera.position = self.start_pos
        self.camera.yaw = self.start_yaw
        self.camera.pitch = 0.0
        if self.skybox_dict[level]:
            self.skybox = Skybox(self.skybox_dict[level],
                                 self.mario_graphics_dir)
            self.skybox.skybox_set_fov(self.fov)
            self.skybox_present = True
        else:
            self.skybox_present = False

    def on_mouse_press(self, x, y, button, modifiers):
        if button == pyglet.window.mouse.RIGHT:
            time_str = time.strftime("%Y_%m_%d_%H%M%S",
                                     time.localtime()) + '.png'
            buf = (GLubyte * (4 * self.width * self.height))(0)
            glReadPixels(0, 0, self.width, self.height, GL_RGBA,
                         GL_UNSIGNED_BYTE, buf)
            with open(str((self.screenshot_dir / time_str).resolve()),
                      'wb') as f:
                f.write(
                    util_math.write_png(bytearray(buf), self.width,
                                        self.height))
            return True

    def on_key_press(self, symbol, modifiers):
        ## Pause/unpause the game.
        if symbol == pyglet.window.key.ESCAPE:
            self.pause_game()
            return True

    def on_resize(self, width, height):
        glViewport(0, 0, width, height)
        if hasattr(self, 'pause_menu'):
            self.pause_menu.on_screen_resize(width, height)

    def on_activate(self):
        self.set_exclusive_mouse(self.exclusive_mouse)

    def on_deactivate(self):
        self.set_exclusive_mouse(False)

    def on_draw(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        ## Draw skybox first.
        if self.load_skyboxes:
            if self.skybox_present:
                self.skybox.update_and_draw(self.camera.yaw, self.camera.pitch)
        """
        Set the scene based on the camera's pitch, yaw, and position.
        Applies transforms.  We first rotate and then translate.
        Note that glRotatef( angle, x, y, z ) rotates by angle degrees around the vector ( x, y, z ).
        Since pitch is a rotation around the x-axis, we want to use ( 1, 0, 0 ).
        Since yaw is a rotation around the y-axis, we want to use ( 0, 1 , 0 ).
        Then we translate by the camera's position.  Note that the camera's position is already the negative of its actual worldview position.  Thus, we don't have to multiply by -1 when we're moving the world.
        Finally, we perform a 3D projection based on the fov and the resolution.
        """
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glRotatef(self.camera.pitch, 1.0, 0.0, 0.0)
        glRotatef(self.camera.yaw, 0.0, 1.0, 0.0)
        glTranslatef(*self.camera.position)
        #print( self.camera.pitch, self.camera.yaw, self.camera.position )
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(self.fov, self.x_res / self.y_res, 10,
                       self.draw_distance)
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)

        ## Draw the actual level.
        self.level_batch.draw()

        ## Draw the menu, if applicable.
        if self.paused or self.in_intro:
            self.pause_menu.draw()

        ## Draw the FPS display, if applicable.
        if self.show_fps:
            if self.wireframe:
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
            self.fps_display.draw()
            if self.wireframe:
                glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

    def on_update(self, dt):
        if not self.paused:
            self.camera.update(dt)

    def register_menu_event_types(self):
        ## Button Events
        Menu.register_event_type('exit_game')
        Menu.register_event_type('go_back')
        Menu.register_event_type('enter_submenu')
        LevelSelectMenu.register_event_type('load_new_level')
        OptionsMenu.register_event_type('toggle_skyboxes')
        OptionsMenu.register_event_type('toggle_wireframe')
        OptionsMenu.register_event_type('toggle_textures')
        OptionsMenu.register_event_type('toggle_fps')
        ## Slider Events
        OptionsMenu.register_event_type('set_fov')
        OptionsMenu.register_event_type('set_mouse_sensitivity')

    def set_menu_handlers(self):
        ## OptionsMenu Buttons
        self.pause_menu.options_menu.set_handler('toggle_skyboxes',
                                                 self.toggle_skyboxes)
        self.pause_menu.options_menu.set_handler('toggle_wireframe',
                                                 self.toggle_wireframe)
        self.pause_menu.options_menu.set_handler('toggle_textures',
                                                 self.toggle_textures)
        self.pause_menu.options_menu.set_handler('toggle_fps', self.toggle_fps)
        self.pause_menu.options_menu.set_handler('go_back',
                                                 self.pause_menu.go_back)
        ## OptionsMenu Sliders
        self.pause_menu.options_menu.set_handler('set_fov', self.set_fov)
        self.pause_menu.options_menu.set_handler('set_mouse_sensitivity',
                                                 self.set_mouse_sensitivity)

        ## IntroMenu
        self.pause_menu.intro_menu.set_handler('exit_game', self.exit_game)
        self.pause_menu.intro_menu.set_handler('enter_submenu',
                                               self.pause_menu.enter_submenu)

        ## MainPauseMenu
        self.pause_menu.main_pause_menu.set_handler('exit_game',
                                                    self.exit_game)
        self.pause_menu.main_pause_menu.set_handler(
            'enter_submenu', self.pause_menu.enter_submenu)

        ## LevelSelectMenu
        self.pause_menu.level_select_menu.set_handler('load_new_level',
                                                      self.load_new_level)
        self.pause_menu.level_select_menu.set_handler('go_back',
                                                      self.pause_menu.go_back)
        self.pause_menu.level_select_menu.set_handler(
            'enter_submenu', self.pause_menu.enter_submenu)
        self.pause_menu.level_select_menu_2.set_handler(
            'load_new_level', self.load_new_level)
        self.pause_menu.level_select_menu_2.set_handler(
            'go_back', self.pause_menu.go_back)
        self.pause_menu.level_select_menu_2.set_handler(
            'enter_submenu', self.pause_menu.enter_submenu)

    def pause_game(self):
        if self.paused == True:
            self.paused = False
            self.exclusive_mouse = True
            self.set_exclusive_mouse(self.exclusive_mouse)
            ## Stop PauseMenu from receiving user input and instead send the input to the camera's input_handler.
            self.pop_handlers()
            self.push_handlers(self.camera.input_handler)
            self.pause_menu.current_menu = self.pause_menu.main_pause_menu
            self.pause_menu.menu_stack = [self.pause_menu.main_pause_menu]

        elif self.paused == False:
            if self.in_intro == True:
                self.in_intro = False
                self.exclusive_mouse = True
                self.set_exclusive_mouse(self.exclusive_mouse)
                self.pop_handlers()
                self.push_handlers(self.camera.input_handler)

            else:
                self.paused = True
                self.exclusive_mouse = False
                self.set_exclusive_mouse(self.exclusive_mouse)
                self.set_mouse_position(int(self.width / 2),
                                        int(self.height / 2))
                self.pop_handlers()
                self.pause_menu.current_menu = self.pause_menu.main_pause_menu
                self.pause_menu.menu_stack = [self.pause_menu.main_pause_menu]
                self.push_handlers(self.pause_menu)

    def exit_game(self):
        self.on_close()

    def toggle_bool(self, attribute):
        if getattr(self, attribute) == False:
            setattr(self, attribute, True)
        else:
            setattr(self, attribute, False)

    def toggle_textures(self):
        self.toggle_bool('load_textures')
        self.level_geometry.toggle_group_textures(self.load_textures)

    def toggle_wireframe(self):
        if self.wireframe == False:
            self.wireframe = True
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
            glDisable(GL_DEPTH_TEST)
            glDisable(GL_ALPHA_TEST)
            glDisable(GL_BLEND)
        else:
            self.wireframe = False
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
            glEnable(GL_DEPTH_TEST)
            glEnable(GL_ALPHA_TEST)
            glEnable(GL_BLEND)
        self.pause_menu.set_wireframe(self.wireframe)

    def toggle_fps(self):
        self.toggle_bool('show_fps')

    def toggle_skyboxes(self):
        self.toggle_bool('load_skyboxes')

    def set_fov(self, fov):
        self.fov = fov
        if hasattr(self, 'skybox'):
            self.skybox.skybox_set_fov(self.fov)

    def percent_to_sensitivity(self, percent):
        """For user experience, the mouse sensitivity slider goes from 1 to 100.  However, the actual mouse sensitivity used will vary from self.min_mouse_sensitivity (corresponds to 1) to self.max_mouse_sensitivity (corresponds to 100).  This function takes a number between 1 and 100 inclusive and converts it to the actual mouse sensitivity used."""
        return (((percent - 1) / 99) *
                (self.max_mouse_sensitivity - self.min_mouse_sensitivity) +
                self.min_mouse_sensitivity)

    def set_mouse_sensitivity(self, sensitivity):
        self.mouse_sensitivity = self.percent_to_sensitivity(sensitivity)
        self.camera.mouse_sensitivity = self.mouse_sensitivity