I'm making a game and I want the user to be able to click a button which opens the file manager and asks them to open a game save file. Is there a module / how could I put this in my game? If there's no good gui way how would I make a text input thing (without using the terminal)? Thanks!
Pygame is a low-level library, so it doesn't have the kind of built-in dialogs you're after. You can create such, but it will be quicker to use the tkinter module which is distributed with Python and provides an interface to the TK GUI libraries. I'd recommend reading the documentation, but here's a function that will pop up a file selection dialog and then return the path selected:
def prompt_file():
"""Create a Tk file dialog and cleanup when finished"""
top = tkinter.Tk()
top.withdraw() # hide window
file_name = tkinter.filedialog.askopenfilename(parent=top)
top.destroy()
return file_name
Here's a small example incorporating this function, pressing Spacebar will popup the dialog:
import tkinter
import tkinter.filedialog
import pygame
WIDTH = 640
HEIGHT = 480
FPS = 30
def prompt_file():
"""Create a Tk file dialog and cleanup when finished"""
top = tkinter.Tk()
top.withdraw() # hide window
file_name = tkinter.filedialog.askopenfilename(parent=top)
top.destroy()
return file_name
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
f = "<No File Selected>"
frames = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
f = prompt_file()
# draw surface - fill background
window.fill(pygame.color.Color("grey"))
## update title to show filename
pygame.display.set_caption(f"Frames: {frames:10}, File: {f}")
# show surface
pygame.display.update()
# limit frames
clock.tick(FPS)
frames += 1
pygame.quit()
Notes: This will pause your game loop, as indicated by the frame counter but as events are being handled by the window manager, this shouldn't be an issue.
I'm not sure why I needed the explicit import tkinter.filedialog, but I get an AttributeError if I don't.
As for string entry in pygame, you might want to do this natively, in which case you'd be handling KEYUP events for letter keys to build the string and perhaps finishing when the user presses Enter or your own drawn button. You could continue down the tk path, in which case you'll want to use something like tkinter.simpledialog.askstring(…)
It is possible to make a file dialogue (though not using the native file explorer) using the pygame_gui module.
Simply create an instance of UIFileDialog and grab the path when the user hits 'ok':
file_selection = UIFileDialog(rect=Rect(0, 0, 300, 300), manager=manager, initial_file_path='C:\\')
if event.ui_element == file_selection.ok_button:
file_path = file_selection.current_file_path
If you want to allow selecting a directory set allow_picking_directories to True, but note that it does not allow picking an initial_file_path.
file_selection = UIFileDialog(rect=Rect(0, 0, 300, 300), manager=manager, allow_picking_directories=True)
Here's the above code in a simple program that allows you to pick a file when the button is clicked:
#!/usr/bin/env python
import pygame
import pygame_gui
from pygame_gui.windows.ui_file_dialog import UIFileDialog
from pygame_gui.elements.ui_button import UIButton
from pygame.rect import Rect
pygame.init()
window_surface = pygame.display.set_mode((800, 600))
background = pygame.Surface((800, 600))
background.fill(pygame.Color('#000000'))
manager = pygame_gui.UIManager((800, 600))
clock = pygame.time.Clock()
file_selection_button = UIButton(relative_rect=Rect(350, 250, 100, 100),
manager=manager, text='Select File')
while 1:
time_delta = clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.USEREVENT:
if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == file_selection_button:
file_selection = UIFileDialog(rect=Rect(0, 0, 300, 300), manager=manager, allow_picking_directories=True)
if event.ui_element == file_selection.ok_button:
print(file_selection.current_file_path)
manager.process_events(event)
manager.update(time_delta)
window_surface.blit(background, (0, 0))
manager.draw_ui(window_surface)
pygame.display.update()
Related
I am trying to make a window open every time I run my code, but every time I run the code, it shows the window as unresponsive.
My code is as follows:
import os
import time
import sys
pygame.font.init()
WIDTH, HEIGHT = 500, 400
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("EscapeGame")
BG = pygame.transform.scale(pygame.image.load(os.path.join("GameMap1.png")), (WIDTH, HEIGHT))
def main():
fps = 60
clock = pygame.time.Clock()
def redraw_window():
WIN.blit(BG, (0,0))
pygame.display.update()
while True:
clock.tick(fps)
redraw_window()
for event in pygame.event.get():
if event.type == sys.exit:
sys.exit()
input()
main()
I've already tried a few thing with event.get and event.pump, I'm probably missing something very basic. Could someone tell me what else I could try?
Also, I'm new at pygame, so if anyone sees another mistake, please tell me about. I would greatly appreciate it.
The window is unresponsive because you don't handle the events by calling pygame.event.get().
While pygame.event.get() is in your code, it's never executed because
a) it's in a function you never call
b) your code blocks on the input() call
You're code should look like this:
import os
pygame.init()
WIDTH, HEIGHT = 500, 400
def main():
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("EscapeGame")
BG = pygame.transform.scale(pygame.image.load(os.path.join("GameMap1.png")).convert_alpha(), (WIDTH, HEIGHT))
fps = 60
clock = pygame.time.Clock()
while True:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
WIN.blit(BG, (0,0))
pygame.display.flip()
main()
I'm trying to implement some text into my game. I've followed a tutorial that has led me to this
pygame.font.init()
gamefont = pygame.font.SysFont('Bahnschrift', 16)
text = gamefont.render("Round: ", 1, (0,0,0))
win.blit(text, (390, 10))
However, when I run my code, it says that the name 'win' is not defined. I was wondering if anyone knew how to fix this?
win is the pygame.Surface object which is associated to the window.
Somewhere you have t o initialize pygame (pygame.init()) and to create the window Surface (pygame.display.set_mode):
Minimal applicaiton:
import pygame
# initializations
pygame.init()
win = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
# create font ad render text
pygame.font.init()
gamefont = pygame.font.SysFont('Bahnschrift', 16)
text = gamefont.render("Round: ", 1, (0,0,0))
# main application loop
run = True
while run:
clock.tick(60)
# event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# clear the display
win.fill((255, 255, 255))
# draw the text
win.blit(text, (390, 10))
# update the display
pygame.display.flip()
I am on Ubuntu and I want to use PyGame for a beginner's project. But when I run the program for the window, it only opens briefly then closes back out. How do I fix this?
import pygame
pygame.init()
window = pygame.display.set_mode((700,700))
pygame.display.set_caption("First Game")
Your PyGame program needs to at least service the event loop. The program is opening the window, but then doing nothing, so it just closes again.
Try something like this:
import pygame
WINDOW_WIDTH = 700
WINDOW_HEIGHT = 700
SKY_BLUE = (161, 255, 254)
### Open the PyGame Wdinow
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
pygame.display.set_caption("First Game")
### make a clock for later.
clock = pygame.time.Clock()
### Main Loop
done = False
while not done:
# Handle Window Events, etc.
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Handle Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
print("up")
elif ( keys[pygame.K_DOWN] ):
print("down")
elif ( keys[pygame.K_LEFT] ):
print("left")
elif ( keys[pygame.K_RIGHT] ):
print("right")
# Update the window
window.fill( SKY_BLUE )
# Flush all updates out to the window
pygame.display.flip()
# Clamp frame-rate to 60 FPS
clock.tick_busy_loop(60)
pygame.quit()
This opens a minimal window, handles the window-close event, paints the background blue. It will register key-presses for arrow keys, but other than that does very little.
I have a pygame project which has a main menu - in said menu I have it so that upon clicking the Options button it triggers a Tkinter UI to open. The UI has a OptionMenu widget affecting game speed. How do I retrieve the value of the OptionMenu outside the class I used to create the Tkinter window
Here is a simplified issue:
class GUI(object):
def __init__(self):
import Tk # etc...
# CODE FOR THE PROGRAM WINDOW
defaultSpeed = StringVar(root)
speedMenu = OptionMenu(root, defaultSpeed, 'Slow', 'Normal, 'Fast')
speedMenu.pack()
I know I need defaultSpeed.get() to get the value.
The Pygame menu has this:
click = pygame.mouse.get_pressed()
clicked = click[0] == 1
if "Play" clicked:
startGame(ticks)
if "Options" clicked:
options = GUI()
At this point ^^^ How do I somehow obtain the speed (defaultSpeed.get()) and have it as global variable so that I can use it in the startGame function to affect the number of ticks on clock?
Thinking about it, I'm essentially asking for how to have a global variable defined when initialising a Tkinter class?
I've tried creating a function in GUI() called getSpeed and calling options.getSpeed() but for some reason I still have a logic error that means it's never actually printed/displayed/returned.
I am aware you shouldn't return values when in __init__ - should I just move all my Tkinter app. to a function inside GUI() and leave the __init_ out? surely I could just refrain from initalising it? Would this be better off programmed procedurally over OOP (allowing me to simply call GUI() in the main menu)?
Any help solving is appreciated.
RECAP OF PROBLEM:
Main Menu has 2 options (1 runs game in Pygame, 1 runs Options window in Tkinter)
I need to get the option from the options menu and have it apply to a variable in the game.
EDIT: I was asked to provide a minimal code
I'm currently really busy but here is minimal pseudocode - all you need to do is create the dimensions for the game window and for the menu (they are both 940,500)
import pygame
import time
import math
import tkinter
from tkinter import *
pygame.init()
clock = pygame.tick.Clock()
ticks = 0
#Here is all the game code
#This function is the main game and includes the game loop
def StartGame(ticks):
running = True
myGame1 = teams()
myGame1.startTeam()
#Here is some functions providing the rules of the sport
def updateBall(ticks):
theBall.x += float(theBall.speed) * ticks / 1000
while running:
myGame1.displayPlayers()
updateBall(ticks)
#Calling alot of functions and the game running is here
ticks = clock.tick(30)
pygame.display.flip
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
quit
def game_intro(menu_image):
intro = True
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
quit
click = pygame.mouse.get_pressed()
clicked = click[0] == 1
if 62+288 > mouse[0] > 62 and 150+28 > mouse[1] > 150:
if clicked:
startGame(ticks)
pass
elif 62+288 > mouse[0] > 62 and 230+30 > mouse[1] > 230:
if clicked:
options = GUI()
class GUI(object):
def __init__(self):
root = self.root = tkinter.Tk
root.geometry('500x400')
root.configure(background = '#ffffff')
speedLabel = tkinter.Message(root, text = 'Game speed: ')
defaultSpeed = StringVar(root)
speedMenu = OptionMenu(root,defaultSpeed, 'Slow', 'Normal', 'Fast')
speedLabel.grid(row = 0, column = 0)
speedMenu.grid(row = 0, column = 1)
game_intro(menu_image)
Before you create defaultSpeed:
global defaultSpeed
Then before you access it:
global defaultSpeed
It basically means that you can access the variable from anywhere after using global.
I have built a game that logs and saves the users score, using pickle, to a text file. When their lives are used up, they enter their name and their name and score are saved to a text file. Currently, if the "High Scores" section is selected on the main menu the high scores are simply printed in the python shell (or CMD if they're using that). I would like to create a separate window for just displaying the high scores. The window would simply display the scores and would refresh every time it is opened.
Currently I have the code to load the pickled file and create a new window. If I enter static text it works fine but when I try to display the text file contents I get the following error:
Traceback (most recent call last):
File "C:\LearnArabic\Program\Test1.py", line 22, in
textsurface = myfont.render(high_scores, False, (0, 0, 0))
TypeError: text must be a unicode or bytes
Here is my code:
import pygame
from operator import itemgetter
import pickle
pygame.font.init()
high_scores = []
with open("C:\\LearnArabic\\HighScores\\HighScores.txt", 'rb') as f:
high_scores = pickle.load(f)
#Background color
background_color = (255,255,255)
(width, height) = (400, 500)
HighScoreScreen = pygame.display.set_mode((width, height))
pygame.display.set_caption('High Scores')
HighScoreScreen.fill(background_color)
#Displaying text on window
myfont = pygame.font.SysFont('Comic Sans MS', 30)
textsurface = myfont.render(high_scores, False, (0, 0, 0))
HighScoreScreen.blit(textsurface,(0,0))
pygame.display.flip()
running = True
while running:
for event in pygame.event.get():
if event.type ==pygame.QUIT:
running = False
Is there a different function from render that would allow me to display the results in a tabular form?
I'm relatively new to programming and am using python 3. Thanks for the help!
You could blit the highscores onto another surface and then blit this surf onto the screen. To blit the highscore list, use a for loop and enumerate the list, so that you can multiply the y-offset by i. To toggle the highscore surface, you can just add a variable highscores_visible = False and then do highscores_visible = not highscores_visible, and in the main loop check if highscores_visible: # blit the surf (press 'h' to update and toggle the highscore table in the example below). Of course you need to make sure that the names and highscores fit onto the surface.
import pygame
pygame.font.init()
screen = pygame.display.set_mode((400, 500))
clock = pygame.time.Clock()
high_scores = [
('Carrie', 350),
('Arthur', 200),
('Doug', 100),
]
background_color = (255, 255, 255)
highscore_surface = pygame.Surface((300, 400))
highscore_surface.fill((90, 100, 120))
myfont = pygame.font.SysFont('Comic Sans MS', 30)
highscores_visible = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_h:
highscores_visible = not highscores_visible
if highscores_visible:
highscore_surface.fill((90, 100, 120))
for i, (name, score) in enumerate(high_scores):
text = myfont.render('{} {}'.format(name, score), True, (0, 0, 0))
highscore_surface.blit(text, (50, 30*i+5))
screen.fill(background_color)
if highscores_visible:
screen.blit(highscore_surface, (50, 50))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Regarding the TypeError, you can't pass a list to myfont.render only a string or byte string, so you have to convert the list, e.g. str(high_scores). However, if you just convert the high_scores list into a string before you pass it, pygame will render the whole list as one line. You need to use a for loop if you want several lines of text.