Search code examples
pythoninputpyglet

Pyglet on_key_press and on_mouse_motion are being very inconsistent


Background

I've been toying around with pyglet when I stumbled across this guy's Minecraft clone. Github is here: https://github.com/fogleman/Minecraft

I've made some modifications (for Python 3 and some of my own preferences), and the complete code is here: modified minecraft.

The Problem

Whenver I run the code, it may sometimes not register any mouse movements or key presses. It is rare, but it can happen occasionally. I would say that out of 10 times, it'll break once.

Details

I don't even know what the culprit is, but I'll provide some snippets code.

It's unpredictable, but there are some ways to fix it. The only sure-fire way currently is to FORCE QUIT (not just quit) the application and then restart it.

I'm not sure why, and I've tried all sorts of things to try and fix it.

If it matters, I'm using macOS Mojave, Python 3.8.2, and Pyglet 1.5.14

Here's the __init__ function for the window:

def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Whether or not the window exclusively captures the mouse.
        self.exclusive = False

        # When flying gravity has no effect and speed is increased.
        self.flying = False

        # Strafing is moving lateral to the direction you are facing,
        # e.g. moving to the left or right while continuing to face forward.
        #
        # First element is -1 when moving forward, 1 when moving back, and 0
        # otherwise. The second element is -1 when moving left, 1 when moving
        # right, and 0 otherwise.
        self.strafe = [0, 0]

        # Current (x, y, z) position in the world, specified with floats. Note
        # that, perhaps unlike in math class, the y-axis is the vertical axis.
        self.position = (0, 0, 0)

        # First element is rotation of the player in the x-z plane (ground
        # plane) measured from the z-axis down. The second is the rotation
        # angle from the ground plane up. Rotation is in degrees.
        #
        # The vertical plane rotation ranges from -90 (looking straight down) to
        # 90 (looking straight up). The horizontal rotation range is unbounded.
        self.rotation = (0, 0)

        # Which sector the player is currently in.
        self.sector = None

        # The crosshairs at the center of the screen.
        self.reticle = None

        # Velocity in the y (upward) direction.
        self.dy = 0

        # A list of blocks the player can place. Hit num keys to cycle.
        self.inventory = [BRICK, GRASS, SAND]

        # The current block the user can place. Hit num keys to cycle.
        self.block = self.inventory[0]

        # Convenience list of num keys.
        self.num_keys = [
            key._1, key._2, key._3, key._4, key._5,
            key._6, key._7, key._8, key._9, key._0]

        # Instance of the model that handles the world.
        self.model = Model()

        # The label that is displayed in the top left of the canvas.
        self.label = pyglet.text.Label('', font_name='Arial', font_size=18,
            x=10, y=self.height - 10, anchor_x='left', anchor_y='top',
            color=(0, 0, 0, 255))

        # This call schedules the `update()` method to be called
        # TICKS_PER_SEC. This is the main game event loop.
        pyglet.clock.schedule_interval(self.update, 1.0 / TICKS_PER_SEC)

Here's the input handlers:

def on_mouse_press(self, x, y, button, modifiers):
        """ Called when a mouse button is pressed. See pyglet docs for button
        amd modifier mappings.

        Parameters
        ----------
        x, y : int
            The coordinates of the mouse click. Always center of the screen if
            the mouse is captured.
        button : int
            Number representing mouse button that was clicked. 1 = left button,
            4 = right button.
        modifiers : int
            Number representing any modifying keys that were pressed when the
            mouse button was clicked.

        """
        if self.exclusive:
            vector = self.get_sight_vector()
            block, previous = self.model.hit_test(self.position, vector)
            if (button == mouse.RIGHT) or \
                    ((button == mouse.LEFT) and (modifiers & key.MOD_CTRL)):
                # ON OSX, control + left click = right click.
                if previous:
                    self.model.add_block(previous, self.block)
            if button == pyglet.window.mouse.LEFT and block:
                texture = self.model.world[block]
                self.model.remove_block(block)
        else:
            self.set_exclusive_mouse(True)

    def on_mouse_motion(self, x, y, dx, dy):
        """ Called when the player moves the mouse.

        Parameters
        ----------
        x, y : int
            The coordinates of the mouse click. Always center of the screen if
            the mouse is captured.
        dx, dy : float
            The movement of the mouse.

        """
        if self.exclusive:
            m = 0.15
            x, y = self.rotation
            x, y = x + dx * m, y + dy * m
            y = max(-90, min(90, y))
            self.rotation = (x, y)

    def on_key_press(self, symbol, modifiers):
        if symbol == key.W:
            self.strafe[0] -= 1
        if symbol == key.S:
            self.strafe[0] += 1
        if symbol == key.A:
            self.strafe[1] -= 1
        if symbol == key.D:
            self.strafe[1] += 1
        if symbol == key.SPACE:
            if self.dy == 0:
                self.dy = JUMP_SPEED
        if symbol == key.ESCAPE:
            self.set_exclusive_mouse(False)
        if symbol == key.TAB:
            self.flying = not self.flying
        if symbol in self.num_keys:
            index = (symbol - self.num_keys[0]) % len(self.inventory)
            self.block = self.inventory[index]

And finally, here's the setup:

    """ Configure the OpenGL fog properties.

    """
    # Enable fog. Fog "blends a fog color with each rasterized pixel fragment's
    # post-texturing color."
    glEnable(GL_FOG)
    # Set the fog color.
    glFogfv(GL_FOG_COLOR, (GLfloat * 4)(0.5, 0.69, 1.0, 1))
    # Say we have no preference between rendering speed and quality.
    glHint(GL_FOG_HINT, GL_DONT_CARE)
    # Specify the equation used to compute the blending factor.
    glFogi(GL_FOG_MODE, GL_LINEAR)
    # How close and far away fog starts and ends. The closer the start and end,
    # the denser the fog in the fog range.
    glFogf(GL_FOG_START, 50.0)
    glFogf(GL_FOG_END, 100.0)


def setup():
    """ Basic OpenGL configuration.

    """
    # Set the color of "clear", i.e. the sky, in rgba.
    glClearColor(0.5, 0.69, 1.0, 1)
    # Enable culling (not rendering) of back-facing facets -- facets that aren't
    # visible to you.
    glEnable(GL_CULL_FACE)
    # Set the texture minification/magnification function to GL_NEAREST (nearest
    # in Manhattan distance) to the specified texture coordinates. GL_NEAREST
    # "is generally faster than GL_LINEAR, but it can produce textured images
    # with sharper edges because the transition between texture elements is not
    # as smooth."
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    setup_fog()


def main():
    window = Window(width=800, height=600, caption='Minecraft', resizable=True)
    # Hide the mouse cursor and prevent the mouse from leaving the window.
    setup()

if __name__ == '__main__':
    main()
    pyglet.app.run()

Here's texture.png:

RENAME THIS TO texture.png

Here's an example of what's happening (the black circles means I've clicked the mouse, and at the end, I was rapidly pressing W): gif

What I've Done

Here is what I've done so far:

  • Looked at the Pyglet Docs
  • Researched on google with various phrasing and keywords

Solution

  • It sounds like it could be a Mac related issue. Here is a bug report on something similar happening with Mac: https://github.com/pyglet/pyglet/issues/225

    One thing I would try is just try a barebones setup and see if the problems persists with the minimal code. If this still occurs, there is most likely a bug in the Mac/Pyglet interaction. If the basic sample works, there might be a bug in the Minecraft example.

    import pyglet
    
    window = pyglet.window.Window()
    
    @window.event
    def on_draw():
        print('on_draw')
        window.clear()
    
    @window.event
    def on_mouse_motion(x, y, dx, dy):
        print('on_mouse_motion', x, y, dx, dy)
    
    @window.event
    def on_key_press(symbol, modifiers):
        print('on_key_press', symbol, modifiers)
    
    pyglet.app.run()