Pygame - blitting for a preview picture - python

First off, this is a school assignment so I want to be upfront about that. Second I'm just asking for advice on the approach, possible help with the code. I'm working on a MSPAINT style clone using some pre-existing code from our book. The code already has the use of the draw.line when pressing mouse button 1. Teacher wants us to add ability to make circles or rectangles. I'm working on the circle part and I have figured out (thanks to the forums on here) how to implement what I wanted to do with the MOUSEBUTTONDOWN and MOUSEBUTTONUP events.. This has brought me to a new Question.. How would I blit then erase then blit a preview of the circle until it is the size the user wants and they release the MOUSEBUTTON and view the final blit...
while keepGoing:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
elif event.type == pygame.MOUSEMOTION:
lineEnd = pygame.mouse.get_pos()
if pygame.mouse.get_pressed() == (1,0,0):
pygame.draw.line(background, drawColor, lineStart, lineEnd, lineWidth)
lineStart = lineEnd
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
circleStart = pygame.mouse.get_pos()
elif event.type == pygame.MOUSEBUTTONUP and event.button == 3:
circleEnd = pygame.mouse.get_pos()
size = (circleEnd[0] - circleStart[0])
pygame.draw.circle(background, drawColor, circleStart, size, lineWidth)
elif event.type == pygame.KEYDOWN:
myData = (event, background, drawColor, lineWidth, keepGoing)
myData = checkKeys(myData)
event, background, drawColor, lineWidth, keepGoing) = myData
Thanks so much
-Ben

So after some thinking this is the best solution I came up with using pygame. Tell me what you think and if it has helped you.
import pygame,sys,math #---- Import modules we will need
pygame.init() #---- Initialize the module
def get_rad(origin_x,origin_y,x,y): #----- Returns the appropriate radius
return math.sqrt((origin_x - x)**2 + (origin_y - y)**2) #----- Distance between 2
#----- points
screen = pygame.display.set_mode((400,400)) #----- Sets up the screen
clock = pygame.time.Clock() #------- Sets up the clock
mouse_button = 0 #--------- This variable is used to determine whether a mouse button
#--------- has been pressed
draw_final_circle = False #---------- This variable lets us know that we should draw the
#---------- final circle
while True: #------ main loop
clock.tick(60) #------ Limit the Fps
mouse_button0 = mouse_button #-------- This variable holds the previous value of
#-------- mouse_button(it will be useful later)
mouse_x,mouse_y = pygame.mouse.get_pos() #----- Get the mosue coordinates
for e in pygame.event.get(): #---- Cycle through events
if e.type == pygame.QUIT: pygame.quit();sys.exit() #--Quit when window is closed
if e.type == pygame.MOUSEBUTTONDOWN: #---- If the mouse button is pressed
if mouse_button == 0: #---- if the mouse button is released
mouse_button = 1 #----- set it to pressed basically
originx,originy = mouse_x,mouse_y #---- keep the mouse_x,mouse_y pos
if e.type == pygame.MOUSEBUTTONUP: #---- if the mouse button is released
if mouse_button == 1: #-------- if it is pressed
mouse_button = 0 #--------- set it to released
screen.fill((255,255,255)) #---- clear the screen
#-------- If a mouse button is pressed and a circle can be drawn (rad>width) ------#
if mouse_button == 1 and get_rad(originx,originy,mouse_x,mouse_y) > 1:
rad = int(get_rad(originx,originy,mouse_x,mouse_y)) #---- get the radius(as int)
pos = mouse_x,mouse_y
pygame.draw.circle(screen,(0,0,0),pos,rad,1) #--- draw the circle
#----------------------------------------------------------------------------------#
#---------- if the button is released but in the previous loop it was pressed -----#
if mouse_button == 0 and mouse_button0 == 1:
draw_final_circle = True #----- set the final circle boolean to True
if draw_final_circle: #----- if the final circle is decided
pygame.draw.circle(screen,(0,0,0),pos,rad,1) #---- keep drawing it
pygame.display.flip() #----- flip the buffer

I suggest you implement the different modes of your drawing program into different classes that represent the current mode and its state. This way, implementing different modes become very easy.
As for a circle drawing mode, you want to take a copy the screen surface when the user presses the mouse button, and blit that copy to the screen every frame.
Then draw your circle on that copy. This way, you basically "erase" the temporary circles.
Here's a simple example. Press SPACE to cycle between the different modes (draw, circle, rect) and TAB for different colors:
import pygame
from math import hypot
from itertools import cycle
from operator import itemgetter
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
colors = cycle(sorted(pygame.color.THECOLORS.iteritems(), key=itemgetter(0)))
color = next(colors)[1]
class DrawMode(object):
def __init__(self):
self.last = None
def handle(self, e):
if e.type == pygame.MOUSEBUTTONDOWN and e.button == 1:
self.last = pygame.mouse.get_pos()
if e.type == pygame.MOUSEBUTTONUP and e.button == 1:
self.last = None
def draw(self, screen):
pos = pygame.mouse.get_pos()
if self.last:
pygame.draw.line(screen, color, self.last, pos)
self.last = pos
class ClickReleaseMode(object):
def __init__(self):
self.tmp = None
self.start = None
def handle(self, e):
if e.type == pygame.MOUSEBUTTONDOWN and e.button == 1:
self.start = pygame.mouse.get_pos()
if e.type == pygame.MOUSEBUTTONUP and e.button == 1:
self.start = self.tmp = None
def draw(self, screen):
if not self.tmp:
self.tmp = screen.copy()
pos = pygame.mouse.get_pos()
screen.blit(self.tmp, (0,0))
if self.start:
self.do_draw(screen, pos)
class CircleMode(ClickReleaseMode):
def __init__(self):
super(CircleMode, self).__init__()
def do_draw(self, screen, pos):
r = hypot(pos[0] - self.start[0], pos[1] - self.start[1])
if r >= 2:
pygame.draw.circle(screen, color, self.start, int(r), 2)
class RectMode(ClickReleaseMode):#
def __init__(self):
super(RectMode, self).__init__()
def do_draw(self, screen, pos):
p = pos[0] - self.start[0], pos[1] - self.start[1]
pygame.draw.rect(screen, color, pygame.Rect(self.start, p), 2)
quit = False
modes = cycle((DrawMode, CircleMode, RectMode))
mode = next(modes)()
while not quit:
quit = pygame.event.get(pygame.QUIT)
for e in pygame.event.get():
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
mode = next(modes)()
print 'enter', mode.__class__.__name__
if e.key == pygame.K_TAB:
name, color = next(colors)
print 'changing color to', name, color
mode.handle(e)
mode.draw(screen)
pygame.display.flip()
clock.tick(60)

Related

pygame rect is slipping out of the mouse curser

I have a method which moves rects around when you drag them
everything is working good except that when I move the mouse fast it ends up outside the rect therefore making the rect stop moving
even though I am setting the rect center to the mouse pos
so, How can I make the rect never slip out of the mouse curser?
def play_card(self, events):
global mousebutton
mousebutton = pg.mouse.get_pressed()
for index, card in enumerate(self.cards_rects):
posx,posy = pg.mouse.get_pos()
if mousebutton[0] and card.collidepoint(posx, posy):
print("clicked")
self.clicked = True
else:
self.clicked = False
if self.clicked:
x,y = pg.mouse.get_pos()
self.cards_rects[index].center= x,y
I suggest using the MOUSEBUTTONDOWN, MOUSEBUTTONUP and MOUSEMOTION event for dragging. Test if the mouse collides with the object, set an attribute indicating that the dragging has started and calculate the position of the mouse relative to the object in MOUSEBUTTONDOWN:
if event.type == pygame.MOUSEBUTTONDOWN:
self.dragging = self.rect.collidepoint(event.pos)
self.rel_pos = event.pos[0] - self.rect.x, event.pos[1] - self.rect.y
Set the new position in 'MOUSEMOTION' depending on the new mouse position when in drag mode:
if event.type == pygame.MOUSEMOTION and self.dragging:
self.rect.topleft = event.pos[0] - self.rel_pos[0], event.pos[1] - self.rel_pos[1]
End dragging in MOUSEBUTTONUP:
if event.type == pygame.MOUSEBUTTONUP:
self.dragging = False
Minimal example:
import pygame
class DragOperator:
def __init__(self, rect):
self.rect = rect
self.dragging = False
self.rel_pos = (0, 0)
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN:
self.dragging = self.rect.collidepoint(event.pos)
self.rel_pos = event.pos[0] - self.rect.x, event.pos[1] - self.rect.y
if event.type == pygame.MOUSEBUTTONUP:
self.dragging = False
if event.type == pygame.MOUSEMOTION and self.dragging:
self.rect.topleft = event.pos[0] - self.rel_pos[0], event.pos[1] - self.rel_pos[1]
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
rectangle = pygame.Rect(0, 0, 40, 40)
rectangle.center = window.get_rect().center
drag_rectangle = DragOperator(rectangle)
run = True
while run:
clock.tick(60)
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
drag_rectangle.update(event_list)
rectangle_color = (0, 255, 0) if drag_rectangle.dragging else (255, 0, 0)
window.fill(0)
pygame.draw.rect(window, rectangle_color, rectangle)
pygame.display.flip()
pygame.quit()
exit()
In this part of the code:
if mousebutton[0] and card.collidepoint(posx, posy):
print("clicked")
self.clicked = True
else:
self.clicked = False
The else means that self.clicked will be set False whenever either the mouse is released (not mousebutton[0]) or the mouse escapes from the card by a quick movement (not card.collidepoint(posx, posy)). So the quick movement will cause the card to be dropped.
If the mouse is outside of the card, there are two possibilities:
We were not already dragging the card, and we are still not dragging the card. So self.clicked should be False, but it was already False, so we don't need to do anything.
We were dragging the card, and we will still be dragging the card. So again, we don't need to do anything; self.clicked was True, and should still be True.
We only want to actually set the value back to False if the mouse button is released, so we can e.g. replace the else with elif:
if mousebutton[0] and card.collidepoint(posx, posy):
print("clicked")
self.clicked = True
elif not mousebutton[0]:
self.clicked = False
Another way to do this logic:
if not mousebutton[0]:
self.clicked = False
elif card.collidepoint(posx, posy):
self.clicked = True
That way avoids repeating the mention of mousebutton. If we get to the elif, we already know that mousebutton[0] is set, since otherwise the if branch would have been taken.

Pygame collidepoint() dont account for the blit()

Sorry for any gramatical mistakes as i'm a non english speaker
I'm working on a small game during this quarantine and wanted to make a map maker in order to speed up the process of designing levels.
My probleme is that even if the grid show as i expect on the screen, the "getcollide" function does not account for the "blit" of the surface. I spend two days on this and can't think of another way to work this around, here is a sample code :
import pygame
def run(l, h, fps, scene):
pygame.init()
ecran = pygame.display.set_mode((l, h))
clock = pygame.time.Clock()
scene_active = scene
while scene_active != None:
key = pygame.key.get_pressed()
filtre_touche = []
for event in pygame.event.get():
close = False
if event.type == pygame.QUIT:
close = True
elif event.type == pygame.KEYDOWN:
alt = key[pygame.K_LALT] or touche_active[pygame.K_RALT]
if event.key == pygame.K_ESCAPE:
close = True
elif event.key == pygame.K_F4 and alt:
close = True
if close :
scene.quit()
else :
mouse_pos = pygame.mouse.get_pos()
filtre_touche.append(event)
scene_active.traite_input(filtre_touche, key, mouse_pos)
scene_active.update()
scene_active.render(ecran)
scene_active = scene_active.next
pygame.display.flip()
clock.tick(fps)
surface = pygame.Surface((320,320))
M = []
for i in range(10):
x = []
for j in range(10):
x.append(pygame.draw.rect(surface, (255,255,255),(i*32, j*32, 32,32), 1))
M.append(x)
print(M)
class FirstScreen():
def __init__(self):
self.next = self
def traite_input(self, evenement, touche, mouse_pos):
for event in evenement :
if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
pygame.quit()
for line in M :
for row in line:
if row.collidepoint(mouse_pos):
print('collide')
def update (self):
pass
def render(self, ecran):
ecran.fill((200,200,200))
ecran.blit(surface, (350,250), surface.get_rect())
run(800,600,60, FirstScreen())
If you run this, you will see that the surface is where we expect it to be and the tile actually are of 32 by 32. But hoovering the mouse over the grid has no effect. The print statement is called when you hoover the mouse where the grid should be if i've blit the surface at 0,0.
Am I missing something ? After searching this website, the pygame doc and all kind of forums i seems to be the only one with this trouble, what am i doing wrong ?
Its because your putting the grid on a surface, the bliting the surface on the screen, so when you make the grid, you start at 0 of the surface. Then blit the surface in the middle(ish) of the screen, when you do the collidepoint. the mouse hovers over it when near 0, the top left of the whole screen. Basically, the position of the mouse on the window is translated to the surface so when you hover the mouse over the grid, the pos of the mouse in the window is not on the grid on the surface. if that makes sense.
Easy fix:
if row.collidepoint((mouse_pos[0] - 350,mouse_pos[1] - 250)):
move the mouse pos back so 0,0 on the surface is 0,0 on the window

pygame: drawing a selection rectangle with the mouse

I have written a simple breakout game in pygame and am writing a level editor. Everything was working until I tried to add a selecting rectangle that has the transparent look (like the one on my desktop background). I can get a rectangle (sorta) but everything else vanishes, and it isn't semi-transparent.
code:
pygame.init()
screen = pygame.display.set_mode(size)
mousescreen = pygame.Surface((screen.get_size())).convert_alpha()
...
in the designing loop:
global xpos, ypos, theBricks, clock, mousedrag, mouseRect
global designing, titles
global theLevels, level, cur_max_level, max_level
mousedrag = False
mouseRect = None
mouseDown = False
while designing:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mouseDown = True
mpos = pygame.mouse.get_pos()
x_position = mpos[0]
y_position = mpos[1]
xpos = ((x_position-left)/BRW) * BRW + left
ypos = ((y_position-top)/BRH) * BRH + top
elif event.type == MOUSEMOTION:
if mouseDown:
newx_pos = mpos[0]
newy_pos = mpos[1]
mousedrag = True
if mousedrag:
mouseRect = Rect(newx_pos, newy_pos, xpos, ypos)
elif event.type == MOUSEBUTTONUP:
if mousedrag:
mousedrag = False
else:
if is_a_brick(xpos, ypos):
del_brick(xpos, ypos)
else:
make_brick(xpos, ypos)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
designing = False
titles = True
...
in the update screen function:
for bricks in theBricks:
pygame.draw.rect(screen, GREEN, bricks.rect)
if mousedrag:
pygame.draw.rect(mousescreen, RED, mouseRect, 50)
screen.blit(mousescreen, (0,0))
pygame.draw.rect(screen, WHITE, (xpos, ypos, BRW, BRH))
pygame.display.update()
The rectangle is not transparent and everything else vanishes off the screen? Where am I going wrong?
I'm not sure if .convert_alpha() is creating a transparent screen like you think. Try setting the alpha level on mousescreen explicitly:
mousescreen = pygame.Surface((screen.get_size()))
mousescreen.set_alpha(100) # this value doesn't have to be 100
Another way to achieve the same effect is to draw your rect as 4 lines directly onto the screen which means you don't have to have mousescreen at all. In your update screen function:
if mousedrag:
mouseRectCorners = [mouseRect.topleft,
mouseRect.topright,
mouseRect.bottomright,
mouseRect.bottomleft]
pygame.draw.lines(screen, RED, True, mouseRectCorners, 50)
Just make sure you draws these lines after any other objects or they might get hidden. I'm not sure if this option is really considered best practice but it's always good to have options.

Python/Pygame mouse position does not update (blit function)

I'm trying to make a simple menu using Pygame but I found that whenever I use pygame.mouse.get_position, it does blit what i want but i have to keep move my mouse to make my picture keep blitting.
import pygame
import sys
pygame.init()
screen = pygame.display.set_mode((800,600))
pygame.display.set_caption('cursor test')
cursorPng = pygame.image.load('resources/images/cursor.png')
start = pygame.image.load('resources/images/menuStart.jpg')
enemy = pygame.image.load('resources/images/enemy-1.png')
white = (255,255,255)
black = (0,0,0)
clock = pygame.time.Clock()
FPS = 60
while True:
screen.fill(white)
pygame.mouse.set_visible(False)
x,y = pygame.mouse.get_pos()
x = x - cursorPng.get_width()/2
y = y - cursorPng.get_height()/2
screen.blit(cursorPng,(x,y))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEMOTION:
if x < 50 and y < 250:
screen.blit(enemy,(100,100))
clock.tick(FPS)
pygame.display.update()
what's wrong?
Take a look at your code:
for event in pygame.event.get():
...
elif event.type == pygame.MOUSEMOTION:
if x < 50 and y < 250:
screen.blit(enemy,(100,100))
You check for events, and if you detect that the mouse is being moved (and only then), you draw the image to the screen.
If you want to draw the image even if the mouse is not being moved, just stop checking for the MOUSEMOTION event and simply draw the image always:
while True:
screen.fill(white)
pygame.mouse.set_visible(False)
x,y = pygame.mouse.get_pos()
x = x - cursorPng.get_width()/2
y = y - cursorPng.get_height()/2
screen.blit(cursorPng,(x,y))
if x < 50 and y < 250:
screen.blit(enemy,(100,100))
for event in pygame.event.get():
...
You need to blit into the screen a Surface and a Rect.
First, use this snippet I use for loading images. It makes sure the image is loaded correctly:
def loadImage(name, alpha=False):
"Loads given image"
try:
surface = pygame.image.load(name)
except pygame.error:
raise SystemExit('Could not load image "%s" %s' %
(name, pygame.get_error()))
if alpha:
corner = surface.get_at((0, 0))
surface.set_colorkey(corner, pygame.RLEACCEL)
return surface.convert_alpha()
Second, when you get the Surface, get its rect like this:
cursorSurf = loadImage('resources/images/cursor.png')
cursorRect = cursorSurf.get_rect()
Then, inside the update do the following:
cursorRect.center = pygame.mouse.get_pos()
And finnally, blit to screen like this:
screen.blit(cursorSurf, cursorRect)
Now you will notice your Mouse is being rendered correctly without having to move your mouse.

multiple clicks registering in pygame

I'm trying to make a board that changes color upon a left mouse click. But when I click it cycles through is_square_clicked() 3 times. That's a problem, I only want it to do it once. As you can probably guess this causes an issue for my program. So how do I limit it to 1 pass through per click? Thanks!
def is_square_clicked(mousepos):
x, y = mousepos
for i in xrange(ROWS):
for j in xrange(COLS):
for k in xrange(3):
if x >= grid[i][j][1] and x <= grid[i][j][1] + BLOCK:
if y >= grid[i][j][2] and y <= grid[i][j][2] + BLOCK:
if grid[i][j][0] == 0:
grid[i][j][0] = 1
elif grid[i][j][0] == 1:
grid[i][j][0] = 0
while __name__ == '__main__':
tickFPS = Clock.tick(fps)
pygame.display.set_caption("Press Esc to quit. FPS: %.2f" % (Clock.get_fps()))
draw_grid()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
mousepos = pygame.mouse.get_pos()
is_square_clicked(mousepos)
pygame.display.update()
The reason that it cycles through is because you hold down the mouse long enough for it to check three times. I think if you have it wait between clicks, or you have it check not every time a cycle, it should be fixed.
im going to guess that since the game loops more than once each click it changes more then once
even though a click is very fast the loop is looping faster (depending on the FPS)
here is an example that will change the color of the screen on each click:
"""Very basic. Change the screen color with a mouse click."""
import os,sys #used for sys.exit and os.environ
import pygame #import the pygame module
from random import randint
class Control:
def __init__(self):
self.color = 0
def update(self,Surf):
self.event_loop() #Run the event loop every frame
Surf.fill(self.color) #Make updates to screen every frame
def event_loop(self):
for event in pygame.event.get(): #Check the events on the event queue
if event.type == pygame.MOUSEBUTTONDOWN:
#If the user clicks the screen, change the color.
self.color = [randint(0,255) for i in range(3)]
elif event.type == pygame.QUIT:
pygame.quit();sys.exit()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1' #Center the screen.
pygame.init() #Initialize Pygame
Screen = pygame.display.set_mode((500,500)) #Set the mode of the screen
MyClock = pygame.time.Clock() #Create a clock to restrict framerate
RunIt = Control()
while 1:
RunIt.update(Screen)
pygame.display.update() #Update the screen
MyClock.tick(60) #Restrict framerate
this code will blit a random color background each time you click so you can probably figure out the proper way to do it from the above code
Good Luck!

Categories