0

I want to use raw user input from terminal to move a sprite using pygame, instead of using the key event listeners. Ultimately i want to be able to control the sprite via terminal issuing commands like "move up", "move left" and so on. Im trying to make an interactive terminal application that creates a pygame window as you type commands in the prompt, so you can see how everything is working. Is that possible? This is what I have attempted:

#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement

# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

import pygame
import sys
import os
import time

'''
Objects
'''

class Player(pygame.sprite.Sprite):
    '''
    Spawn a player
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.image = pygame.image.load(os.path.join('Images','character_sized.png'))
        self.rect  = self.image.get_rect()



    def control(self,x,y):
        '''
        control player movement
        '''
        self.movex += x
        self.movey += y

    def update(self):
        '''
        Update sprite position
        '''

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey



'''
Setup
'''
worldx = 960
worldy = 720

fps = 40        # frame rate
ani = 4        # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)

world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load('Images/grass-pattern.png')
backdropbox = world.get_rect()
player = Player()   # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10      # how fast to move
userInput = " "

'''
Main loop
'''
while main == True:
    userInput = raw_input(">>>")
    for event in pygame.event.get():

        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        # Cuando aprietas la tecla
        if userInput == 'l':
            player.control(-steps,0)
            time.sleep(2)    # Esto es lo que hace que se mueva el character a la izquierda.
        if userInput == 'r':
            player.control(steps*2,0)   # Esto es lo que hace que se mueva el character a la derecha.
            time.sleep(2)
        if userInput == 'u':
            print('jump')
            time.sleep(2)

        # # Cuando levantas la tecla.
        # if event.key == pygame.K_LEFT or event.key == ord('a'):
        #     player.control(steps,0)

        # if event.key == pygame.K_RIGHT or event.key == ord('d'):
        #     player.control(-steps,0)
        # if event.key == ord('q'):
        #     pygame.quit()
        #     sys.exit()
        #     main = False

#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.rect.clamp_ip(backdropbox)
    player.update()
    player_list.draw(world) #refresh player position
    pygame.display.flip()
    clock.tick(fps)
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
  • I don't think you can do it with `raw_input()` because it blocks until the user presses the `Enter` key which will prevent the pygame part of the program from running until it returns. – martineau May 13 '18 at 01:48

1 Answers1

0

The curses module has a getch method which you could use for this purpose. Windows users have to install curses.

First enter stdscr.nodelay(1), so that the getch method doesn't block, then use it in the main while loop to get the pressed key and concatenate it with a string variable (called command here), and use stdscr.addstr to display it.

When the user presses enter, check if the entered command is equal to 'move left' or 'move right' and move the game object in the according direction.

import curses
import pygame as pg


def main(stdscr):
    stdscr.nodelay(1)  # Makes the `getch` method non-blocking.
    # Use `addstr` instead of `print`.
    stdscr.addstr('Press "Esc" to exit...\n')
    command = ''  # Add the entered characters to this string.

    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    rect = pg.Rect(300, 100, 30, 50)

    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                return

        inpt = stdscr.getch()  # Here we get the pressed key.
        if inpt == 27:  # Esc
            return
        elif inpt in (ord('\n'), ord('\r')):  # Enter pressed.
            # Check which command was entered and move the rect.
            if command == 'move right':
                rect.x += 20
            elif command == 'move left':
                rect.x -= 20
            command = ''  # Reset the command string.
            stdscr.addstr(2, 0, '{}\n'.format(command))  # Write in the second line.
        elif inpt == 8:  # Backspace
            command = command[:-1]
            stdscr.addstr(2, 0, '{}\n'.format(command))
        elif inpt not in (-1, 0):  # ValueErrors if inpt is -1 or 0.
            command += chr(inpt)  # Concatenate the strings.
            stdscr.addstr(2, 0, '{}\n'.format(command))

        screen.fill((30, 30, 30))
        pg.draw.rect(screen, (0, 120, 250), rect)
        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    # Use the curses.wrapper to start the program. It handles
    # exceptions and resets the terminal after the game ends.
    curses.wrapper(main)
    pg.quit()

I don't have much experience with curses, so no guarantees that everything works correctly.

The msvcrt module (Windows) also has a getch function and I think termios can be used for Linux and MacOS. This cross-platform solution seems to work, but I've never tested it: https://stackoverflow.com/a/510364/6220679

skrx
  • 19,980
  • 5
  • 34
  • 48