It's me again...
As my most recent questions have been discussing, I am making a main menu navigation for a game. Everything was going fine and dandy until a recent bug that has turned all text, either in buttons or simply drawn on the screen, black. I'm not sure why this is and I was hoping that someone smarter than me could figure it out. I really need this done so any help is greatly greatly appreciated.
# Libraries #
import pygame # Imports the Pygame library.
from pygame.locals import * # Imports the Pygame Locals library.
import ResolutionMenu # Imports the ResolutionMenu file (must be stored in the same location as this file to work)
# Classes #
class GuiSettings: # Class used to define the colour and size of buttons.
def __init__(self):
self.TextSize = 26
self.button_color = (35, 65, 145)
self.button_color_hover = (70, 105, 150)
class Button():
def __init__(self, x, y, width, height, outline, settings, text = "", action = None): # Classes used to actually create a button.
self.x = x # Screen is divided into an axis, x being horizontal.
self.y = y # And Y being vertical. The maximum bound changes depending on screen resolution.
self.width = width # This determines the dimensions of the box that is going to be created.
self.height = height # Height and Width are measured in pixels, similar to X and Y.
self.text = text # Allows text to be printed over the top of the button.
self.settings = settings # Allows it to be manipulated by the GuiSettings.
self.action = action # Will be used to determine what 'action' the user is performing on the button.
self.outline = outline # Will be used to determine if an outline is or is not drawn around the button.
def draw(self, screen, outline = None):
if self.outline: # Used to draw a black outline around the perimeter of the square.
pygame.draw.rect(screen, self.outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
if self.text != "":
font = pygame.font.SysFont('segoeuisemibold', self.settings.TextSize) # Settings for text inside of the button box.
text = font.render(self.text, 1, (0, 0, 0)) # Renders the font with the variables 'text' which is the text on the box, anti-aliasing is the second
# option, of which removes the jagged effect that might appear around the box.
# and finally the colour of the outline.
screen.blit(text, (self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2))) # Places the text in the middle of the box.
def update(self, events): # Simply used to determine what actions need to be taken when the button is pressed, if any.
for event in events:
if event.type == pygame.MOUSEBUTTONDOWN and self.isOver(pygame.mouse.get_pos()) and self.action:
self.action()
def isOver(self, pos): # Determines if the cursor is actually over the button box or not.
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
# Variables #
settings = GuiSettings() # Assigning class to variable to access parameters.
H, W = ResolutionMenu.resolution # Splits the tuple value of resolution into individual segments and assigns them their respective variables.
pygame.init() # Initialises the pygame library.
pygame.display.set_caption("Main Menu") # Creates a title for the program.
font = pygame.font.SysFont(None, settings.TextSize) # Sets the default font to that of the font on the System. Allows for cross platform usability.
screen = pygame.display.set_mode((ResolutionMenu.resolution)) # Sets the resolution to the resolution determined in ResolutionMenu.py.
# Functions #
def draw_text_center(text, font, colour, surface):
textobj = font.render(text, 1, colour) # Function for simple text creation. No button surrounding.
textrect = textobj.get_rect() # Creates a font taking the string 'text', sets antialiasing to true and sets the text colour.
textrect.center = ((H / 2), (W / 10)) # Creates a rectangle around 'textobj' which is simply the text.
surface.blit(textobj, textrect) # Finds the center of the screens
def draw_text(text, font, colour, surface, XandY): # Same function as above but with manipulatable positioning.
textobj = font.render(text, 1, colour)
textrect = textobj.get_rect()
textrect.topleft = (XandY)
surface.blit(textobj, textrect)
def MainMenu():
global font
while True:
pygame.display.set_mode((H, W)) # Sets the resolution to the resolution chosen before.
pygame.display.flip() # Updates the surface display.
buttons = [
Button(W / 10, H / 5, W / 3, H / 15, (0, 0, 0), settings, "Start Simulation", lambda: game()), # Buttons that will be displayed are placed inside of a list.
Button(W / 10, H / 2, W / 3, H / 15, (0, 0, 0), settings, "Options Menu", lambda: OptionsMenu()) # Lambda functions are one time use functions without names. This allows each
] # button to have a single use function that directs the program to the specified section.
running = True
while running:
events = pygame.event.get() # Gathers all events (user made inputs) that could occur.
for event in events:
if event.type == pygame.QUIT: # When the X is pressed in the corner of the application, it shall close.
pygame.quit()
return
if event.type == KEYDOWN: # When pressing Esc the function is broken and refreshes.
if event.key == K_ESCAPE:
running = False
for button in buttons:
button.update(events) # Enables the button to be interacted with.
screen.fill((100, 100, 100)) # Creates a grey background.
draw_text_center("Main Menu Navigation", font, (0, 0, 0), screen) # Creates Main Menu Navigation text in the top middle of the screen.
font = pygame.font.SysFont(None, settings.TextSize) # Makes the font size equal to that of settings.TextSize from GuiSettings class.
for button in buttons:
button.draw(screen) # Draws the buttons on the window.
pygame.display.flip() # Updates the surface display again.
pygame.display.update() # Updates a portion of the screen, allowing for text to render.
def game():
running = True
while running:
screen.fill((100, 100, 100))
draw_text_center("Simulation Navigation", font, (0, 0, 0), screen)
buttons = [
Button(W / 10, H / 5, W / 3, H / 15, (0, 0, 0), settings, "Enter Simulation", lambda: FileTraversal()),
Button(W / 10, H / 2, W / 3, H / 15, (0, 0, 0), settings, "Instructions", lambda: Instructions())
]
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
for button in buttons:
button.update(events)
for button in buttons:
button.draw(screen)
pygame.display.flip()
pygame.display.update()
def Instructions():
running = True
while running:
screen.fill((100, 100, 100))
draw_text("This is the simulation manual.", font, (0, 0, 0), screen, (50, 50))
draw_text("There are a few button combinations that will perform different tasks:", font, (0, 0, 0), screen, (50, 100))
draw_text("P", font, (200, 0, 0), screen, (50, 150))
draw_text(": Pauses the simulation.", font, (0, 0, 0), screen, (70, 150))
draw_text("D", font, (200, 0, 0), screen, (50, 200))
draw_text(": Toggles drawing mode. Allows for faster computation.", font, (0, 0, 0), screen, (70, 200))
draw_text("F", font, (200, 0, 0), screen, (50, 250))
draw_text(": Toggles the display of food. Increases FPS.", font, (0, 0, 0), screen, (70, 250))
draw_text("+ and -", font, (200, 0, 0), screen, (50, 300))
draw_text(": Increases or Decreases the speed of the simulation. FPS impact.", font, (0, 0, 0), screen, (130, 300))
draw_text("Middle Mouse Click", font, (200, 0, 0), screen, (50, 350))
draw_text(": Zoom in and out.", font, (0, 0, 0), screen, (250, 350))
draw_text("Right Mouse Click", font, (200, 0, 0), screen, (50, 400))
draw_text(": Pan around the environment", font, (0, 0, 0), screen, (240, 400)) # All of these are displayed on the screen as instructions for the user.
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
pygame.display.flip()
pygame.display.update()
def FileTraversal():
import os # Imports the Operating System library that allows for command line execution.
import platform # Imports the Platform library that allows for system analysis.
p = platform.system() # Assign the variable p the name of the operating system.
if p == "Linux":
os.chdir('/home/CHANGE/Desktop/School Work/New') # Directory traversal.
pygame.display.iconify() # Linux systems do not run EXE's. This file is an ELF file
os.system('./mygame') # and so must be run from terminal.
if p == "Windows":
os.chdir('C:/Users/CHANGE/Desktop/Project/') # Directory traversal.
# Uses EXE's as ELF's require Linux kernals.
if p == "OSX":
None
def OptionsMenu():
global settings # Functions use local variables. In order to manipulate the global value, global settings is specified.
pygame.display.set_mode((800, 600))
buttons = [
Button(100, 150, 250, 50, (0, 0, 0), settings, "Set Text Size: Small", lambda: settings.__setattr__('TextSize', 24)), # Button uses a lambda function to access
Button(450, 150, 250, 50, (0, 0, 0), settings, "Set Text Size: Regular", lambda: settings.__setattr__('TextSize', 26)), # the attributes of settings, which is the class
Button(100, 300, 250, 50, (0, 0, 0), settings, "Set Text Size: Large", lambda: settings.__setattr__('TextSize', 28)), # GuiSettings. It then sets the value of TextSize to
Button(450, 300, 250, 50, (0, 0, 0), settings, "Set Text Size: Larger", lambda: settings.__setattr__('TextSize', 30)) # the corresponding value, depending on the button pressed.
]
running = True
while running:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
pygame.display.set_mode((ResolutionMenu.resolution)) # Bug fix. Returns the resolution to the previously selected.
for button in buttons:
button.update(events)
screen.fill((100, 100, 100))
for button in buttons:
button.draw(screen)
pygame.display.flip()
MainMenu()
To whomever reads through all of this, good luck reading through all of that rubbish lol
**Added the code for ResolutionMenu below
# Libraries #
from tkinter import *
import time
# Variables List #
root = Tk() # Initialises Tkinter under the variable 'root'.
root.title("Resolution") # Applies a name to the window.
root.geometry("230x200") # Sets the window's geometry.
displays = 800, 600, 1024, 786, 1280, 1024, 1600, 1200, 1920, 1080 # Uses a string for resolutions.
running = True
v = IntVar() # Assignes the value of V to be an integer.
# Functions #
def CONFIRM(label1):
global resolution # Global variables used to access / manipulate outside of function.
global selection
selection = v.get() # Takes the value from the radio buttons.
if selection == 1:
resolution = (displays[0:2]) # Iterative Index naviation. 0 (0) indicates the first index up until the 2nd (2), but not including (, index.
time.sleep(0.2) # Pauses the program for .2 seconds for fluid transition appearance.
root.destroy() # Closes the TK window as it is no longer needed.
elif selection == 2:
resolution = (displays[2:4])
time.sleep(0.2)
root.destroy()
elif selection == 3:
resolution = (displays[4:6])
time.sleep(0.2)
root.destroy()
elif selection == 4:
resolution = (displays[6:8])
time.sleep(0.2)
root.destroy()
elif selection == 5:
resolution = (displays[8:10])
time.sleep(0.2)
root.destroy()
else:
resolution = (displays[0:2])
time.sleep(0.2)
root.destroy()
# Radio Button Creation #
v.set(1) # Validation. Sets the variable of V to 1 (radiobutton1) automatically.
Radio1 = Radiobutton(root, font = "Veranda 10", text = "800 x 600", variable = v, value = 1).pack(anchor = W)
Radio2 = Radiobutton(root, font = "Veranda 10", text = "1024 x 768", variable = v, value = 2).pack(anchor = W)
Radio3 = Radiobutton(root, font = "Veranda 10", text = "1280 x 1024", variable = v, value = 3).pack(anchor = W)
Radio4 = Radiobutton(root, font = "Veranda 10", text = "1600 x 1200", variable = v, value = 4).pack(anchor = W)
Radio5 = Radiobutton(root, font = "Veranda 10", text = "1920 x 1080", variable = v, value = 5).pack(anchor = W)
# Each of the above create a radio button. Using Tkinter's pre-made Radiobutton() function, you can simply link
# the radio button to the necessary window, in this case "root", the font size and the desired text message
# a variable that will be used to hold the value of the selection, followed by an integer that will be stored
# inside of said variable. Finally we pack it using .pack() function and give it an anchor, ie the direction it is aligned.
label1 = Label(root,text="") # Temp variable to hold information for lambda expression.
b = Button(root, font = "Veranda 10", text = "Confirm", command = lambda:CONFIRM(label1)) # Lambda creates temporary in-line function to pass selection to Confirm()
b.pack(anchor = SE)
label1.pack()
# Program Loop #
mainloop()
Okay! I solved the issue; it turns out some lines of code in my 'button' class had been deleted by accident. I had a backup of the code and after replacing the class with the working version, everything is back to normal.
The fixed class code will be at the bottom.
Thank you to #Nebulous29 for making the suggestion to change the value of the outline to (255, 0, 0). That's not actually the colour of the button but the outline surrounding the button. After I changed that I realised that something wasn't working with the outline function.
Fixed Code:
class Button():
def __init__(self, x, y, width, height, outline, settings, text = "", action = None):
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
self.settings = settings
self.action = action
self.outline = outline
def draw(self, screen, outline = None):
if self.outline:
pygame.draw.rect(screen, self.outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
color = self.settings.button_color if not self.isOver(pygame.mouse.get_pos()) else self.settings.button_color_hover
pygame.draw.rect(screen, color, (self.x, self.y, self.width, self.height), 0)
if self.text != "":
font = pygame.font.SysFont('segoeuisemibold', self.settings.TextSize)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def update(self, events):
for event in events:
if event.type == pygame.MOUSEBUTTONDOWN and self.isOver(pygame.mouse.get_pos()) and self.action:
self.action()
def isOver(self, pos):
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
The problem of the black background lies when you create your buttons:
Button(W / 10, H / 5, W / 3, H / 15, (0, 0, 0), settings, "Start Simulation", lambda: game()), # Buttons that will be displayed are placed inside of a list.
After the width and heights are set you have (0, 0, 0) which sets the button colour to black. Changing this to something like (255, 0, 0) would cause the background to be red and the text to be black (making the button at least readable).
I'm not sure why the button hover colour and button colour you've set in GuiSettings isn't updating the button though, but at least its a somewhat temporary fix
Related
I am trying to code Blackjack using pygame. I am having difficulties trying to implement the results of the game.
eg.:
def results(sum_player, sum_dealer)
if sum_player == 21:
print("Blackjack! Player has" + str(sum_player) + "while dealer has:" + str(sum_dealer))
run = False)
I want a button or some kind of output in pygame that shows who won the game. Then I need the game to end or reset so i can play again.
Can anyone help me on that?
I want a button or some kind of output in pygame that shows who won the game. Then I need the game to end or reset so i can play again.
In order to display text on the screen in pygame, we will need to do a couple of things:
First, we need to create a font for the text. In order to do that, we can use the pygame.font.SysFont function. You must add pygame.init() after import pygame to use this function. For information on how to use this function, check out the documentation. Example:
my_font = pygame.font.SysFont("Arial", 50)
Next, we will render the text and save it to a variable.Example:
label = my_font.render("my text", True, (0, 0, 0)) --> (0, 0, 0) is black
Lastly, we will need to display the text on the screen using the window.blit function.
Example: window.blit(label, (50, 100))
Pygame doesn't include a prebuilt function for creating buttons, so we'll have to create one ourselves. In order to do that, I created a Button class that can display buttons. By using a class, we can use multiple buttons in the game without repeating code.
The Button Class:
class Button:
def __init__(self, surface, text, bg, fg, x, y, width, height):
self.surface = surface # the window
self.bg = bg # the color of the button
self.x = x
self.y = y
self.width = width
self.height = height
self.rect = pygame.Rect(self.x, self.y, self.width, self.height) # creating a rectangle for the button
font = pygame.font.SysFont("Arial", height//2, "bold") # creating the font for the button text
self.label = font.render(text, True, fg) # rendering the text (fg = color of button text)
def draw(self):
pygame.draw.rect(self.surface, self.bg, self.rect) # displaying the button rectangle
self.surface.blit(self.label, (self.x + self.width*0.25,
self.y + self.height*0.25)) # displaying the button text
To create a new button, just do the following:
button = Button(window, "play", (255, 0, 0), (0, 0, 0), 100, 200, 300, 80)
The parameters that it takes:
surface --> the window of your game
text --> the button text. For example: "Play"
bg --> the background color of the button
fg --> the color of the button text
x --> the x position of the button
y --> the y position of the button
width --> the width of the button
height --> the height of the button
The class also includes a function called draw, which is responsible for displaying the button.
Combining Everything:
import pygame
pygame.init()
window = pygame.display.set_mode((500, 500))
pygame.display.set_caption("My Game")
# colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
class Button:
def __init__(self, surface, text, bg, fg, x, y, width, height):
self.surface = surface # the window
self.bg = bg # the color of the button
self.x = x
self.y = y
self.width = width
self.height = height
self.rect = pygame.Rect(self.x, self.y, self.width, self.height) # creating a rectangle for the button
font = pygame.font.SysFont("Arial", height//2, "bold") # creating the font for the button text
self.label = font.render(text, True, fg) # rendering the text (fg = color of button text)
def draw(self):
pygame.draw.rect(self.surface, self.bg, self.rect) # displaying the button rectangle
self.surface.blit(self.label, (self.x + self.width*0.25,
self.y + self.height*0.25)) # displaying the button text
def game():
# your game code
def results():
my_font = pygame.font.SysFont("Arial", 50, "bold") # creating the font for the text
label = my_font.render("Player 1 has won!", True, WHITE) # rendering the text in white
reset_button = Button(window, "RESET", BLUE, WHITE, 100, 200, 300, 80) # creating a blue "reset" button
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN: # checking if the mouse is down
if reset_button.rect.collidepoint(event.pos): # checking if the mouse clicked the reset button
game() # resetting the game
window.fill(BLACK) # sets the window color to black
window.blit(label, (50, 100)) # displaying the text on the screen
reset_button.draw() # showing the button on the screen
pygame.display.update() # updating the display
pygame.quit()
results()
I added comments to the code that explain certain lines.
I'm making a Sudoku Solver via pygame and I've been able to draw the whole board, however, while programming the section of code that deals with clicking on a tile, I made it so that the current tile would "light up" green so the user can know the current tile. However, I'm stuck figuring out how to draw over the green highlighted section when a user decides to click on a different tile. I'd like to remove that part entirely but, since it's a rect with a thickness of 4 and the base tiles have a thickness of 1, all I accomplish is having an ugly black line over the thick green line. I tried redrawing the whole board but I obtain similar results. Any help?
class Tile:
'''Represents each white tile/box on the grid'''
def __init__(self, value, window, x1, x2):
self.value = value #value of the num on this grid
self.window = window
self.active = False
self.rect = pygame.Rect(x1, x2, 60, 60) #dimensions for the rectangle
def draw(self, color, thickness):
'''Draws a tile on the board'''
pygame.draw.rect(self.window, color, self.rect, thickness)
pygame.display.flip()
def main():
board = Board(screen)
tiles = board.draw_board() #store the locations of all the tiles on the grid
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONUP:
mousePos = pygame.mouse.get_pos()
#board.draw_board() or (see next comment)
for i in range(9):
for j in range (9): #look for tile we clicked on
#tiles[i][j].draw((0,0,0),1)
#yield same results
if tiles[i][j].is_clicked(mousePos):
tiles[i][j].draw((50,205,50),4) #redraws that tile but with a highlighted color to show it's been clicked
break
main()
You could simply store the highlighted tile in a variable. I also don't think you need a Tile class at all, since a sudoku game state is basically just a list of list of numbers.
Here's a simple example. Note the comments:
import random
import pygame
TILE_SIZE = 64
# function to draw the grid
def draw_board():
board_surface = pygame.Surface((9*TILE_SIZE, 9*TILE_SIZE))
board_surface.fill((255, 255, 255))
for x in range(9):
for y in range(9):
rect = pygame.Rect(x*TILE_SIZE, y*TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(board_surface, (0, 0, 0), rect, 1)
pygame.draw.line(board_surface, (0, 0, 0), (0, 3*TILE_SIZE), (9*TILE_SIZE, 3*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (0, 6*TILE_SIZE), (9*TILE_SIZE, 6*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (3*TILE_SIZE, 0), (3*TILE_SIZE, 9*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (6*TILE_SIZE, 0), (6*TILE_SIZE, 9*TILE_SIZE), 5)
return board_surface
def main():
# standard pygame setup
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((9*TILE_SIZE, 9*TILE_SIZE))
font = pygame.font.SysFont(None, 40)
# seperate the game state from the UI
# create a dummy 9x9 sudoku board
state = [[None for _ in range(10)] for _ in range(10)]
for _ in range(15):
x = random.randint(0, 9)
y = random.randint(0, 9)
state[y][x] = random.randint(1, 9)
# a variable to hold the selected tile's position on the board
# (from 0,0 to 8,8)
selected = None
# create the grid surface ONCE and reuse it to clear the screen
board_surface = draw_board()
while True:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
return
# when the player clicks on a tile
# we translate the screen coordinates to the board coordinates
# e.g. a pos of (140, 12) is the tile at (2, 0)
if event.type == pygame.MOUSEBUTTONDOWN:
w_x, w_y = event.pos
selected = w_x // TILE_SIZE, w_y // TILE_SIZE
# clear everything by blitting the grid surface to the screen
screen.blit(board_surface, (0, 0))
# print all numbers in the state to the screen
# we use a Rect here so we can easily center the numbers
rect = pygame.Rect(0, 0, TILE_SIZE, TILE_SIZE)
for line in state:
for tile in line:
if tile != None:
tmp = font.render(str(tile), True, (0, 0, 0))
screen.blit(tmp, tmp.get_rect(center=rect.center))
rect.move_ip(TILE_SIZE, 0)
rect.x = 0
rect.move_ip(0, TILE_SIZE)
# if a tile is selected, we calculate the world coordinates from the board coordinates
# and draw a simple green rect
if selected:
rect = pygame.Rect(selected[0] * TILE_SIZE, selected[1] * TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(screen, (0, 200, 0), rect, 5)
clock.tick(30)
pygame.display.flip()
main()
Also, you shouldn't call pygame.display.flip() outside your main loop. It can lead to effects like flickering or images not showing up correctly.
I see several options. I'll start with the ones that will require the least amount of code changes.
Draw your selection rect only inside your tile. Problem solved.
when you draw a tile, draw the border around it as well.
You might also want to store the selected tile in a variable, so you can some stuff with it later on.
So I have this function in which it puts text on the screen:
def text_speech(font : str ,size : int,text : str,color,x,y, bold : bool):
SCREEN = width, height = 900, 600
font = pygame.font.Font(font,size)
font.set_bold(bold)
text = font.render(text, True, color)
textRect = text.get_rect()
textRect.center = (x,y)
screen.blit(text,textRect)
If I do this:
screen.fill((0,0,0))
text_speed('arialnarrow.ttf', 40, 'Hello', (255,255,255), (width/2), (height/2), False)
It generates the world 'Hello' on a black screen with white text. Is it possible that if the user hovers their mouse over this, it creates a red (255,0,0) outline?
To accomplish an outline you have to blit the multiple times. Render the text in the outline color (red):
outlineSurf = font.render(text, True, (255, 0, 0))
outlineSize = outlineSurf.get_size()
Create a surface which is grater than the text surface. The width and the height have to be increased by the doubled outline thickness:
textSurf = pygame.Surface((outlineSize[0] + outline*2, outlineSize[1] + 2*outline))
textRect = textSurf.get_rect()
Blit the outline surface 8 times on the text surface, shifted by the outline thickness (horizontal, vertical and diagonal:
offsets = [(ox, oy)
for ox in range(-outline, 2*outline, outline)
for oy in range(-outline, 2*outline, outline)
if ox != 0 or ox != 0]
for ox, oy in offsets:
px, py = textRect.center
textSurf.blit(outlineSurf, outlineSurf.get_rect(center = (px+ox, py+oy)))
Render the text with the text color and convert the surface to a per pixel alpha format (convert_alpha):
innerText = font.render(text, True, color).convert_alpha()
Blit the text in the middle of textSurf:
textSurf.blit(innerText, innerText.get_rect(center = textRect.center))
Blit textSurf onto the window:
textRect.center = (x,y)
screen.blit(textSurf, textRect)
See the example:
import pygame
import pygame.font
pygame.init()
width, height = 400, 300
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
textRect = pygame.Rect(0, 0, 0, 0)
def text_speech(font : str, size : int, text : str, color, x, y, bold : bool, outline: int):
global textRect
# font = pygame.font.Font(font,size)
font = pygame.font.SysFont(None, size)
font.set_bold(True)
if outline > 0:
outlineSurf = font.render(text, True, (255, 0, 0))
outlineSize = outlineSurf.get_size()
textSurf = pygame.Surface((outlineSize[0] + outline*2, outlineSize[1] + 2*outline))
textRect = textSurf.get_rect()
offsets = [(ox, oy)
for ox in range(-outline, 2*outline, outline)
for oy in range(-outline, 2*outline, outline)
if ox != 0 or ox != 0]
for ox, oy in offsets:
px, py = textRect.center
textSurf.blit(outlineSurf, outlineSurf.get_rect(center = (px+ox, py+oy)))
innerText = font.render(text, True, color).convert_alpha()
textSurf.blit(innerText, innerText.get_rect(center = textRect.center))
else:
textSurf = font.render(text, True, color)
textRect = textSurf.get_rect()
textRect.center = (x,y)
screen.blit(textSurf, textRect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
hover = textRect.collidepoint(pygame.mouse.get_pos())
outlineSize = 3 if hover else 0
screen.fill((0,0,0))
text_speech('arialnarrow.ttf', 40, 'Hello', (255,255,255), (width/2), (height/2), False, outlineSize)
pygame.display.flip()
Assuming that by "outline" you mean a stroke around it, I've got and easy solution. Simply render the same text, centered around the same position as the text you've already written, a bit bigger and in red. Then, just check when the mouse is hovering over the rect of your initial text, and if so, blit the outline.
In order to do this, we need to extract the rect of your first text. I changed your function so that it outputs the rendered surface, and rect.
I also made a few other adjustments :
You don't need to generate the font and render the text each time, this wastes CPU cycles. I recommend setting each of your fonts as global constants, for each size/typeface
You define a screen within your function, but never use it. I changed the function so that it no longer does the job of rendering.
When you call text_speech (I assume your second usage is a typo), width and height don't refer to anything. I also defined them as global constants, which I set to be your display size.
You haven't included any display code, so I wrote the bare minimum for a running concept.
import pygame
pygame.init()
# Font constants
ARIALNARROW_40 = font = pygame.font.Font('arialnarrow.ttf', 40)
ARIALNARROW_42 = font = pygame.font.Font('arialnarrow.ttf', 42)
# Screen size
WIDTH = 900
HEIGHT = 600
def text_speech(font, text, color, x, y, bold):
font.set_bold(bold)
rendered_text = font.render(text, True, color)
# Directly center the rect upon its creation
text_rect = rendered_text.get_rect(center=(x,y))
return text_rect, rendered_text
screen = pygame.display.set_mode((WIDTH, HEIGHT))
inner_rect, inner_text = text_speech(
ARIALNARROW_40, 'Hello', (255, 255, 255),
(WIDTH / 2), (HEIGHT / 2), False
)
# For your outline
outline_rect, outline_text = text_speech(
ARIALNARROW_42, 'Hello', (255, 0, 0),
(WIDTH / 2), (HEIGHT / 2), False
)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
# Paint our screen
screen.fill((0,0,0))
if inner_rect.collidepoint(pygame.mouse.get_pos()):
# Touching our text! Render outline
screen.blit(outline_text, outline_rect)
screen.blit(inner_text, inner_rect)
# Enact our display changes
pygame.display.update()
Note This does add the potentially unwanted affected of having a "side zoom". However getting around this would mean that'd you either have to mess around with font kerning (with the pygame.freetype module) but that could get very messy very fast, or you could prerender a stroke image ahead of time (and blit it using the same logic I used) but that would require you to rerender every time you changed the text, for all your text surfaces.
I'm currently working on a school project using Python and ideally, the project is to make any program using abstraction or algorithms in place. However, I'm stumped with this problem I've been having for days. I'm using Python with Pygame implementation to create a Vision Quiz, and I'm using buttons as multiple choice answers. Whenever I click on the correct button, it always says it's incorrect, and I have not been able to get past this part for a while. Please help me.
Pastebin link if needed: https://pastebin.com/BFPr6x6a
Code formatting here:
#Christopher Ticona
#Vision and Hearing examination
import pygame #All implementation of pygame itself
pygame.init()
#Colors
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
yellow = (255, 255, 0)
#Images
imageOne = pygame.image.load("imageOne.gif")
imageOne = pygame.transform.scale(imageOne,(200,200))
imageTwo = pygame.image.load("imageTwo.png")
imageTwo = pygame.transform.scale(imageTwo, (400,400))
#Messages and questions
welcome = "Welcome to my program! Let's get started. Today, we'll be examining your eyesight/color and hearing to see if you need any glasses or support for your sense. Don't worry, this could help your health!"
summaryProgram = "There will be multiple images and sounds when you're answering the exam to test yourself on hearing, eyesight, and color blindness. Be honest and don't judge yourself if you get it wrong! Good luck!"
questionOne = "1) What do you see?: "
questionTwo = "2) What's 1 letter from the 20/20 vision section?"
#Screen to display graphics
screen = pygame.display.set_mode((1550, 720))
screen.fill(white)
pygame.display.set_caption("Vision and Hearing Examination")
class Button:
def __init__(self, rect, command):
self.rect = pygame.Rect(rect)
self.image = pygame.Surface(self.rect.size).convert()
self.image.fill((red))
self.function = command
def get_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
self.on_click(event)
def on_click(self, event):
if self.rect.collidepoint(event.pos):
self.function()
def draw(self, surf):
surf.blit(self.image, self.rect)
def button_was_pressed():
if (pygame.MOUSEBUTTONDOWN == answerOne):
printText("You're correct! 1 Point", 15, 350, 100, black)
else:
printText("You're incorrect! Keep going!", 15, 350, 100, black)
def button_was_pressed1():
if (pygame.MOUSEBUTTONDOWN == answerTwo):
printText("You're correct! 1 Point", 15, 350, 300, black)
else:
printText("You're incorrect! Keep going!", 15, 350, 300, black)
def printText(txtText, Textsize , Textx, Texty, Textcolor):
#User's font
myfont = pygame.font.SysFont('Comic Sans MS', Textsize)
#Input text
label = myfont.render(txtText, 1, Textcolor)
#Coordinates of text
screen.blit(label, (Textx, Texty))
#Show the full display
pygame.display.flip()
def textButton(text,x,y):
font = pygame.font.SysFont("Comic Sans MS", 30)
textsurface = font.render((text), True, (0,0,0))
button_rect = textsurface.get_rect(topright=(x,y))
screen.blit(textsurface, button_rect)
#Font is automatically Comic Sans MS (Change if needed)
#printText(Text, Size, X, Y, Color)
welcomingMessage = printText(welcome, 15, 5, 10, black)
explanation = printText(summaryProgram, 15, 5, 30, black)
questionOne = printText(questionOne, 15, 5, 100, black)
questionTwo = printText(questionTwo, 15, 5, 300, black)
#Images displayed to the quiz
screen.blit(imageOne,(700,100))
screen.blit(imageTwo,(600,300))
#Button(rect=(x, y, height, width), command=button_was_pressed)
#Question 1
btn = Button(rect=(25,120,105,30), command=button_was_pressed)
btn1 = Button(rect=(200,120,105,30), command=button_was_pressed)
btn2 = Button(rect=(25,180,105,30), command = button_was_pressed)
btn3 = Button(rect=(200,180,105,30), command = button_was_pressed)
#Question 2
btn4 = Button(rect=(25,320,105,30), command=button_was_pressed1)
btn5 = Button(rect=(200,320,105,30), command=button_was_pressed1)
btn6 = Button(rect=(25,380,105,30), command=button_was_pressed1)
btn7 = Button(rect=(200,380,105,30), command=button_was_pressed1)
answerOne = btn
answerTwo = btn6
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
exit()
quit()
btn.get_event(event)
btn1.get_event(event)
btn2.get_event(event)
btn3.get_event(event)
btn4.get_event(event)
btn5.get_event(event)
btn6.get_event(event)
btn7.get_event(event)
btn.draw(screen)
btn1.draw(screen)
btn2.draw(screen)
btn3.draw(screen)
btn4.draw(screen)
btn5.draw(screen)
btn6.draw(screen)
btn7.draw(screen)
#Text over button
#Question 1
textButton("8", 90, 110)
textButton("3", 250,110)
textButton("7", 90, 170)
textButton("18", 250, 170)
#Question 2
textButton("B", 90, 310)
textButton("A", 250, 310)
textButton("O", 90, 370)
textButton("X", 250, 370)
pygame.display.update()
As #UnholySheep points out, you are passing a function, button_was_pressed, into your Button objects. That function contains this line:
if (pygame.MOUSEBUTTONDOWN == answerOne):
Unfortunately, pygame.MOUSEBUTTONDOWN is a contant provided by the pygame library - it may or may not be equal to answerOne once, but it won't always be equal to whatever "right" indicator you need (unless you make your questions always have the same answer, "always pick answer 'C'" or something).
Let me make a few suggestions:
Move your check for mouse button 1 into a separate function, taking an event:
def was_mouse1_click(event):
"""Return true if event is a mouse button 1 down event"""
pass
Write a method on Button to determine if a click was inside the button's area.
Write a function to decide which button was clicked, or None.
Clean up your code so that your main loop is short and sweet:
buttons = [btn1, btn2, btn3, btn4]
while not done:
for event in pygame.event.get():
if was_quit(event):
done = true
break
elif was_mouse1_click(event):
btn = which_button_was_clicked(event, buttons)
if btn is None:
# click outside buttons? ignore
continue
elif btn == right_answer:
celebrate()
else:
wrong()
else:
# Not a quit, not a click, ignore it.
pass
# draw/update screen, etc.
# broke out of while? we're done
exit()
My goal is to make a module that will make a grid on a pygame canvas and allow you to highlight boxes by their x and y coords.
Here is a simple example usage.
from grid import Grid
g = Grid(100, 100, 10) # width and height in cells, cell width in pixels
g.highlightBox(2, 2, (0, 255, 0)) # cell x and y, rgb color tuple
g.clearGrid()
Here is the code I have so far. The problem is, I need to have an event loop to keep the window open and make the close button functional, but I also need to allow the other functions to draw to the screen.
import pygame
import sys
class Grid:
colors = {"blue":(0, 0, 255), "red":(255, 0, 0), "green":(0, 255, 0), "black":(0, 0, 0), "white":(255, 255, 255)}
def __init__(self, width, height, cellSize, borderWidth=1):
self.cellSize = cellSize
self.borderWidth = borderWidth
self.width = width * (cellSize + borderWidth)
self.height = height * (cellSize + borderWidth)
self.screen = pygame.display.set_mode((self.width, self.height))
running = True
while running:
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = False
def clearGrid(self):
pass
def highlightBox(self, x, y, color):
xx = x * (self.cellSize + self.borderWidth)
yy = y * (self.cellSize + self.borderWidth)
pygame.draw.rect(self.screen, color, (xx, yy, self.cellSize, self.cellSize), 0)
When I run the first sample, the code will be stuck in the loop, not allowing me to run the highlightBox function until the loop is done(the exit button is pressed).
For starters, I wouldn't put the game loop inside the initialization function; find some other place for it. To solve this, simply put the code you want to execute in the game loop, next to the code for handling events:
running = True
while running:
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = False
# Print your screen in here
# Also do any other stuff that you consider appropriate
I think what you need is to disconnect the Grid class from the display of it. You sould make it generate surfaces, that would be printed to the screen Surface by the main game-loop. Your init , highlight_cell, and clear_grid methods could return Surfaces for example, or make a get_surface method that would be called once every game-loop
This would give much more flexibility
I got a working version with the multiprocessing library and pipes. It seems kinda unpythonic but it will work for this project.
import pygame
import sys
from multiprocessing import Process, Pipe
class Grid:
colors = {"blue":(0, 0, 255), "red":(255, 0, 0), "green":(0, 255, 0), "black":(0, 0, 0), "white":(255, 255, 255)}
def __init__(self, width, height, cellSize, borderWidth=1):
self.cellSize = cellSize
self.borderWidth = borderWidth
self.width = width * (cellSize + borderWidth)
self.height = height * (cellSize + borderWidth)
#pygame.draw.rect(self.screen, todo[1], (todo[2], todo[3], todo[4], todo[5]), 0)
self.parent_conn, self.child_conn = Pipe()
self.p = Process(target=self.mainLoop, args=(self.child_conn, self.width, self.height,))
self.p.start()
def close():
self.p.join()
def clearGrid(self):
pass
def highlightBox(self, x, y, color):
xx = x * (self.cellSize + self.borderWidth)
yy = y * (self.cellSize + self.borderWidth)
self.parent_conn.send(["box", color, xx, yy, self.cellSize, self.cellSize])
def mainLoop(self, conn, width, height):
#make window
screen = pygame.display.set_mode((self.width, self.height))
running = True
while running:
# is there data to read
if conn.poll():
#read all data
todo = conn.recv()
print("Recived " + str(todo))
#do the drawing
if todo[0] == "box":
print("drawing box")
pygame.draw.rect(screen, todo[1], (todo[2], todo[3], todo[4], todo[5]), 0) #color, x, y, width, height
todo = ["none"]
#draw to screen
pygame.display.flip()
#get events
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = False