Pygame mouse position not precise enough to rotate the 3D scene

Question:

I need to rotate a 3D scene or a character using the mouse position: for example, if I move the mouse to the right, I want to turn to the right / the character should turn right.

I’ve seen lots of games place the mouse in the center of the screen, then get its position relatively to the previous frame, update the game accordingly and move the mouse back in the center.

I tried to replicate this but I ran into the issue that pygame does not rotate reliably: with higher framerates, I tend to get a slower rotation. This is not a problem when rotating a character in a 2D game, but this is concerning when a 3D scene rotates differently if there are more objects to draw.

I used this simple code to make sure the problem actually came from this:

import pygame
from pygame.locals import *

pygame.init()
WIDTH, HEIGHT = (640, 480)
screen = pygame.display.set_mode((WIDTH, HEIGHT))
font = pygame.font.SysFont('consolas', 20)
clock = pygame.time.Clock()

angle = 0
time_passed = 0

pygame.mouse.set_pos((WIDTH//2, HEIGHT//2))
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            exit()

    # I did not use the MOUSEMOTION event because it used to glitch a bit
    x, y = pygame.mouse.get_pos()
    angle += (WIDTH//2 - x) * 360 / WIDTH # 360° every WIDTH pixels
    pygame.mouse.set_pos((WIDTH//2, HEIGHT//2))

    screen.fill(0)
    text = font.render('Angle: %d°' %angle, True, (255, 255, 255))
    w, h = text.get_size()
    screen.blit(text, (WIDTH//2 - w//2, HEIGHT//2 - h//2))
    pygame.display.flip()

    time_passed = clock.tick(60)/1000

I think either the mouse or the OS tweak the travelled distance according to the mouse speed, so I did the following tests at similar and relatively constant speed:

Going say 5 centimeters with the framerate limited to 60 FPS did not result in the same final angle as with 120 FPS for example, even when moving the mouse at the same speed, with the same mouse with high DPI.


Do I need to implement a tick system for example, where I only update the position every few frames? I think the problem wouldn’t be solved with slower framerates then, and maybe the movement would not be as responsive as I want.

Or perhaps there is an alternative to pygame.mouse.get_pos() / event.pos, getting the raw, more precise position from the mouse?

I would appreciate any help.

Asked By: D_00

||

Answers:

You get the position of the mouse with pygame.mouse.get_pos() and you reset the position with pygame.mouse.set_pos(). What if there was an event between pygame.mouse.get_pos() and pygame.mouse.set_pos()? Also pygame.mouse.get_pos() only tells you the current position of the mouse curosr, but it does not tell you how the mouse got there.

I suggest to use the MOUSEMOTION event. The event queue gives you all movements of the mouse. The relative movement of the mouse pointer since the last movement can be queried with the rel attribute:

for event in pygame.event.get():
    if event.type == pygame.MOUSEMOTION:
        angle += event.rel[0] * 360 / screen.get_width()

Minimal example

import pygame

pygame.init()
screen = pygame.display.set_mode((640, 480))
font = pygame.font.SysFont('consolas', 20)
clock = pygame.time.Clock()

angle = 0
text = font.render(f'Angle: {angle}°', True, (255, 255, 255))
pygame.mouse.set_pos(screen.get_rect().center)

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            run = False
        if event.type == pygame.MOUSEMOTION:
            angle += event.rel[0] * 360 / screen.get_width()
            text = font.render(f'Angle: {angle}°', True, (255, 255, 255))
    
    pygame.mouse.set_pos(screen.get_rect().center)

    screen.fill(0)
    screen.blit(text, text.get_rect(center = screen.get_rect().center))
    pygame.display.flip()

pygame.quit()
exit()
Answered By: Rabbid76
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.