Why does Pygame NEED an event handler to function? [duplicate] - python-3.x

The program works fine like this but, I don't understand why it needs the useless for event in pygame.event.get(): None in the gameOver while statement inside game_loop. If you could find a way to delete it or explain why it doesn't run without it, that would be great!
import pygame, time, random
pygame.init()
# SOUND/TEXTURES
icon = pygame.image.load("textures\snakeicon.png")
pygame.display.set_icon(icon)
# VARIABLES
white = (255, 255, 255)
black = (0, 0, 0)
red = (200, 0, 0)
green = (0, 155, 0)
bright_green = (0, 250, 0)
bright_red = (255, 0, 0)
font_size = 50
font = pygame.font.SysFont(None, font_size)
# FUNCTIONS
def text_objects(text, font):
textSurface = font.render(text, True, black)
return textSurface, textSurface.get_rect()
def button(msg, x, y, w, h, ic, ac, action=None):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if x + w > mouse[0] > x and y + h > mouse[1] > y:
pygame.draw.rect(gameWindow, ac, (x, y, w, h))
if click[0] == 1 and action != None:
if action == "play":
game_loop()
elif action == "quit":
gameRun = False
gameWindow.fill(white)
message_to_screen("Closing Game...", black, 280, 280)
pygame.display.update()
time.sleep(1)
pygame.quit()
quit()
else:
pygame.draw.rect(gameWindow, ic, (x, y, w, h))
smallText = pygame.font.Font("freesansbold.ttf", 20)
textSurf, textRect = text_objects(msg, smallText)
textRect.center = ((x + (w / 2)), (y + (h / 2)))
gameWindow.blit(textSurf, textRect)
def snake(rect_x, rect_y, block_size):
pygame.draw.rect(gameWindow, green, [rect_x, rect_y, block_size, block_size])
def message_to_screen(msg, color, x, y):
screen_text = font.render(msg, True, color)
gameWindow.blit(screen_text, [x, y])
# WINDOW/SURFACE
display_w = 800
display_h = 600
window_title = "Window"
gameWindow = pygame.display.set_mode((display_w, display_h))
pygame.display.set_caption(window_title)
# FPS/Clock
clock = pygame.time.Clock()
# Game Loop
def game_loop():
# RECT OPTIONS
moveSpeed = 10
block_size = 10
rect_x = display_w / 2
rect_y = display_h / 2
change_x = 0
change_y = 0
randApplex = round(random.randrange(0, display_w - block_size) / 10.0) * 10.0
randAppley = round(random.randrange(0, display_h - block_size) / 10.0) * 10.0
global gameRun, gameOver
gameRun = True
gameOver = False
while gameRun:
while gameOver:
gameRun = False
gameWindow.fill(white)
# button(msg, x, y, w, h, ic, ac, action=None)
message_to_screen("Game Over!", red, 300, 300)
button("Restart", 150, 450, 100, 50, green, bright_green, "play")
button("Quit", 550, 450, 100, 50, red, bright_red, "quit")
pygame.display.update()
# RIGHT HERE!
for event in pygame.event.get():
None
# RIGHT THERE!
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameRun = False
gameOver = False
gameWindow.fill(white)
message_to_screen("Closing Game...", black, 280, 280)
pygame.display.update()
time.sleep(1)
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
change_y = -moveSpeed
change_x = 0
elif event.key == pygame.K_s:
change_y = moveSpeed
change_x = 0
elif event.key == pygame.K_a:
change_x = -moveSpeed
change_y = 0
elif event.key == pygame.K_d:
change_x = moveSpeed
change_y = 0
# BOARDER CRASH
if rect_x >= display_w or rect_x < 0 or rect_y >= display_h or rect_y < 0:
gameOver = True
# LOGIC
rect_x += change_x
rect_y += change_y
if rect_x == randApplex and rect_y == randAppley:
randApplex = round(random.randrange(0, display_w - block_size) / 10.0) * 10.0
randAppley = round(random.randrange(0, display_h - block_size) / 10.0) * 10.0
# RENDER
gameWindow.fill(white)
pygame.draw.rect(gameWindow, red, [randApplex, randAppley, block_size, block_size])
snake(rect_x, rect_y, block_size)
pygame.display.update()
clock.tick(15)
message_to_screen("You Lose!", red, 325, 300)
pygame.display.update()
time.sleep(1)
message_to_screen("Closing Game!", black, 280, 350)
pygame.display.update()
time.sleep(1)
# QUIT
pygame.quit()
quit()
game_loop()

Basically, the OS expects pygame to handle events during your program. If the OS notice that events aren't handled, it'll alert the user. The program doesn't actually crash or freeze, the OS is just saying that your program has become unresponsive (which it has because you're not responding to any user events), but it still works.
When your game is entering small scenes you might think that you don't need to handle events, but there is one event you should always check for: the pygame.QUIT event (sent when the user press the close button at the top corner). In your example you're not allowing the user to quit during the game over sequence (you're providing the player a button to click but a user would also expect clicking the close button would close the game as well).
Another reason is that the event queue is constantly filling up. So if the user would spam multiple keys and press multiple areas with the mouse, nothing would happen until he/she enters the game again (where you have an event loop). Then every event would be executed. Therefore it's important to regularly empty the queue. The queue is emptied every time you call pygame.event.get() or pygame.event.clear().
The function pygame.event.pump() is the function that put all events into the event queue (it doesn't clear the previous events, it just adds). The event queue won't be filled/updated with any events if the function isn't called. However, the function is implicitly called inside the functions pygame.event.get(), pygame.event.clear(), pygame.event.poll(), pygame.event.wait() and pygame.event.peek(), so there is rarely a reason to call it explicitly. If you are sure you don't want to handle events at some time, you could use pygame.event.clear() so the event queue is empty when you start processing events again. If you don't want to handle events at all, then use pygame.event.pump().

You can replace it with pygame.event.pump(). The documentation explains why you need to call this or have to use an event loop each frame.
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system. If you are not using other event functions in your game, you should call pygame.event.pump() to allow pygame to handle internal actions.
This function is not necessary if your program is consistently processing events on the queue through the other pygame.eventpygame module for interacting with events and queues functions.
There are important things that must be dealt with internally in the event queue. The main window may need to be repainted or respond to the system. If you fail to make a call to the event queue for too long, the system may decide your program has locked up.

Every process with a GUI needs to maintain a Message Pump (at least in Windows it's critical)
Most of the time, your GUI framework (QT for example) will maintain the pump for you - and will dispatch matching events for your callbacks (mouse clicks, keyboard etc..).
I guess that pygame wants to give you some finer control about how you handle the messages (if I'm not mistaken, game engines would want to wait and pump all events with each rendering of a single frame).

Related

Pygame set_alpha not working with attempted background fading

I've been trying to create a short code to use for a project that can fade in and from black, but for some reason only the function that fades in is working while the fade out function is being skipped more or less. By giving them parameters I confirmed that the problem is in the second function and that the transparency isn't changing at all. Here's my code-
import pygame
screen = pygame.display.set_mode((800,600))
image = pygame.image.load_extended('Map.png').convert_alpha()
image = pygame.transform.scale(image,(530,300))
image.set_alpha(0)
x = 0
y = 255
def fade_in(x):
while True:
screen.blit(image,(0,0))
pygame.display.update()
image.set_alpha(x)
pygame.time.delay(100)
if x < 255:
x += 5
else:
pygame.time.delay(500)
x = 0
fade_out(y)
def fade_out(y):
while True:
screen.blit(image,(0,0))
pygame.display.update()
image.set_alpha(y)
pygame.time.delay(100)
if y > 0:
y -= 5
else:
pygame.time.delay(500)
y = 255
fade_in(x)
while True:
fade_in(x)
Does anyone have an idea of what the problem might be?
When you draw a transparent surface on the screen, the surface is blended with the current contents of the screen. Hence you need to clear the screen before drawing the fading background with screen.fill(0).
Do not try to control the application recursively or with loops within the application loop. Do not delay the game. delay causes the game to stop responding.
Use the application loop and use pygame.time.Clock to control the frames per second and thus the game speed.
The method tick() of a pygame.time.Clock object, delays the game in that way, that every iteration of the loop consumes the same period of time. See pygame.time.Clock.tick():
This method should be called once per frame.
That means that the loop:
clock = pygame.time.Clock()
run = True
while run:
clock.tick(60)
Example:
import pygame
pygame.init()
screen = pygame.display.set_mode((400,300))
clock = pygame.time.Clock()
try:
image = pygame.image.load_extended('Map.png').convert_alpha()
image = pygame.transform.scale(image,(530,300))
except:
image = pygame.Surface(screen.get_size())
pygame.draw.circle(image, (255, 0, 0), screen.get_rect().center, min(*screen.get_size()) // 2 - 20)
alpha = 0
alpha_change = 1
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
alpha += alpha_change
if not 0 <= alpha <= 255:
alpha_change *= -1
alpha = max(0, min(alpha, 255))
screen.fill(0)
alpha_image = image.copy()
alpha_image.set_alpha(alpha)
screen.blit(alpha_image, (0, 0))
pygame.display.flip()
pygame.quit()
exit()

Why is this simple pygame program lagging?

So, I've been studying programming for a short time and decided to make the snake game in pygame. However, while making the base of the program I realized that the rectangle (snake) controlled by the player is teleporting (maybe by the lag) every second while moving. Here is the code:
import pygame
pygame.init()
# Window
window = (1280, 720)
center = (window[0]//2, window[1]//2)
screen = pygame.display.set_mode(window)
pygame.display.set_caption("Snake")
# Colors
COLOR_LIGHT_GREY = (200, 200, 200)
COLOR_DARK_GREY = pygame.Color('gray12')
# Game loop
game_loop = True
game_clock = pygame.time.Clock()
# Create image
def img(name):
img_path = "./assets/natanael.lucena_" + name + ".png"
return pygame.image.load(img_path).convert_alpha()
# Set object coordinates
def set_obj_coordinates(obj, x, y):
obj.x = x
obj.y = y
# Check player key press
def check_player_key(b):
global snake_direction
if event.key == pygame.K_w or event.key == pygame.K_s or event.key == pygame.K_a or event.key == pygame.K_d:
snake_direction[event.key] = b
# Check key events in-game
def event_conditional():
global game_loop
if event.type == pygame.QUIT:
game_loop = False
elif event.type == pygame.KEYDOWN:
check_player_key(True)
elif event.type == pygame.KEYUP:
check_player_key(False)
# Check if the snake collided and the game is over
def game_over():
if snake.y < 0 or snake.y > 720 or snake.x < 0 or snake. x > 1280:
return True
# Snake
snake_img = img("snake")
snake = snake_img.get_rect()
move_keys = [pygame.K_w, pygame.K_d, pygame.K_s, pygame.K_a]
snake_direction = {k: False for k in move_keys}
snake_score = 0
snake_vel = 10
set_obj_coordinates(snake, center[0], center[1])
# Apple
apple_img = img("apple")
apple = apple_img.get_rect()
apple_eaten = False
set_obj_coordinates(apple, 40, 40)
# Main game loop
while game_loop:
for event in pygame.event.get():
event_conditional()
# score_text = text_render(snake_score)
if not game_over():
for i in range(4):
if i % 2:
coord_aux = "x "
else:
coord_aux = "y "
if i % 3:
op = "+= "
else:
op = "-= "
if snake_direction[move_keys[i]]:
exec("snake." + coord_aux + op + "snake_vel")
# the for loop above is equivalent to :
# if snake_direction[move_keys[0]]:
# snake.y -= snake_vel
# if snake_direction[move_keys[1]]:
# snake.x += snake_vel
# if snake_direction[move_keys[2]]:
# snake.y += snake_vel
# if snake_direction[move_keys[3]]:
# snake.x -= snake_vel
screen.fill(COLOR_DARK_GREY)
screen.blit(snake_img, snake)
screen.blit(apple_img, apple)
# Update screen
pygame.display.flip()
game_clock.tick(60)
pygame.quit()
If someone can tell me the reason for the problem, I really appreciate it.
Edit: looks like the problem is just happening with me
Thats a common problem with pygame, especially since pygame 2/sdl 2 where you can't use the directx video driver and enable vsync anymore.
You should do the following:
keep track of the sub-pixel coordinates of your moving game objects. A Rect can only store integer values in its x and y attributes, so you'll need another variable to store this. I usually use a Vector2 because it's easy to use and the performance hit usually does not matter anyway.
use an accurate clock. Pygame's clock object only uses milliseconds, which is not accurate enough for really smooth movement. If you're on windows, usually the best method for timing is the GetSystemTimePreciseAsFileTime function.
use a delta timing.
You could also use different threads for the parts of your game that needs different timing methods (e.g. your game logic expects fixed 30 or 60 FPS and your drawing code wants to run as fast as possible), but that's overkill for your small game.
So here's an example I hacked together that gives you smooth movement (at least that's what I use usually since it works fine for me. Note that it's windows specific):
import pygame
import ctypes.wintypes
pygame.init()
screen = pygame.display.set_mode((1280, 720))
center = screen.get_rect().center
pygame.display.set_caption("Snake")
game_loop = True
# https://stackoverflow.com/a/28574340/142637
def utcnow_microseconds():
system_time = ctypes.wintypes.FILETIME()
ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(system_time))
large = (system_time.dwHighDateTime << 32) + system_time.dwLowDateTime
return large // 10 - 11644473600000000
# Snake
snake_img = pygame.Surface((40, 40))
snake_img.fill('white')
snake = snake_img.get_rect()
snake_vel = 10
snake_pos = pygame.Vector2(center[0], center[1])
snake.topleft = snake_pos.x, snake_pos.y
# Apple
apple_img = pygame.Surface((40, 40))
apple_img.fill('red')
apple = apple_img.get_rect(topleft=(40, 40))
dt = 0
while game_loop:
t1 = utcnow_microseconds()
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_loop = False
keys = pygame.key.get_pressed()
snake_pos.x += (keys[pygame.K_d] - keys[pygame.K_a]) * snake_vel * dt
snake_pos.y += (keys[pygame.K_s] - keys[pygame.K_w]) * snake_vel * dt
snake.topleft = snake_pos.x, snake_pos.y
screen.fill('darkgrey')
screen.blit(snake_img, snake)
screen.blit(apple_img, apple)
pygame.display.flip()
t2 = utcnow_microseconds()
dt = (t2 - t1) / 1000. / 1000. * 30
pygame.quit()
Further reading.
Very likely the bottleneck is the line
exec("snake." + coord_aux + op + "snake_vel")
exec has to parse and interpret the text in the argument.
This code can be easily improved
if not game_over():
for i in range(4):
if snake_direction[move_keys[i]]:
sign = 1 if i % 3 else -1
if i % 2:
snake.x += sign * snake_vel
else:
snake.y += sign * snake_vel
Since snake is a pygame.Rect object, you can even do the following:
if not game_over():
for i in range(4):
if snake_direction[move_keys[i]]:
sign = 1 if i % 3 else -1
snake[(i+1) % 2] += sign * snake_vel
However:
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action or a step-by-step movement.
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
Use pygame.key.get_pressed() for a smooth continuous movement:
# Main game loop
while game_loop:
for event in pygame.event.get():
event_conditional()
# score_text = text_render(snake_score)
if not game_over():
keys = pygame.key.get_pressed()
snake.x += (keys[pygame.K_d] - keys[pygame.K_a]) * snake_vel
snake.y += (keys[pygame.K_s] - keys[pygame.K_w]) * snake_vel
# [...]

moving a square in a grid

making a square move in a grid
import pygame
from pygame.locals import *
pygame.init()
clock = pygame.time.Clock()
w = 1008
h = 640
left = 16
top = 16
width = 16
height = 16
YELLOW = (255, 255, 55)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
bg = (255, 255, 255)
x = 0
y = 0
screen = pygame.display.set_mode((w, h))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == KEYDOWN:
if event.key == K_DOWN:
top += 16
if event.key == K_UP:
top -= 16
if event.key == K_RIGHT:
left += 16
if event.key == K_LEFT:
left -= 16
x += 16
y += 16
screen.fill(BLACK)
pygame.draw.rect(screen, [255, 255, 55], [left, top, width, height], 0)
pygame.draw.line(screen, bg, (w - 16, y), (16, y), 1)
pygame.draw.line(screen, bg, (x, h - 16), (y, 16), 1)
pygame.display.flip()
pygame.quit()
at the moment if I # out screen.fill() grid appears on the screen and the rect moves with a tail behind, if I take away the # from the screen.fill() grid disappears but rect moves correct with no tail I want both to happen.
The issue with your code is that it isn't doing what you might expect. Every time you iterate through your while loop, you increment the x and y values by 16 and draw a single horizontal and vertical line. This appears to draw a grid, because the screen is NOT cleared automatically between your while loop iterations. However, as a result, after a while, your x and y values increase to the point that you start drawing more and more grid lines that are off the screen (try to print out the start and end points for these lines in your loop if this is unclear)!
When you add the screen.fill(BLACK) call before you start drawing grid lines, you basically clear the entire screen. Since you do not redraw your old grid lines, only the next pair of grid lines are drawn and the rest are lost. Since the new pair of grid lines eventually goes off the screen (as described in the first paragraph), your code does not appear to draw any grid lines.
When you comment out screen.fill(BLACK) instead, previous drawings are not cleared. All of the grid is drawn over multiple while loop iterations (in addition to wastefully drawing new grid lines off screen), as is all of the previously positions your player is drawn at.
To solve this, your game drawing loop should be structured like this:
Clear the screen.
Draw the entire grid, not just two lines at a time. Do this in a function (e.g. draw_grid).
Draw the rectangle at the new player position. Do this in a function as well (e.g. draw_player).
Rinse and repeat next iteration!
Implementing these small changes, your code could look like this:
import pygame
from pygame.locals import *
def draw_grid():
screen.fill(BLACK)
for y in range(height, h, height):#horizontal lines
pygame.draw.line(screen, bg, (width, y), (w - width, y), 1)
for x in range(width, w, width):#vertical lines
pygame.draw.line(screen, bg, (x, height), (x, h - height), 1)
def draw_player():
pygame.draw.rect(screen, [255, 255, 55], [left, top, width, height], 0)
pygame.init()
clock = pygame.time.Clock()
w = 1008
h = 640
left = 16
top = 16
width = 16
height = 16
YELLOW = (255, 255, 55)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
bg = (255, 255, 255)
x = 0
y = 0
screen = pygame.display.set_mode((w, h))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == KEYDOWN:
if event.key == K_DOWN:
top += 16
if event.key == K_UP:
top -= 16
if event.key == K_RIGHT:
left += 16
if event.key == K_LEFT:
left -= 16
draw_grid()
draw_player()
pygame.display.flip()
pygame.quit()
That being said, there is plenty of room for improvement for this code, which I will leave up to you. The big issue is that you are using a bunch of global variables. These are basically all of the variables listed before your while loop. Use of such global variables is "evil" when they are mutable. As your code gets bigger, it will become more difficult to reason what functions are changing these variables, since every function could potentially have access to these variables.

How to compile code into a Class with functions

I don't know how to turn this code into a class statement and then run it!
I want to have the same result as this but using class statements.
import pygame
import random
pygame.init()
winh = 500
winl = 500
win = pygame.display.set_mode((winh, winl))
width = 20
vel = 5
y = 250
x = 250
score = 0
direction = "up"
class Dot():
def __init__(self):
self.x = x
self.y = y
self.width = width
self.direction = 'right'
def direction(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.direction = "up"
if keys[pygame.K_a]:
self.direction = "left"
if keys[pygame.K_s]:
self.direction = "down"
if keys[pygame.K_d]:
self.direction = "right"
p = True
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
direction = "up"
if keys[pygame.K_a]:
direction = "left"
if keys[pygame.K_s]:
direction = "down"
if keys[pygame.K_d]:
direction = "right"
if direction == "up":
y -= width
if direction == "down":
y += width
if direction == "left":
x -= width
if direction == "right":
x += width
if y > winh - 20:
y = 20
if y < 20:
y = winh - 20
if x > winl - 20:
x = 20
if x < 0:
x = winl - 20
win.fill((0,0,0))
dot = pygame.draw.rect(win, (0, 255, 0), (x, y, width, width))
pygame.display.update()
pygame.quit()
So you're class was getting there already. A class is an encapsulation of data and functions that are common to that "object". Anything for or about that object should go into this class. But when code for keyboard handling was put into a Dot class, this was going in the wrong direction. A Dot is a thing draw to a screen, it has a size and colour, and position. It should not be responsible for handling user-input. That's outside the scope of what a Dot is.
When updating the class, I chose to base it on the PyGame sprite class. This is a little bit more work at first, and it needs to be written in a particular way - have an image, and a rect, and (hopefully) an update() function. But being a sprite it gets lots of functionality that's already written, including the PyGame collision routines.
Near the main loop, there's a couple of additions. The first is creating the sprite dotty, and a sprite-group sprites to hold it. It's a bit weird to create a sprite group for a single sprite, but I assumed in the future there would be more than one.
Then in the actual loop, the code calls sprites.update(). This is a handy routine that calls the update() function for every sprite in the group. Then later on, there's a call to sprites.draw( win ) which paints every sprite in the group to the screen. This uses the sprite's image and the rect to know where, and what to paint.
import pygame
import random
pygame.init()
WIN_H = 500
WIN_L = 500
win = pygame.display.set_mode((WIN_H, WIN_L))
width = 20
vel = 5
y = 250
x = 250
score = 0
direction = "up"
class Dot( pygame.sprite.Sprite ):
def __init__( self, x,y, size=20, direction='right', colour=(0,255,0) ):
pygame.sprite.Sprite.__init__( self )
self.size = size
self.colour = colour
self.direction = direction
self.image = pygame.Surface( ( size, size ), pygame.SRCALPHA )
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.image.fill( colour )
def move( self, x_dir, y_dir ):
global WIN_H, WIN_L
# Adjust our co-oridinates
self.rect.x += x_dir
self.rect.y += y_dir
# Stay on the screen, and wrap around
if (self.rect.left >= WIN_L ):
self.rect.right = 0
elif (self.rect.right <= 0 ):
self.rect.left = WIN_L
if (self.rect.top >= WIN_H ):
self.rect.bottom = 0
elif (self.rect.bottom <= 0):
self.rect.top = WIN_H
def update( self ):
# TODO - handle animation, collisions, whatever
pass
# make some sprites
dotty = Dot( x,y )
sprites = pygame.sprite.Group() # a group, for a single sprite (or more)
sprites.add( dotty )
p = True
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
direction = "up"
if keys[pygame.K_a]:
direction = "left"
if keys[pygame.K_s]:
direction = "down"
if keys[pygame.K_d]:
direction = "right"
if direction == "up":
dotty.move( 0, -width )
if direction == "down":
dotty.move( 0, width )
if direction == "left":
dotty.move( -width, 0 )
if direction == "right":
dotty.move( width, 0 )
# update all the sprites
sprites.update()
# repaint the screen
win.fill((0,0,0))
sprites.draw( win )
#dot = pygame.draw.rect(win, (0, 255, 0), (x, y, width, width))
pygame.display.update()
pygame.quit()
The movement code was moved into the Dot class. This allows the Dot to adjust its position, but also take care of screen-wrapping issues. If this sprite was say, some kind of projectile, maybe when it crossed the screen boundary it would call the sprite.kill() function to remove itself from the sprite group. But since a Dot is clearly not a projectile, it can warp to the other side.

Is there any way to make the pygame window follow the mouse? [duplicate]

I want to create a pygame window that doesn't have a frame and that moves when the user clicks on it and moves the mouse.
I tried this script but when I click on the windows, '0' is printed but not '1'
Something is wrong in my script.
# coding : utf-8
import pygame
from pygame.locals import *
from random import randint
from os import environ
from math import sqrt
pygame.init()
max_fps = 250
clock = pygame.time.Clock()
window_size_x, window_size_x = 720, 360
infos = pygame.display.Info()
environ['SDL_VIDEO_WINDOW_POS'] = str(int(infos.current_w / 2)) + ',' + str(int(infos.current_h / 2)) # center the window
screen = pygame.display.set_mode((window_size_x, window_size_x), pygame.NOFRAME)
def move_window(): # move the windows when custom bar is hold
window_x, window_y = eval(environ['SDL_VIDEO_WINDOW_POS'])
mouse_x, mouse_y = pygame.mouse.get_pos()
dist_x , dist_y = mouse_x - window_x, mouse_y - window_y # calculate the distance between mouse and window origin
for event in pygame.event.get():
if event.type != MOUSEBUTTONUP: # while bar is hold
print('1')
mouse_x, mouse_y = pygame.mouse.get_pos()
environ['SDL_VIDEO_WINDOW_POS'] = str(mouse_x - dist_x) + ',' + str(mouse_x - dist_x)
screen = pygame.display.set_mode((window_size_x, window_size_x), pygame.NOFRAME) # rebuild window
def main():
run = True
while run :
screen.fill((255, 255, 255))
pygame.display.update()
clock.tick(60) # build frame with 60 frame per second limitation
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
print('0')
move_window()
if __name__ == '__main__':
main()
Write a function, which moves the window from dependent on a previous mouse position (start_x, start_y) and a mouse position (new_x, new_y)
def move_window(start_x, start_y, new_x, new_y):
global window_size_x, window_size_y
window_x, window_y = eval(environ['SDL_VIDEO_WINDOW_POS'])
dist_x, dist_y = new_x - start_x, new_y - start_y
environ['SDL_VIDEO_WINDOW_POS'] = str(window_x + dist_x) + ',' + str(window_y + dist_y)
# Windows HACK
window_size_x += 1 if window_size_x % 2 == 0 else -1
screen = pygame.display.set_mode((window_size_x, window_size_y), pygame.NOFRAME)
In this function is a very important line:
window_size_x += 1 if window_size_x % 2 == 0 else -1
this line changes the width of the window from alternately by +1 and -1. On Windows systems there seems to be a bug, which ignores the new position parameter, if the size of the window didn't change.
This "hack" is a workaround, which slightly change the size of the window whenever the position is changed.
A different approach, with no flickering, may look as follows. Note, though, that this version is significantly slower:
def move_window(start_x, start_y, new_x, new_y):
global window_size_x, window_size_y
buffer_screen = pygame.Surface((window_size_x, window_size_y))
buffer_screen.blit(pygame.display.get_surface(), pygame.display.get_surface().get_rect())
window_x, window_y = eval(environ['SDL_VIDEO_WINDOW_POS'])
dist_x, dist_y = new_x - start_x, new_y - start_y
environ['SDL_VIDEO_WINDOW_POS'] = str(window_x + dist_x) + ',' + str(window_y + dist_y)
window_size_x += 1 if window_size_x % 2 == 0 else -1
screen = pygame.display.set_mode((window_size_x, window_size_y), pygame.NOFRAME)
screen.blit(buffer_screen, buffer_screen.get_rect())
pygame.display.flip()
Change the position on MOUSEMOTION and MOUSEBUTTONUP:
def main():
run = True
pressed = False
start_pos = (0,0)
while run :
# [...]
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
pressed = True
start_pos = pygame.mouse.get_pos()
elif event.type == MOUSEMOTION:
if pressed:
new_pos = pygame.mouse.get_pos()
move_window(*start_pos, *new_pos)
pygame.event.clear(pygame.MOUSEBUTTONUP)
elif event.type == MOUSEBUTTONUP:
pressed = False
new_pos = pygame.mouse.get_pos()
move_window(*start_pos, *new_pos)
Full example program:
# coding : utf-8
import pygame
from pygame.locals import *
from os import environ
pygame.init()
clock = pygame.time.Clock()
window_size_x, window_size_y = 720, 360
infos = pygame.display.Info()
environ['SDL_VIDEO_WINDOW_POS'] = str(int(infos.current_w/2)) + ',' + str(int(infos.current_h/2))
screen = pygame.display.set_mode((window_size_x, window_size_x), pygame.NOFRAME)
def move_window(start_x, start_y, new_x, new_y):
global window_size_x, window_size_y
window_x, window_y = eval(environ['SDL_VIDEO_WINDOW_POS'])
dist_x, dist_y = new_x - start_x, new_y - start_y
environ['SDL_VIDEO_WINDOW_POS'] = str(window_x + dist_x) + ',' + str(window_y + dist_y)
window_size_x += 1 if window_size_x % 2 == 0 else -1
screen = pygame.display.set_mode((window_size_x, window_size_y), pygame.NOFRAME)
def main():
run = True
pressed = False
start_pos = (0,0)
while run :
screen.fill((255, 255, 255))
pygame.display.update()
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
if event.type == MOUSEBUTTONDOWN:
pressed = True
start_pos = pygame.mouse.get_pos()
elif event.type == MOUSEMOTION:
if pressed:
new_pos = pygame.mouse.get_pos()
move_window(*start_pos, *new_pos)
pygame.event.clear(pygame.MOUSEBUTTONUP)
elif event.type == MOUSEBUTTONUP:
pressed = False
new_pos = pygame.mouse.get_pos()
move_window(*start_pos, *new_pos)
if __name__ == '__main__':
main()
This solution no longer works under Windows systems and with Pygame 2.0. The position of a window can, however, be changed with the WINAPI function MoveWindow:
import pygame
from ctypes import windll
pygame.init()
screen = pygame.display.set_mode((400, 400), pygame.NOFRAME)
clock = pygame.time.Clock()
def moveWin(new_x, new_y):
hwnd = pygame.display.get_wm_info()['window']
w, h = pygame.display.get_surface().get_size()
windll.user32.MoveWindow(hwnd, new_x, new_y, w, h, False)
window_pos = [100, 100]
moveWin(*window_pos)
run = True
while run :
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
elif event.type == pygame.MOUSEMOTION:
if pygame.mouse.get_pressed()[0]:
window_pos[0] += event.rel[0]
window_pos[1] += event.rel[1]
moveWin(*window_pos)
screen.fill((255, 255, 255))
pygame.display.update()
clock.tick(60)
This code use only one for event loop with MOUSEBUTTONDOWN to set moving = True, MOUSEBUTTONUP to set moving = False and MOUSEMOTION which changes window's position when moving is True.
After move I use pygame.event.clear(pygame.MOUSEBUTTONUP) to remove this type of events because new window was getting this even and it was stoping window.
import pygame
from os import environ
# --- constants --- (UPPER_CASE_NAMES)
WINDOW_WIDTH = 720
WINDOW_HEIGHT = 360
# --- main ---
def main():
pygame.init()
infos = pygame.display.Info()
environ['SDL_VIDEO_WINDOW_POS'] = '{},{}'.format(infos.current_w//2, infos.current_h//2) # center the window
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.NOFRAME)
moving = False
clock = pygame.time.Clock()
run = True
while run:
screen.fill((255, 255, 255))
pygame.display.update()
clock.tick(60) # build frame with 60 frame per second limitation
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
if not moving:
print('MOUSEBUTTONDOWN')
moving = True
# remeber start distance
#window_x, window_y = eval(environ['SDL_VIDEO_WINDOW_POS'])
window_x, window_y = map(int, environ['SDL_VIDEO_WINDOW_POS'].split(','))
dist_x = event.pos[0] # mouse x
dist_y = event.pos[1] # mouse y
elif event.type == pygame.MOUSEBUTTONUP:
if moving:
print('MOUSEBUTTONUP')
moving = False
elif event.type == pygame.MOUSEMOTION:
if moving:
print('moving')
mouse_x, mouse_y = pygame.mouse.get_pos()
diff_x = dist_x - mouse_x
diff_y = dist_y - mouse_y
window_x -= diff_x
window_y -= diff_y
environ['SDL_VIDEO_WINDOW_POS'] = "{},{}".format(window_x, window_y)
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.NOFRAME) # rebuild window
pygame.event.clear(pygame.MOUSEBUTTONUP) # to remove MOUSEBUTTONUP event which stops moving window
if __name__ == '__main__':
main()
The code's not ready, but I was getting there.
I had to abandon it, but it might help someone save some time.
Press f for full-screen mode. The part that is not done,
supposed to be the window-mode resize part. You must have an in-depth
look at toogle_resize() function for that.
The resize it's taking full desktop resolution and compare it
to the space between clik (MOUSEBUTTONDOWN) and (MOUSEBUTTONUP).
Or at least that's how I wanted it to work. Good luck!
Also the code needs to be optimized, it is raw.
import pyautogui
import pygame
import sys
from pygame.locals import *
from pyinput.mouse import Controller
Mouse controller gets the position on desktop, but I had to run it once more inside the while loop to get the updated values.
mouse = Controller()
standard = current_mouse_position = mouse.position
pygame.init()
Silkscreen = False
blueGray = (73, 111, 135)
width, height = pyautogui.size()
w = width / 4
h = height / 4
ww = width - w
hh = height - h
wi = ww - 4
hi = hh - 4
Set the background and the flags
room = pygame.image.load('img.png')
full_flags = pygame.FULLSCREEN | pygame.SCALED |
pygame.NOFRAME
normal_flags = pygame.NOFRAME | pygame.RESIZABLE |
pygame.SCALED
def toggle_fullscreen(f):
if f:
return pygame.display.set_mode((ww, hh), full_flags)
# pygame.display.set_mode(size, normal_flags) # uncomment
this to see issue being fixed as a workaround
return pygame.display.set_mode((ww, hh), normal_flags)
def toggle_resize(click):
if click:
return pygame.display.set_mode((ww + movement, hh),
normal_flags) # Expands by resize_y
# Set up the drawing window
screen = pygame.display.set_mode((ww, hh), normal_flags)
def bg():
screen.blit(room, (0, 0))
def border():
pygame.draw.rect(screen, blueGray, (0, 0, ww, hh), 2) #
width = 3
clock = pygame.time.Clock()
# Run until the user asks to quit
running = True
while running:
current_mouse_position = mouse.position
# print(current_mouse_position[0])
bg()
mw, mh = pygame.mouse.get_pos() # 0 - 1439(window size)
In the next line, I checked if the mouse is on the window border. Apply only to the left, right border. You must create code for the top, bottom border. Left, right borders, 3 pixels range each side.
if mw <= 3 or (mw > wi and mw < ww):
active = True
moveOne = 0
# print("data", moveOne)
# print(type(moveOne))
moveTwo = 0
# event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
sys.exit()
# print(mw, mh)
If the user clicks, the standard variable at the beginning of the code it gets the mouse current position. You must run the active bool and check if it is True if you want to start resizing from the border of the window.
The code is very complicated and needs some optimization, but I bet you gonna get it right. Good luck!;)
if event.type == pygame.MOUSEBUTTONDOWN:
standard = current_mouse_position[0]
print(standard)
if event.type == pygame.MOUSEBUTTONUP:
moveTwo = current_mouse_position[0]
movement = standard - moveTwo
print("This is:", moveTwo)
toggle_resize(click=MOUSEBUTTONUP)
active = False
Full-screen handler from here down. Press f for full screen and back to window mode.
if event.type == pygame.KEYDOWN:
Silkscreen = not Silkscreen
if event.key == K_f:
screen = toggle_fullscreen(Silkscreen)
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
border()
# Flip the display
pygame.display.flip()
clock.tick(144) # framerate
# Done! Time to quit.
pygame.quit()'
Here is a version of #Rabbid76's answer for Pygame 2. Note that this example may break as the module _sdl2.video, used to set the window position, is experimental.
move_window.py
import pygame
from pygame._sdl2.video import Window
start_pos = pygame.Vector2(0, 0) #Initial mouse position
pressed = False #Flag that denotes when the mouse is being continuously pressed down
def move_window(window : Window, start_mouse_pos : pygame.Vector2, new_mouse_pos : pygame.Vector2) -> None:
"""Moves the window by the offset between start_mouse_pos and new_mouse_pos"""
screen = pygame.display.get_surface()
buffer_screen = pygame.Surface((window.size[0], window.size[1]))
buffer_screen.blit(screen, screen.get_rect())
window_pos_Vec2 = pygame.Vector2(window.position)
window.position = window_pos_Vec2 + new_mouse_pos - start_mouse_pos
screen.blit(buffer_screen, buffer_screen.get_rect())
pygame.display.flip()
def check_event(window : Window, event : pygame.event, move_area : pygame.Rect = pygame.Rect(-1, -1, 1, 1)) -> None:
"""Takes a window and event and updates the window position accordingly. \n
move_area can be used to set what area of the screen can be clicked in order to move the window. \n
move_area defaults to a dummy rect which is then internally changed to the full window."""
global start_pos, pressed
if move_area == pygame.Rect(-1, -1, 1, 1):
move_area = pygame.Rect((0, 0), window.size)
mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
if move_area.collidepoint(mouse_pos):
if event.type == pygame.MOUSEBUTTONDOWN:
pressed = True
start_pos = mouse_pos
elif event.type == pygame.MOUSEMOTION and pressed:
move_window(window, start_pos, mouse_pos)
elif event.type == pygame.MOUSEBUTTONUP:
pressed = False
move_window(window, start_pos, mouse_pos)
else:
pressed = False
And in your main file:
import pygame
from pygame._sdl2.video import Window
screen = pygame.display.set_mode(...)
window = Window.from_display_module()
#...
while True:
for event in pygame.event.get():
#...
move_window.check_event(window, event)

Resources