1

I was trying to make program to control my mouse with my PS4 controller, everything works fine, I get the data and I can move my mouse accordingly. However when I hold my joystick to any axis, the mouse doesn't move, it not only happens when I hold it, but whenever the joystick value doesn't change. Is there any way I can fix this?

import pygame
import os
import pyautogui

pygame.init()
pygame.joystick.init
j = pygame.joystick.Joystick(0)
j.init()
data = {}

while True:
    for event in pygame.event.get():
        if event.type == pygame.JOYAXISMOTION:
            data[event.axis] = round(event.value, 2)
        if event.type == pygame.JOYBUTTONUP:
            data["test"] = "letsgoooo"
        if event.type == pygame.JOYBUTTONDOWN:
            if "test" in data:
                del data["test"]
        if 0 in data and 1 in data:
            if data[0] > 0.08:
                pyautogui.move(5, 0, 0.1)
            if data[0] < -0.08:
                pyautogui.move(-5, 0, 0.1)
            if data[1] > 0.08:
                pyautogui.move(0, 10, 0.1)
            if data[1] < -0.08:
                pyautogui.move(0, -10, 0.1)
    if 0 in data and 1 in data and 2 in data and 3 in data and 4 in data and 5 in data:
        os.system('cls')
        print(f"Left:\nX = {data[0]}, Y = {data[1]}")
        print(f"Right:\nX = {data[2]}, Y = {data[3]}")
        print(f"Left trigger = {data[5]}, Right trigger = {data[4]}")
        print(f"Mouse: X = {pyautogui.position()[0]}, Y = {pyautogui.position()[1]}") 
        print(data)
    else:
        print("Move your joystick")
Kingsley
  • 14,398
  • 5
  • 31
  • 53
Haslol
  • 13
  • 2
  • is that the real code ? it looks like mixed from examples ? – user3732793 Jul 27 '20 at 11:23
  • i did steal some code but most of it i made – Haslol Jul 27 '20 at 11:29
  • ok fine, you are mixing things like data[event.axis] with data[0]. You should debug or print what the event says and only use one method to access data until you know what event.axis is – user3732793 Jul 27 '20 at 11:35
  • the condition if your code is if the joy btn is pressed up or down. so your mouse will only move on the press not on the hold if you want to take into consideration the hold you need to put the 2 conditions on the `JOYBUTTON` under the `JOYAXISMOTION` so that only if the axis was moved change the direction of the movment. And if you didn't move the axis then the previous movment will remain. that makes it a `while` loop. ( while joyaxismotion didn't change move according to the direction of the motion ) . and if you do that I guess you'll need to add a case to stop the movment of the mouse. – Anass ABEA Jul 27 '20 at 11:40
  • I tested your code using a standard game joystick. The mouse continued to move when the joystick was pressed to an axis.The PS4 controller may be sending different events than you expect. – Mike67 Jul 28 '20 at 00:17
  • I get the same thing. If the PS4 controller joystick is fully left/right/up/down it sends a `0.00` position event instead of `1.00`. It's pretty broken in pygame, no movement is 0.00, full movement is 0.00. Not sure what you can do. Any movement up-to "full lock" seems OK. Maybe it's a pygame bug. (using 1.9.6 / linux) – Kingsley Jul 28 '20 at 00:38
  • I've been thinking about this overnight. I am wondering if the PS4 joystick sends an axis event with a `1.0` before going silent. If this is happening, it may be enough to register this "full-on" flag, and keep moving full until another event cancels it out. I wrote some test code yesterday, but it will be a while until I can re-test myself. – Kingsley Jul 28 '20 at 22:47

1 Answers1

2

The joystick sends update events. I guess a bit like the mouse, if it doesn't emit an event, you must assume the mouse hasn't changed position. Joystick events are the same, if the joystick says it's at 0.753 and there hasn't been an event since, well it's still at that angle.

So your movement code needs to remember the most-recent axis value, and use this as the current position of the stick. When a new event comes then you update the value, but not otherwise. That way then the stick is positioned to "full lock" (e.g.: 100% left) you just keep re-positioning whatever the joystick is moving the maximum amount each frame.

The left directional-keypad on the PS4 controller is a PyGame "hat", so not does not generate events, you have to query it independently. For whatever reason up and down seem to generate reverse values to what a normal person would expect (at the time of writing anyway).

I made some example code that moves 3 cross-hairs(sp?) around the window. One for each stick, and one for the hat. It will also show which buttons are pressed. The code could probably do with a re-organisation. I don't like the way the hat has to be handled separately to stick-events, etc. But it's a reasonably good demonstration how it all fits together.

PS4 Controller Demo

Oh, and the left & right triggers are also single-axis joysticks, but I didn't really do much with them in the code, other than pass on the events.

import pygame

# Window size
WINDOW_WIDTH    = 600
WINDOW_HEIGHT   = 600
WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF

DARK_BLUE = (   3,   5,  54 )
YELLOWISH = ( 255, 245, 145 )

class PS4Joystick:
    """ Class that knows about PS4 Controller Buttons, etc. """
    BUTTON_CROSS   = 0
    BUTTON_CIRCLE  = 1
    BUTTON_TRIANGLE= 2
    BUTTON_SQUARE  = 3
    BUTTON_L1      = 4
    BUTTON_R1      = 5
    BUTTON_L2      = 6
    BUTTON_R2      = 7
    BUTTON_SHARE   = 8
    BUTTON_OPTIONS = 9
    BUTTON_PS      = 10

    AXIS_LEFT_RIGHT = 1
    AXIS_UP_DOWN    = 2
    AXIS_LTRIGGER   = 3
    AXIS_RTRIGGER   = 4

    STICK_LEFT     = 1
    STICK_RIGHT    = 2
    STICK_LTRIGGER = 3
    STICK_RTRIGGER = 4

    #PS4ButtNames = [ '⨯', '○', '△', '□', 'L1', 'R1', 'L2', 'R2', 'Share', 'Options', 'PS' ]
    PS4ButtNames = [ 'eX', 'Oh', 'Pointy', 'Square', 'L1', 'R1', 'L2', 'R2', 'Share', 'Options', 'PS' ]
    PS4AxisNames = [ 'Left E/W', 'Left N/S', 'Left Trig', 'Right E/W', 'Right N/S', 'Right Trig' ]
    PS4AxisDir   = [ AXIS_LEFT_RIGHT, AXIS_UP_DOWN, AXIS_LTRIGGER, AXIS_LEFT_RIGHT, AXIS_UP_DOWN, AXIS_RTRIGGER ]
    
    @staticmethod
    def buttonName( butt_index ):
        """ Convert the button index to human-readable name """
        if ( butt_index >= 0 and butt_index < len( PS4Joystick.PS4ButtNames ) ):
            return PS4Joystick.PS4ButtNames[ butt_index ]
        else:
            return None  # error

    @staticmethod
    def axisName( axis_index ):
        """ Convert the axis index to human-readable name """
        if ( axis_index >= 0 and axis_index < len( PS4Joystick.PS4AxisNames ) ):
            return PS4Joystick.PS4AxisNames[ axis_index ]
        else:
            return None  # error
    
    @staticmethod
    def axisDirection( axis_index ):
        """ Convert the axis index to x/y indicator """
        if ( axis_index >= 0 and axis_index < len( PS4Joystick.PS4AxisDir ) ):
            return PS4Joystick.PS4AxisDir[ axis_index ]
        else:
            return None  # error

    @staticmethod
    def getStick( axis_index ):
        """ Given an axis, work out if it's from the left or right stick """
        if ( axis_index == 0 or axis_index == 1):
            return PS4Joystick.STICK_LEFT 
        elif ( axis_index == 3 or axis_index == 4 ):
            return PS4Joystick.STICK_RIGHT
        elif ( axis_index == 2 ):
            return PS4Joystick.STICK_LTRIGGER      
        elif ( axis_index == 5 ):
            return PS4Joystick.STICK_RTRIGGER      
        else:
            return None  # error


class CursorSprite( pygame.sprite.Sprite ):
    SIZE   = 48
    SPEED  = 5

    def __init__( self, colour=( 255, 245, 145 ) ):
        pygame.sprite.Sprite.__init__( self )
        self.image = pygame.Surface( ( self.SIZE, self.SIZE ), pygame.SRCALPHA )
        self.rect  = self.image.get_rect( center = ( WINDOW_WIDTH//2, WINDOW_HEIGHT//2 ) )
        # Make a centred '+'
        pygame.draw.line( self.image, colour, ( self.SIZE//2, 0 ), ( self.SIZE//2, self.SIZE ), 3 )
        pygame.draw.line( self.image, colour, ( 0, self.SIZE//2 ), ( self.SIZE, self.SIZE//2 ), 3 )

    def move( self, joy_x, joy_y ):
        # Joystick events are 
        self.rect.x += round( joy_x * self.SPEED )
        self.rect.y += round( joy_y * self.SPEED )


### initialisation
pygame.init()
pygame.font.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption( "PS4 Joystick Demo" )


### Joystick(s) initialisation
joystick_count = pygame.joystick.get_count()
print( "Initialising %d Joystick(s)" % ( joystick_count ) )
for i in range( joystick_count ):
    joystick = pygame.joystick.Joystick(i)
    joystick.init()
    print( "Joystick %d:" % ( i ) )
    print( "    name ........... [%s]" % ( joystick.get_name() ) )
    print( "    axis count ..... [%d]" % ( joystick.get_numaxes() ) )
    print( "    button count ... [%d]" % ( joystick.get_numbuttons() ) )
    print( "    hat count ...... [%d]" % ( joystick.get_numhats() ) )

# Just deal with Joystick 0 for now
joystick = pygame.joystick.Joystick( 0 )
left_stick_val_horiz = 0
left_stick_val_vert  = 0
right_stick_val_horiz = 0
right_stick_val_vert = 0
hat_val_horiz = 0
hat_val_vert = 0

# cursor to show movement
sprite_group = pygame.sprite.Group()
cursor_sprite_left  = CursorSprite( ( 255, 50, 50 ) )
cursor_sprite_right = CursorSprite( ( 50, 255, 50 ) )
cursor_sprite_hat   = CursorSprite( ( 50, 55, 255 ) )
sprite_group.add( cursor_sprite_left )
sprite_group.add( cursor_sprite_right )
sprite_group.add( cursor_sprite_hat )
button_text = ''

### Main Loop
clock = pygame.time.Clock()
font = pygame.font.Font( 'Arial_Bold.ttf', 24 )
done = False
while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True

        # Button pushed
        elif ( event.type == pygame.JOYBUTTONDOWN ):
            button_name = PS4Joystick.buttonName( event.button )
            button_text += ' ' + button_name
            print( "Button %s pressed" % ( button_name ) )

        # Button released
        elif ( event.type == pygame.JOYBUTTONUP ):
            button_name = PS4Joystick.buttonName( event.button )
            button_text = button_text.replace( ' ' + button_name, '' )
            print( "Button %s released" % ( button_name ) )

        # Position update form PS4-stick(s)
        elif ( event.type == pygame.JOYAXISMOTION ):
            stick = PS4Joystick.getStick( event.axis )
            axis  = PS4Joystick.axisDirection( event.axis )
            #name  = PS4Joystick.axisName( event.axis )

            if ( stick == PS4Joystick.STICK_LEFT ):
                if ( axis == PS4Joystick.AXIS_LEFT_RIGHT ):
                    left_stick_val_horiz = event.value   
                else:
                    left_stick_val_vert = event.value   

            elif ( stick == PS4Joystick.STICK_RIGHT ):
                if ( axis == PS4Joystick.AXIS_LEFT_RIGHT ):
                    right_stick_val_horiz = event.value   
                else:
                    right_stick_val_vert = event.value   

            # The left and right triggers are also relative-positioned sticks
            elif ( stick == PS4Joystick.STICK_LTRIGGER ):
                pass
            elif ( stick == PS4Joystick.STICK_RTRIGGER ):
                pass

            #if ( event.value > 0.01 or event.value < -0.01 ):
            #print( "AXIS: %s=%6.8f" % ( name, event.value ) )

    # The Joystick "Hat" is not handled via events
    if ( joystick.get_numhats() > 0 ):
        hat_vals = joystick.get_hat( 0 )
        hat_val_horiz = hat_vals[0]
        hat_val_vert  = -hat_vals[1]  # up/down reversed for some reason

    # Update the on-screen tracker cursors
    cursor_sprite_left.move( left_stick_val_horiz, left_stick_val_vert )
    cursor_sprite_right.move( right_stick_val_horiz, right_stick_val_vert )
    cursor_sprite_hat.move( hat_val_horiz, hat_val_vert )
    
    # Update the window, but not more than 60fps
    window.fill( DARK_BLUE )
    if ( len( button_text ) > 0 ):
        button_display = font.render( button_text+' ',  True, DARK_BLUE, YELLOWISH )
        window.blit( button_display, ( 50, WINDOW_HEIGHT-50-button_display.get_height() ) )
    sprite_group.update()
    sprite_group.draw( window )
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)


pygame.quit()
Kingsley
  • 14,398
  • 5
  • 31
  • 53