This question already has answers here:
Is it possible to update a pygame drawing attribute when a event occurs?
(1 answer)
Python: How to make drawn elements snap to grid in pygame
(1 answer)
Closed 5 months ago.
I have a function to get a position on a grid (the size of the grid is flexible) based on the mouse position
def get_coords_from_mouse_pos(self, mx, my):
return mx // self.item_width, my // self.item_height
self.item_width and self.item_height are the width and height of one tile on the grid. mx and my are the mouse x and y positions. This probably exists somewhere else but I can't find it.
I probably just have the math wrong, but I would appreciate it if someone could help.
Here's a minimal example that shows your logic working, the left and top grid borders are considered as part of the cell below.
import pygame
# Configuration
width, height = 320, 240
cell_width, cell_height = 32, 32
pygame.init()
sys_font = pygame.font.SysFont(None, 60)
text = sys_font.render(" ", True, "turquoise")
clock = pygame.time.Clock()
screen = pygame.display.set_mode((width, height))
# create transparent background grid
grid = pygame.Surface((width, height), flags=pygame.SRCALPHA)
for y in range(0, height, cell_height):
pygame.draw.aaline(grid, "green", (0, y), (width, y)) # horizontal lines
for x in range(0, width, cell_width):
pygame.draw.aaline(grid, "green", (x, 0), (x, height)) # vertical lines
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
mx, my = pygame.mouse.get_pos()
pygame.display.set_caption(f"Mouse: {mx}, {my}")
if mx > 0 and my > 0:
cellx = mx // cell_width
celly = my // cell_height
text = sys_font.render(f"Cell: {cellx}, {celly}", True, "turquoise")
# Graphics
screen.fill("grey25")
screen.blit(grid, (0, 0))
# Draw Text in the center
screen.blit(text, text.get_rect(center=screen.get_rect().center))
# Update Screen
pygame.display.update()
clock.tick(30)
pygame.quit()
This will look something like this:
Related
I am trying to create a game using pygame and I am attempting to add a background to it (I have used some code from a YouTube video but this is not working). I also to not understand what the code is on about. I mean the background and does move but it automatically adds a new version of the background in the middle of the screen when the older background has not gone off screen yet:
class Background:
def __init__(self, x, y, picture):
self.xpos = x
self.ypos = y
self.picture = picture
self.rect = self.picture.get_rect()
self.picture = pygame.transform.scale(self.picture, (1280, 720))
def paste(self, xpos, ypos):
screen.blit(self.picture, (xpos, ypos))
def draw(self):
screen.blit(self.picture, (self.xpos, self.ypos))
while True:
background=pygame.image.load("C:/images/mars.jpg").convert_alpha()
cliff = Background(0, 0, background)
rel_x = x % cliff.rect.width
cliff.paste(rel_x - cliff.rect.width, 0)
if rel_x < WIDTH:
cliff.paste(rel_x, 0)
x -= 1
This is what currently happens with my background
[![what my problem looks like][1]][1]
[![What I want the background to move like ][2]][2]
This is what I want my background to look like (please ignore the sign it was the only one I could find)
I have now discovered what the real problem is
If you want to have a continuously repeating background, then you've to draw the background twice:
You've to know the size of the screen. The size of the height background image should match the height of the screen. The width of the background can be different, but should be at least the with of the window (else the background has to be drawn more than 2 times).
bg_w, gb_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
The background can be imagined as a endless row of tiles.
If you want to draw the background at an certain position pos_x, then you have to calculate the position of the tile relative to the screen by the modulo (%) operator. The position of the 2nd tile is shifted by the width of the background (bg_w):
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
Finally the background has to be blit twice, to fill the entire screen:
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
You can test the process by the following example program. The background can be moved by <- respectively ->
import pygame
pygame.init()
size = (800,600)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
bg_w, bg_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
pos_x = 0
speed = 10
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
allKeys = pygame.key.get_pressed()
pos_x += speed if allKeys[pygame.K_LEFT] else -speed if allKeys[pygame.K_RIGHT] else 0
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
pygame.display.flip()
Also see How to make parallax scrolling work properly with a camera that stops at edges pygame
This SO answer should have what you need
This seems to provide maybe a smarter and more functional background class than what you're using. I'd say give a try.
I am trying to create a game using pygame and I am attempting to add a background to it (I have used some code from a YouTube video but this is not working). I also to not understand what the code is on about. I mean the background and does move but it automatically adds a new version of the background in the middle of the screen when the older background has not gone off screen yet:
class Background:
def __init__(self, x, y, picture):
self.xpos = x
self.ypos = y
self.picture = picture
self.rect = self.picture.get_rect()
self.picture = pygame.transform.scale(self.picture, (1280, 720))
def paste(self, xpos, ypos):
screen.blit(self.picture, (xpos, ypos))
def draw(self):
screen.blit(self.picture, (self.xpos, self.ypos))
while True:
background=pygame.image.load("C:/images/mars.jpg").convert_alpha()
cliff = Background(0, 0, background)
rel_x = x % cliff.rect.width
cliff.paste(rel_x - cliff.rect.width, 0)
if rel_x < WIDTH:
cliff.paste(rel_x, 0)
x -= 1
This is what currently happens with my background
[![what my problem looks like][1]][1]
[![What I want the background to move like ][2]][2]
This is what I want my background to look like (please ignore the sign it was the only one I could find)
I have now discovered what the real problem is
If you want to have a continuously repeating background, then you've to draw the background twice:
You've to know the size of the screen. The size of the height background image should match the height of the screen. The width of the background can be different, but should be at least the with of the window (else the background has to be drawn more than 2 times).
bg_w, gb_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
The background can be imagined as a endless row of tiles.
If you want to draw the background at an certain position pos_x, then you have to calculate the position of the tile relative to the screen by the modulo (%) operator. The position of the 2nd tile is shifted by the width of the background (bg_w):
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
Finally the background has to be blit twice, to fill the entire screen:
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
You can test the process by the following example program. The background can be moved by <- respectively ->
import pygame
pygame.init()
size = (800,600)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
bg_w, bg_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
pos_x = 0
speed = 10
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
allKeys = pygame.key.get_pressed()
pos_x += speed if allKeys[pygame.K_LEFT] else -speed if allKeys[pygame.K_RIGHT] else 0
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
pygame.display.flip()
Also see How to make parallax scrolling work properly with a camera that stops at edges pygame
This SO answer should have what you need
This seems to provide maybe a smarter and more functional background class than what you're using. I'd say give a try.
This question already has answers here:
How to Center Text in Pygame
(6 answers)
Closed 1 year ago.
im making a game in pygame and I can't figure out how I can center some text in a rectangle. I draw the rectangle by using pygame.draw_rect(win, color, (x, y, w, h)) and text with
text = font.render("text", 1, color).
i know I can use text_rect = text.get_Rect(), to get a rect object of the text. but I don't know how to use those to center the text. I was searching for a solution online and couldn't find it
You can get a rect from the surface generated by rendering your font. Then set the centre that rect to the centre of your rectangle. Then blit your font surface to the main surface.
Here's a small example that uses the mousewheel to enlarge and shrink the border rectangle to show that the font remains centred. Pressing a key randomises the text.
import random
import pygame
WIDTH, HEIGHT = 320, 240
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# use the first available font
font = pygame.font.SysFont(pygame.font.get_fonts()[0], 60)
pygame.display.set_caption("Centering Font Rect")
text = "Initial Text"
widget = font.render(text, True, pygame.Color("seagreen"))
border = pygame.Rect(10, 10, WIDTH - 40, HEIGHT - 40)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
# change the text
text = random.choice(["Short", "Long Looooong", ".", "+++", "ABC"])
widget = font.render(text, True, pygame.Color("purple"))
elif event.type == pygame.MOUSEWHEEL:
# enlarge or shrink the border rect
border.inflate_ip(event.y, event.y)
screen.fill(pygame.Color("turquoise"))
# draw border rect
pygame.draw.rect(screen, pygame.Color("red"), border, width=1)
# get the current font rect
font_rect = widget.get_rect()
# move rect to be centered on border rect
font_rect.center = border.center
screen.blit(widget, font_rect)
# update display
pygame.display.update()
pygame.quit()
This shows:
Then after some resizing:
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.
This question already has answers here:
pygame capture keyboard events when window not in focus
(2 answers)
Get Inputs without Focus in Python/Pygame?
(3 answers)
Closed 3 months ago.
I'm trying to implement a feature in Pygame where if the user moves the mouse outside the window the relative position (event.rel) can be returned. I'm trying to do this because I want to make a game where you can keep turning left or right from mouse input.
I know this is possible from the docs:
If the mouse cursor is hidden, and input is grabbed to the current display the mouse will enter a virtual input mode, where the relative movements of the mouse will never be stopped by the borders of the screen. See the functions pygame.mouse.set_visible() and pygame.event.set_grab() to get this configured.
For this reason, I've implemented these lines in my code
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
However this doesn't help. The actual behaviour is:
when the mouse is moving in the window, event.rel prints to the console (expected)
when the mouse is moving outside the window, event.rel doesn't print to the console (not expected)
Other strange behaviour:
Initially event.rel is set to (300, 300) which is the center of my 600x600 screen. Ideally initial event.rel should be (0,0)
Possible cause:
I'm running the code below in trinket.io or repl.it. Because this is a browser the demo window might cause problems. Maybe this is solved by downloading python locally but i don't want to do this as it takes up too much space (my laptop sucks) and also it would be good to have an online demo to easily show employers that are too lazy to paste code into their IDEs.
Similar issue:
This guy on reddit had a very similar issue but I think his solution is not related to my situation
This block of code was what I asked originally but it doesn't demonstrate the question as well as the last block. Please scroll down to see the last block of code instead :)
import pygame
pygame.init()
# screen
X = 600 # width
Y = 600 # height
halfX = X/2
halfY = Y/2
screen = pygame.display.set_mode((X, Y), pygame.FULLSCREEN)
clock = pygame.time.Clock()
# Colors
WHITE = (255, 255, 255)
RED = (255, 0, 0)
# From the docs:
# "If the mouse cursor is hidden, and input is grabbed to the
# current display the mouse will enter a virtual input mode, where
# the relative movements of the mouse will never be stopped by the
# borders of the screen."
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
def drawCross(point, size, color):
pygame.draw.line(screen, color, (point[0]-size,point[1]-size), (point[0]+size,point[1]+size), 2)
pygame.draw.line(screen, color, (point[0]+size,point[1]-size), (point[0]-size,point[1]+size), 2)
canvas_rel = (halfX,halfY)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
print('event.rel = ' + str(event.rel))
canvas_rel = [10 * event.rel[0] + halfX, 10 * event.rel[1] + halfY]
print(' canvas_rel = ' + str(canvas_rel))
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
print('escape')
running = False
break
screen.fill(WHITE)
# Red sight to represent pygame mouse event.rel
drawCross(canvas_rel, 10, RED)
pygame.display.flip()
clock.tick(20)
pygame.quit()
EDIT:
The above code doesn't fully demonstrate my problem. I've added a better sample below to make the the question easier to understand. This sample will draw a triangle in the centre of the screen. The triangle will rotate with relative horizontal motion of the mouse. Note the attempt to use pygame.mouse.set_pos
import pygame
from math import pi, cos, sin
pygame.init()
# screen
X = 600 # width
Y = 600 # height
halfX = X/2
halfY = Y/2
screen = pygame.display.set_mode((X, Y), pygame.FULLSCREEN)
clock = pygame.time.Clock()
# for getting relative mouse position when outside the screen
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
# sensitivity
senseTheta = 0.01
# player
stand = [halfX, halfY]
t = 0 # angle in radians from positive X axis to positive x axis anticlockwise about positive Z
# draws a white cross in the screen center for when shooting is implemented :)
def drawPlayer():
p = stand
d = 50
a1 = 0.25*pi-t
a2 = 0.75*pi-t
P = (p[0],p[1])
A1 = (p[0] + d*cos(a1), p[1] + d*sin(a1)) # P + (d*cos(a1), d*sin(a1))
A2 = (p[0] + d*cos(a2), p[1] + d*sin(a2)) # P + (d*cos(a2), d*sin(a2))
pygame.draw.line(screen, BLACK, P, A1, 2)
pygame.draw.line(screen, BLACK, A1, A2, 2)
pygame.draw.line(screen, BLACK, A2, P, 2)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
mouseMove = event.rel
print('mouseMove = ' + str(mouseMove))
t -= mouseMove[0] * senseTheta
# "Fix" the mouse pointer at the center
# pygame.mouse.set_pos((screen.get_width()//2, screen.get_height()//2))
# But I think set_pos triggers another MOUSEMOTION event as moving in positive x
# seems to move backward the same amount in x (i.e. the triangle doesn't
# rotate). See the console log.
# If you uncomment this line it works ok (the triangle rotates) but still
# glitches when the mouse leaves the window.
# uncommented or not I still cant get the mouse to work outside the window
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
break
screen.fill(WHITE)
drawPlayer()
pygame.display.flip()
clock.tick(40)
pygame.quit()