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")
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.
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()